DEV Community

Cover image for Code Reading of Golang's HTTP Server
Kenta Takeuchi
Kenta Takeuchi

Posted on • Originally published at bmf-tech.com

Code Reading of Golang's HTTP Server

This article was originally published on bmf-tech.com.

Overview

This article is the 20th entry of the Qiita - Go6 Advent Calendar 2019.

We will perform a code reading of the details of setting up an HTTP server in Golang.

Reference Implementation

Here is the implementation we will be reading through.

package main

import (
    "net/http"
)

func main() {
    mux := http.NewServeMux()
    handler := new(HelloHandler)
    mux.Handle("/", handler)

    s := http.Server{
        Addr:    ":3000",
        Handler: mux,
    }
    s.ListenAndServe()
}

type HelloHandler struct{}

func (h *HelloHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
    w.Write([]byte("Hello World"))
}
Enter fullscreen mode Exit fullscreen mode

We will go through this somewhat verbose code line by line, simplifying it as we read.

ServeHttp(w ResponseWriter, r *Request)

First, let's look at:

type HelloHandler struct{}

func (h *HelloHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
    w.Write([]byte("Hello World"))
}
Enter fullscreen mode Exit fullscreen mode

This part implements the Handler interface.

// url: https://golang.org/src/net/http/server.go?s=61586:61646#L1996
func (mux *ServeMux) ServeHTTP(w ResponseWriter, r *Request) {
    if r.RequestURI == "*" {
        if r.ProtoAtLeast(1, 1) {
            w.Header().Set("Connection", "close")
        }
        w.WriteHeader(StatusBadRequest)
        return
    }
    h, _ := mux.Handler(r)
    h.ServeHTTP(w, r)
}
Enter fullscreen mode Exit fullscreen mode
// url: https://golang.org/src/net/http/server.go?s=61586:61646#L79
type Handler interface {
    ServeHTTP(ResponseWriter, *Request)
}
Enter fullscreen mode Exit fullscreen mode

In the reference implementation, a HelloHandler struct is prepared for ServeHTTP(w ResponseWriter, r *Request), but we can rewrite it more concisely using HandlerFunc.

// url: https://golang.org/src/net/http/server.go?s=61509:61556#L1993
type HandlerFunc func(ResponseWriter, *Request)

func (f HandlerFunc) ServeHTTP(w ResponseWriter, r *Request) {
    f(w, r)
}
Enter fullscreen mode Exit fullscreen mode

Rewriting the reference implementation looks like this:

package main

import (
    "net/http"
)

func main() {
    mux := http.NewServeMux()
    mux.Handle("/", http.HandlerFunc(hello))

    s := http.Server{
        Addr:    ":3000",
        Handler: mux,
    }
    s.ListenAndServe()
}

func hello(w http.ResponseWriter, r *http.Request) {
    w.Write([]byte("Hello World"))
}
Enter fullscreen mode Exit fullscreen mode

We were able to rewrite the part that used ServeHTTP(w ResponseWriter, r *Request).

By the way, the implementation inside mux.Handle looks like this:

// url: https://golang.org/src/net/http/server.go?s=75321:75365#L2390
func (mux *ServeMux) Handle(pattern string, handler Handler) {
    mux.mu.Lock()
    defer mux.mu.Unlock()

    if pattern == "" {
        panic("http: invalid pattern")
    }
    if handler == nil {
        panic("http: nil handler")
    }
    if _, exist := mux.m[pattern]; exist {
        panic("http: multiple registrations for " + pattern)
    }

    if mux.m == nil {
        mux.m = make(map[string]muxEntry)
    }
    e := muxEntry{h: handler, pattern: pattern}
    mux.m[pattern] = e
    if pattern[len(pattern)-1] == '/' {
        mux.es = appendSorted(mux.es, e)
    }

    if pattern[0] != '/' {
        mux.hosts = true
    }
}
Enter fullscreen mode Exit fullscreen mode

ServeMux

Next, let's take a closer look at the shortened part.

    mux := http.NewServeMux()
    mux.Handle("/", http.HandlerFunc(hello))

    s := http.Server{
        Addr:    ":3000",
        Handler: mux,
    }
Enter fullscreen mode Exit fullscreen mode

The part mux.Handle("/", http.HandlerFunc(hello)) can be written more concisely by using HandleFunc, which allows some internal processing.

url: https://golang.org/src/net/http/server.go?s=75575:75646#L2448
func HandleFunc(pattern string, handler func(ResponseWriter, *Request)) {
    DefaultServeMux.HandleFunc(pattern, handler)
}
Enter fullscreen mode Exit fullscreen mode
url: https://golang.org/src/net/http/server.go?s=75575:75646#L2435
func (mux *ServeMux) HandleFunc(pattern string, handler func(ResponseWriter, *Request)) {
    if handler == nil {
        panic("http: nil handler")
    }
    mux.Handle(pattern, HandlerFunc(handler))
}
Enter fullscreen mode Exit fullscreen mode

Taking the above into account, we can rewrite it like this:

package main

import (
    "net/http"
)

func main() {
    http.HandleFunc("/", hello)

    s := http.Server{
        Addr:    ":3000",
        Handler: http.DefaultServeMux,
    }
    s.ListenAndServe()
}

func hello(w http.ResponseWriter, r *http.Request) {
    w.Write([]byte("Hello World"))
}
Enter fullscreen mode Exit fullscreen mode

DefaultServeMux is a variable that internally holds a pointer to the ServeMux struct. HandleFunc is a method that allows registration of URL pattern matches to DefaultServeMux.

url: https://golang.org/src/net/http/server.go?s=75575:75646#L2207
// DefaultServeMux is the default ServeMux used by Serve.
var DefaultServeMux = &defaultServeMux

var defaultServeMux ServeMux
Enter fullscreen mode Exit fullscreen mode
url: https://golang.org/src/net/http/server.go?s=68149:68351#L2182
type ServeMux struct {
    mu    sync.RWMutex
    m     map[string]muxEntry
    es    []muxEntry // slice of entries sorted from longest to shortest.
    hosts bool       // whether any patterns contain hostnames
}
Enter fullscreen mode Exit fullscreen mode

Server

Finally, let's look at this part.

    s := http.Server{
        Addr:    ":3000",
        Handler: http.DefaultServeMux,
    }
    s.ListenAndServe()
Enter fullscreen mode Exit fullscreen mode

The internals of s.ListenAndServe() are as follows:

url: https://golang.org/src/net/http/server.go?s=68149:68351#L3093
func (srv *Server) ListenAndServe() error {
    if srv.shuttingDown() {
        return ErrServerClosed
    }
    addr := srv.Addr
    if addr == "" {
        addr = ":http"
    }
    ln, err := net.Listen("tcp", addr)
    if err != nil {
        return err
    }
    return srv.Serve(ln)
}
Enter fullscreen mode Exit fullscreen mode

When there is no need to provide detailed configuration values to the Server, we can write it more concisely by using ListenAndServe(). For details about the Server configuration values, refer to golang.org - server.go.

url: https://golang.org/src/net/http/server.go?s=68149:68351#L3071
func ListenAndServe(addr string, handler Handler) error {
    server := &Server{Addr: addr, Handler: handler}
    return server.ListenAndServe()
}
Enter fullscreen mode Exit fullscreen mode

When written concisely, it looks like this:

package main

import (
    "net/http"
)

func main() {
    http.HandleFunc("/", hello)

    http.ListenAndServe(":3000", nil)
}

func hello(w http.ResponseWriter, r *http.Request) {
    w.Write([]byte("Hello World"))
}
Enter fullscreen mode Exit fullscreen mode

Using an anonymous function, it looks like this:

package main

import (
    "net/http"
)

func main() {
    http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
        w.Write([]byte("Hello World"))
    })

    http.ListenAndServe(":3000", nil)
}
Enter fullscreen mode Exit fullscreen mode

Thoughts

I was trying to create my own HTTP router package in Golang, and I needed to touch on the internal implementation of net/http, so I did a little research. It seems easy to extend, so I have a positive impression of creating my own.

Postscript

I implemented a URL router.

github.com - bmf-san/goblin

References

Top comments (0)