Saturday, April 1, 2017

Golang: A Fast HTTP Mux with http.HandlerFunc

Objective

1. First we wanted a fast HTTP mux with http.HandlerFunc support
2. It should have good benchmarks

The search

Exploring at github.com/julienschmidt/go-http-routing-benchmark for a list of fast routers, we found one very interesting fast router => the Gin router.


Exploring Gin (github.com/gin-gonic/gin)

Gin have:
- very good benchmarks
- good middleware design
- and above all, it has impressive sets of unit tests

What to change in Gin

The only thing we don't like from Gin is its handlers.

     // This handler will match /user/john but will not match neither /user/ or /user  
      router.GET("/user/:name", func(c *gin.Context) {  
           name := c.Param("name")  
           c.String(http.StatusOK, "Hello %s", name)  
      })  

This is completely understandable because the "context" package was not part of Golang's "http" package prior to go1.7.


Replacing Gin's handlers

Request.WithContext(...) is slow.

Request.WithContext is doing a shallow copy of "request", which in my benchmarks costs about 300+ ns/op.
(ref: https://golang.org/pkg/net/http/#Request.WithContext)

Context alternative

So we try to find other means under the Request type definition:

 type Request struct {  
     Method string  
     URL *url.URL  
     Proto   string   
     ProtoMajor int  
     ProtoMinor int  
     Header Header  
     Body io.ReadCloser  
     GetBody func() (io.ReadCloser, error)  
     ContentLength int64  
     TransferEncoding []string  
     Close bool  
     Host string  
     Form url.Values  
     PostForm url.Values  
     MultipartForm *multipart.Form  
     Trailer Header  
     RemoteAddr string  
     RequestURI string  
     TLS *tls.ConnectionState  
     Cancel <-chan struct{}  
     Response *Response  
 }  


Instinctively, Form and PostForm are quick choices. But you will end up running ParseForm() to initialize them.

Other interesting options are Body and GetBody.

But Body is used widely within the http package,  and we don't want to break it.

The final choice is "GetBody". We can hide our Gin Context inside of GetBody without breakage (hopefully). And benchmarks gives us about 150+ ns/op or  20+ ns/op.

Here are the changes done:
https://github.com/noypi/router/commit/82586b8967933e131ecfa7d1a2eeaadb2ade0e78


Thanks,
S