พื้นฐานของ HTTP Server ของ package net/http
Tags: go, http
Published: 2021-03-06 01:23:03

package [`net/http`](https://pkg.go.dev/net/http) นั้นออกแบบให้เราสร้าง HTTP Server ขึ้นมาได้โดยสร้าง type ที่ชื่อว่า Server เพื่อเก็บค่าต่างๆของ Server แล้วก็มี method ชื่อ ListenAndServ ให้เรา start server พร้อมกับกำหนด Handler เพื่อทำงานเมื่อมี request เข้ามานั้นเอง

นี่คือ type Server ซึ่งเป็น struct ที่มี fields ต่างๆไว้กำหนดรายละเอียดของ server ซึ่งเบื้องต้นที่จำเป็นก็คือ Addr ไว้กำหนด TCP address (hostname:port) แล้วก็ Handler ไว้กำหนด Handler ที่จะทำงานเมื่อมี request เข้ามา

```go
type Server struct {
	// Addr optionally specifies the TCP address for the server to listen on,
	// in the form "host:port". If empty, ":http" (port 80) is used.
	// The service names are defined in RFC 6335 and assigned by IANA.
	// See net.Dial for details of the address format.
	Addr string

	Handler Handler // handler to invoke, http.DefaultServeMux if nil

	// TLSConfig optionally provides a TLS configuration for use
	// by ServeTLS and ListenAndServeTLS. Note that this value is
	// cloned by ServeTLS and ListenAndServeTLS, so it's not
	// possible to modify the configuration with methods like
	// tls.Config.SetSessionTicketKeys. To use
	// SetSessionTicketKeys, use Server.Serve with a TLS Listener
	// instead.
	TLSConfig *tls.Config

	// ReadTimeout is the maximum duration for reading the entire
	// request, including the body.
	//
	// Because ReadTimeout does not let Handlers make per-request
	// decisions on each request body's acceptable deadline or
	// upload rate, most users will prefer to use
	// ReadHeaderTimeout. It is valid to use them both.
	ReadTimeout time.Duration

	// ReadHeaderTimeout is the amount of time allowed to read
	// request headers. The connection's read deadline is reset
	// after reading the headers and the Handler can decide what
	// is considered too slow for the body. If ReadHeaderTimeout
	// is zero, the value of ReadTimeout is used. If both are
	// zero, there is no timeout.
	ReadHeaderTimeout time.Duration

	// WriteTimeout is the maximum duration before timing out
	// writes of the response. It is reset whenever a new
	// request's header is read. Like ReadTimeout, it does not
	// let Handlers make decisions on a per-request basis.
	WriteTimeout time.Duration

	// IdleTimeout is the maximum amount of time to wait for the
	// next request when keep-alives are enabled. If IdleTimeout
	// is zero, the value of ReadTimeout is used. If both are
	// zero, there is no timeout.
	IdleTimeout time.Duration

	// MaxHeaderBytes controls the maximum number of bytes the
	// server will read parsing the request header's keys and
	// values, including the request line. It does not limit the
	// size of the request body.
	// If zero, DefaultMaxHeaderBytes is used.
	MaxHeaderBytes int

	// TLSNextProto optionally specifies a function to take over
	// ownership of the provided TLS connection when an ALPN
	// protocol upgrade has occurred. The map key is the protocol
	// name negotiated. The Handler argument should be used to
	// handle HTTP requests and will initialize the Request's TLS
	// and RemoteAddr if not already set. The connection is
	// automatically closed when the function returns.
	// If TLSNextProto is not nil, HTTP/2 support is not enabled
	// automatically.
	TLSNextProto map[string]func(*Server, *tls.Conn, Handler)

	// ConnState specifies an optional callback function that is
	// called when a client connection changes state. See the
	// ConnState type and associated constants for details.
	ConnState func(net.Conn, ConnState)

	// ErrorLog specifies an optional logger for errors accepting
	// connections, unexpected behavior from handlers, and
	// underlying FileSystem errors.
	// If nil, logging is done via the log package's standard logger.
	ErrorLog *log.Logger

	// BaseContext optionally specifies a function that returns
	// the base context for incoming requests on this server.
	// The provided Listener is the specific Listener that's
	// about to start accepting requests.
	// If BaseContext is nil, the default is context.Background().
	// If non-nil, it must return a non-nil context.
	BaseContext func(net.Listener) context.Context

	// ConnContext optionally specifies a function that modifies
	// the context used for a new connection c. The provided ctx
	// is derived from the base context and has a ServerContextKey
	// value.
	ConnContext func(ctx context.Context, c net.Conn) context.Context
	// contains filtered or unexported fields
}
```

ส่วน Handler นั้นจริงๆแล้วเป็น interface หน้าตาแบบนี้

```go
type Handler interface {
	ServeHTTP(ResponseWriter, *Request)
}
```

นั่นคือเราจะสร้าง type อะไรก็ได้ขอให้ implements method ServeHTTP ตรงกับที่ interface Handler กำหนดก็พอก็สามารถกำหนดให้กับ Server ได้แล้ว

ทีนี้มาดูโค้ดที่ใช้ start HTTP Server ด้วย port 8000 สำหรับรับ HTTP request เบื้องต้นกัน

```go
package main

import (
	"log"
	"net/http"
)

type handler struct{}

func (h *handler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
	w.Write([]byte("Hello, World\n"))
}

func main() {
	server := http.Server{
		Addr:    ":8000",
		Handler: &handler{},
	}
	err := server.ListenAndServe()
	if err != nil {
		log.Fatal(err)
	}
}
```

ลองเปิด browser แล้วเข้าไปที่ localhost:8000 จะเจอ `Hello, World` แบบนี้

![Alt Text](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ovb5eqse2dxnhnzd6xrt.png)

นี่คือโครงสร้างเบื้องต้นที่ package `net/http` ถูกออกแบบเอาไว้เลย แต่ที่เราใช้งานแล้วสะดวกกว่านี้เพราะตัว package เองนั้นได้เตรียม Handler ที่ใช้ง่ายกว่านี้เอาไว้ให้แล้วนั่นเองเช่น ที่เราใช้กันบ่อยๆโดยอาจจะไม่รู้ตัวนั่นคือ `HandlerFunc`, `ServeMux` เพราะ function อย่าง `http.HandleFunc` กับ `http.Handle` มันจัดการให้เราเองไปแล้ว โดยทุกครั้งที่เราใช้สองฟังก์ชันนี้มันจะใช้ `DefaultServeMux` นั่นเอง

ทีนี้มาดูว่าถ้าเราจะไม่ใช้ default แล้วสร้าง `ServeMux` เองจะเป็นยังไง ตัวอย่างโค้ดที่ใช้ `ServeMux` เองเป็นแบบนี้

```go
package main

import (
	"log"
	"net/http"
)

func main() {
	mux := http.NewServeMux()
	mux.HandleFunc("/hello", func(w http.ResponseWriter, r *http.Request) {
		w.Write([]byte("Hello, World\n"))
	})
	server := http.Server{
		Addr:    ":8000",
		Handler: mux,
	}
	err := server.ListenAndServe()
	if err != nil {
		log.Fatal(err)
	}
}
```

ลองดูโค้ด method ของ `HandleFunc` ของ `ServeMux` หน้าตาเป็นแบบนี้

```go
// HandleFunc registers the handler function for the given pattern.
func (mux *ServeMux) HandleFunc(pattern string, handler func(ResponseWriter, *Request)) {
	if handler == nil {
		panic("http: nil handler")
	}
	mux.Handle(pattern, HandlerFunc(handler))
}
```

นั่นคือเอาฟังก์ชันที่เราส่งไปแปลงเป็น type HandlerFunc ที่ก็ยัง implements interface Handler เอาไว้นั่นเอง แล้วส่งต่อไปเรีย `mux.Handle` ซึ่งจะเก็บข้อมูลว่า pattern ไหนจะเรียก handler ตัวไหนเอาไว้ เมื่อมี request เข้ามาก็จะเรียก method ServeHTTP ของ `ServeMux` เพื่อแกะ request path แล้วเรียก handler ที่ register ไว้อีกที

ส่วน type HandlerFunc นั้นหน้าตาเป็นแบบนี้ และ implements Handler เอาไว้ง่ายๆเลยแบบนี้

```go
// The HandlerFunc type is an adapter to allow the use of
// ordinary functions as HTTP handlers. If f is a function
// with the appropriate signature, HandlerFunc(f) is a
// Handler that calls f.
type HandlerFunc func(ResponseWriter, *Request)

// ServeHTTP calls f(w, r).
func (f HandlerFunc) ServeHTTP(w ResponseWriter, r *Request) {
	f(w, r)
}
```

นั่นคือตัวมันเองเป็น function ดังนั้นเมื่อ implement ServeHTTP ก็เอา receiver variable ที่เป็นฟังก์ชันนั่นเองเรียกต่อไปได้เลย

## ขอฝาก Buy Me a Coffee
สำหรับท่านใดที่อ่านแล้วชอบโพสต์ต่างๆของผมที่นี่ ต้องการสนับสนุนค่ากาแฟเล็กๆน้อยๆ สามารถสนับสนุนผมได้ผ่านทาง Buy Me a Coffee คลิ๊กที่รูปด้านล่างนี้ได้เลยครับ

<a href="https://www.buymeacoffee.com/weerasak" target="_blank"><img src="https://cdn.buymeacoffee.com/buttons/v2/default-yellow.png" alt="Buy Me A Coffee" style="height: 60px !important;width: 217px !important;" ></a>

ส่วนท่านใดไม่สะดวกใช้บัตรเครดิต หรือ Paypal สามารถสนับสนุนผมได้ผ่านทาง PromptPay โดยดู QR Code ได้จากโพสต์ที่พินเอาไว้ได้ที่ Page DevDose ครับ https://web.facebook.com/devdoseth

ขอบคุณครับ 🙏