http
Package net/http provides HTTP client and server implementations.
Usage
The server just needs to register http handlers in a map structure (which is inside a ServeMux structure) and then listen and serve requests on incoming connections.
// user code
// register handlers and start the service
http.HandleFunc("/hello", helloHandler)
log.Fatal(http.ListenAndServe(":8080", nil))
// the underlying implementation
// src code in pkg net/http
func HandleFunc(pattern string, handler func(ResponseWriter, *Request)) {
...
e := muxEntry{h: HandlerFunc(handler), pattern: pattern}
mux.m[pattern] = e
...
}
// DefaultServeMux is used if handler is nil
func ListenAndServe(addr string, handler Handler) error {
...
for {
...
serverHandler{c.server}.ServeHTTP(w, w.req)
...
}
}
func (mux *ServeMux) ServeHTTP(w ResponseWriter, r *Request) {
...
h, _ := mux.m[path]
h.ServeHTTP(w, r)
}
type HandlerFunc func(ResponseWriter, *Request)
func (f HandlerFunc) ServeHTTP(w ResponseWriter, r *Request) {
f(w, r)
}
TCP Connection
The function ListenAndServe hides details of the underlying
tcp interaction (pls refer to socket example):
[1] listen to the service port
[2] accept a new request and create a new conn object (which
contains local and remote endpoints, i.e., sockets)
[3] open a new goroutine to handle this conn, and then go to [2]
Multiplexer
The Server structure contains a Handler to handle a connection.
// src code
type Server struct {
Addr string
Handler Handler // handler to invoke
// other elements ...
}
type Handler interface {
ServeHTTP(ResponseWriter, *Request)
}
We can just write a function to implement the Handler interface.
- abstract uri in the request line, e.g.,
GET /admin/users HTTP/1.1 - run a code block of
if-elseconditions - trigger corresponding actions
However, the if-else block would be complicated and
difficult to maintain. A more appropriate way is to add a
multiplexer, working as a middleware, which matches the
URL of each incoming request against a list of registered patterns
and calls the corresponding handler.
The type ServeMux does this job.
- registration stage: save handlers for each pattern via
ServeMux.HandleFunc() - dispatching stage: match uri in request line and trigger its handler
// data structure
type ServeMux struct {
mu sync.RWMutex
m map[string]muxEntry // store handlers for each pattern
}
// registtration stage
// HandleFunc() --> DefaultServeMux.HandleFunc() --> DefaultServeMux.Handle()
http.HandleFunc("/hello", helloHandler)
// underlyig mechnism
func (mux *ServeMux) Handle(pattern string, handler Handler) {
...
e := muxEntry{h: handler, pattern: pattern}
mux.m[pattern] = e
...
}
// dispatching stage
func (mux *ServeMux) ServeHTTP(w ResponseWriter, r *Request) {
...
h, _ := mux.m[path]
h.ServeHTTP(w, r)
}
When a Server is created,
a ServeMux, with registered handlers, is assigned to its
Handler member. Once a conn is established,
ServeMux.ServeHTTP() will be called to dispatch the request.
If the user does not specify a Handler, a default ServeMux is used.
MiddleWare
The multiplexer ServeMux is also a middleware in the architecture. The midddleware structure provides more space to manipulate:
- chain another middleware, e.g., log request info in the Handler
- replace the default multiplexer ServeMux with another one, e.g., httprouter
replace a middleware
HttpRouter is a lightweight high performance HTTP request router (also called multiplexer or just mux for short).
In contrast to the default mux of Go’s net/http package, this router supports variables in the routing pattern and matches against the request method. It also scales better. We can replace default mux with this one:
package main
import (
"fmt"
"log"
"net/http"
"github.com/julienschmidt/httprouter"
)
func Hello(w http.ResponseWriter, r *http.Request) {
fmt.Fprintf(w, "hello, you request for %s!\n", r.Host)
}
func Hi(w http.ResponseWriter, r *http.Request, ps httprouter.Params) {
fmt.Fprintf(w, "hi, %s!\n", ps.ByName("name"))
}
func main() {
// mux from pkg net/http
serveMux := http.NewServeMux()
serveMux.HandleFunc("/hello", Hello)
// response: hello, you request for localhost:8081!
go func() {
log.Fatal(http.ListenAndServe(":8081", serveMux))
}()
// replace it with another one
router := httprouter.New()
router.HandlerFunc(http.MethodGet, "/hello", Hello)
// response: hello, you request for localhost:8080!
router.GET("/hi/:name", Hi)
// request: http://localhost:8080/hi/Jack
// response: hi, Jack!
log.Fatal(http.ListenAndServe(":8080", router))
}