Package "net/http"

Package http provides HTTP client and server implementations.

  • There is a good web application tutorial in the official website of Go - Writing Web Applications. Have a look before starting this section.

This is a section about handling requests.

1) Introducing ListenAndServe(..), Handle(..), HandleFunc(..), Handler(Interface) & ServeMux(struct).

In the previous section Installation of Go and IDE, we start a web server using the following,

hello1.go

package main

import (
    "net/http"
)

func main() {
    http.HandleFunc("/", func(w http.ResponseWriter, req *http.Request) {       // HandleFunc from http
        w.Write([]byte("Hello World , Moon !"))
    })
    http.ListenAndServe(":8000", nil)       // handle is nil , which means to use DefaultServeMux
}

You can do it another way with a normal function hello (they like to called it handler.. sometimes it makes a lot of confusions, so be careful of all the terms ! ) ,

hello2.go

package main

import (
    "fmt"
    "net/http"
)

func hello(w http.ResponseWriter, r *http.Request) {
    fmt.Fprintf(w, "Hello %s !", "World")
}

func main() {
    http.HandleFunc("/", hello)             // HandleFunc from http
    http.ListenAndServe(":8000", nil)       // handle is nil , which means it's using DefaultServeMux
}

ListenAndServe starts an HTTP server with a given address (port no. also) and handler. It listens on the TCP network address addr and then calls Serve with handler to handle requests on incoming connections. If you are from a node.js backgroud, you will feel at home with the code style above.

The handler is usually nil, which means to use DefaultServeMux. By default if 2nd parameter is nil, it will use DefaultServeMux automatically.

We can declare the ServeMux explicitly using NewServeMux(),

hello3.go

package main

import (
    "fmt"
    "net/http"
)

// handle func
func hello(w http.ResponseWriter, r *http.Request) {
    fmt.Fprintf(w, "Hello %s !", "World")
}

func main() {
    mux := http.NewServeMux()

    // change the handle function to handler
    helloHandler := http.HandlerFunc(hello)
    mux.Handle("/", helloHandler)               // Handle(..) from mux , you can skip the helloHandler
                                                // by writing mux.HandleFunc("/", hello) , direct
                                                // using HandleFunc from mux instead of http.
    http.ListenAndServe(":8000", mux)
}

What is a ServeMux and DefaultServeMux in the examples above ?

  • ServeMux is an HTTP request multiplexer (a term used in electronics). It matches the URL of each incoming request against a list of registered patterns and calls the handler for the pattern that most closely matches the URL.

  • DefaultServeMux is a not a default implementation of ServeMux, because it is a struct, not an interface. DefaultServeMux is just an instance of ServeMux that is publicly available to the application that imports the net/http library. It is also the instance of ServeMux that is used when no handler is provided to the Server struct.

Let's check the api of ListenAndServe,

func ListenAndServe(addr string, handler Handler) error

There is a Handler in the 2nd parameter of the function, what is a Handler ?

If we check the api again, we will find that Handler is an interface.

type Handler interface {                                // src/net/http/server.go
    ServeHTTP(ResponseWriter, *Request)
}

but we passed a ServeMux as the second parameter in hello3.go, it is not a Handler ! right ?

  • We were able to do this because DefaultServeMux also has a ServeHTTP method which implement Handler interface, so ServeMux is also a Handler !

What is a HandleFunc(..) ?

  • HandleFunc(..) registers another normal function which has a type func(ResponseWriter, *Request) in the 2nd parameter for a given pattern (1st parameter) to be the handle function, anything that satisfy the type func(ResponseWriter, *Request) will do, no matter it is a anonymous function or named function like the hello(..) function in hello2.go, this is why we can create normal function with different name, and then throw it to HandleFunc(..) to register.
  • Most of the times, HandleFunc(..) feel like a converter, it convert normal function which satisfied the signature (ResponseWriter, *Request) into a Handler.

lets check the api, there are two HandleFunc found in server.go,

// src/net/http/server.go

// HandleFunc registers the handler function for the given pattern.
func (mux *ServeMux) HandleFunc(pattern string, handler func(ResponseWriter, *Request)) {
    mux.Handle(pattern, HandlerFunc(handler))
}

// Handle registers the handler for the given pattern
// in the DefaultServeMux.
// The documentation for ServeMux explains how patterns are matched.
func HandleFunc(pattern string, handler func(ResponseWriter, *Request)) {
    DefaultServeMux.HandleFunc(pattern, handler)
}

what are the different for the 2 functions? which one we usually call ?

  • the 1st HandleFunc is a method which serves a ServeMux (which is a struct).
  • the 2nd HandleFunc is a function, and it is serving package http.

HandleFunc in example of hello3.go does exactly the same thing as the previous examples of hello.go & hello2.go , but we use the variable mux to register the handle function ( the 1st HandleFunc(..) above ) instead of directly registering from the net/http package. What’s going on underneath? Well, the reason you can directly register the handle function in the package level is because net/http has a DefaultServeMux inside the package ( the 2nd HandleFunc(..) above ).

Ref :

  1. Package http/net
  2. A Recap of Request Handling in Go
  3. Building a Web Server in Go
  4. Custom Handlers and Avoiding Globals in Go Web Applications

2) Other ways of handling request.

Lets try other ways of handling request.

beauty_and_the_beast.go

package main

import (
    "fmt"
    "net/http"
)

type BeautyHandler struct{}                         // implement of the Handler interface

func (h *BeautyHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
    fmt.Fprintf(w, "Beauty !")
}

type BeastHandler struct{}                          // implement of the Handler interface

func (h *BeastHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
    fmt.Fprintf(w, "Beast !")
}

func main() {

    beauty := BeautyHandler{}
    beast := BeastHandler{}

    server := http.Server{
        Addr: "127.0.0.1:8000",
    }

    http.Handle("/beauty", &beauty)
    http.Handle("/beast", &beast)

    server.ListenAndServe()
}

We are using duck typing here again, everything implement the method ServeHTTP(w http.ResponseWriter, r *http.Request) is a Handler. One new thing here - http.Server, check the api,

// src/net/http/server.go

// A Server defines parameters for running an HTTP server.
// The zero value for Server is a valid configuration.
type Server struct {
    Addr           string        // TCP address to listen on, ":http" if empty
    Handler        Handler       // handler to invoke, http.DefaultServeMux if nil
    ReadTimeout    time.Duration // maximum duration before timing out read of the request
    WriteTimeout   time.Duration // maximum duration before timing out write of the response
    MaxHeaderBytes int           // maximum size of request headers, DefaultMaxHeaderBytes if 0
    TLSConfig      *tls.Config   // optional TLS config, used by ListenAndServeTLS

    // TLSNextProto optionally specifies a function to take over
    // ownership of the provided TLS connection when an NPN
    // 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.
    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 and unexpected behavior from handlers.
    // If nil, logging goes to os.Stderr via the log package's
    // standard logger.
    ErrorLog *log.Logger

    disableKeepAlives int32 // accessed atomically.
}

It is just a Server struct which defines parameters for running an HTTP server. Try http://localhost:8000/beauty & http://localhost:8000/beast to feel the beauty and the beast.

3) Chaining handlers and handler functions.

Regarding the chaining of handlers or handler functions, there is a lot of good references:

Ref:

  1. Section 3.3 of Go Web Programming (Manning, Sau Sheong Chang) - The simplest examples.
  2. The http.HandlerFunc wrapper technique in #golang
  3. Making and Using HTTP Middleware
  4. Middleware - Negroni
  5. Alice – Painless Middleware Chaining for Go

4) Other multiplexers

There is a lot multiplexers out there instead of standard one. But why use another multiplexer ? this is because the default multiplexer has limitation.

For example, ServeMux does not support variables in its pattern matching against the URL, it is difficult to handle url like /user/1 for retrieving and displaying the thread with id 123. To process such URLs, your handler(ServeMux) will need to parse the request path, and extract the relevant sections. Also, you cannot do something like /user/1/userType/4 if you want to retrieve the userType with id 4 from the user with id 1( you can actually, but with a lot of unnecessary parsing complexity ).

l guess the references below is enough for introducing multiplexers, sometimes they like to call them routers.

Ref:

  1. Guide to 3rd Party Routers in Go
  2. Section 3.3 of Go Web Programming (Manning, Sau Sheong Chang)
  3. Gowalker HttpRouter
  4. My favorite Go multiplexer
  5. Gorilla vs Pat vs Routes: A Mux Showdown
  6. Go HTTP request router and web framework benchmark

5) How Go web server achieve high concurrency and performance ?

This is the "most wanted question to ask" for a beginner like me, because l alway want a non-blocking , asynchronous web server.

Answer: Using go routine,

// src/net/http/server.go

// Serve accepts incoming connections on the Listener l, creating a
// new service goroutine for each.  The service goroutines read requests and
// then call srv.Handler to reply to them.
func (srv *Server) Serve(l net.Listener) error {
    defer l.Close()
    var tempDelay time.Duration // how long to sleep on accept failure
    for {
        rw, e := l.Accept()
        if e != nil {
            if ne, ok := e.(net.Error); ok && ne.Temporary() {
                if tempDelay == 0 {
                    tempDelay = 5 * time.Millisecond
                } else {
                    tempDelay *= 2
                }
                if max := 1 * time.Second; tempDelay > max {
                    tempDelay = max
                }
                srv.logf("http: Accept error: %v; retrying in %v", e, tempDelay)
                time.Sleep(tempDelay)
                continue
            }
            return e
        }
        tempDelay = 0
        c, err := srv.newConn(rw)
        if err != nil {
            continue
        }
        c.setState(c.rwc, StateNew)
        go c.serve()                          // The high performance magic is here, using go routine !!!
    }
}

You can alway find a good description of the mechanism in the book of astaxie, so l don't repeat here.

Further Ref:

  1. Painless Web Handlers in Go

results matching ""

    No results matching ""