05/02/2020 http client server middleware
All examples from the article are located in the github repository.
Let's take a look to HTTP tools in GO.
Among other standard GO packages there is net/http
package,
implemented both HTTP-client and -server functionality.
The simplies way to start HTTP-server — to call http.ListenAndServe
:
package main import ( "log" "net/http" ) type Handler struct {} func (h *Handler) ServeHTTP(w http.ResponseWriter, req *http.Request){ _,err := w.Write([]byte("hello")) if err != nil { log.Println(err) } } func main() { log.Println(http.ListenAndServe("127.0.0.1:9090", &Handler{})) }
We just ran server on port 9090.
The second parameter of http.ListenAndServe
is http.Handle
interface implementer.
The interface requires of ServeHTTP(w http.ResponseWriter, req *http.Request)
function.
This function is going to process all requests on our server:
curl localhost:9090/someurl hello curl localhost:9090/secondurl hello
Also we can configure the server to process the specific URL, for example — /hello
:
package main import ( "log" "net/http" ) func main() { http.HandleFunc("/hello", func(w http.ResponseWriter, req *http.Request){ _,err := w.Write([]byte("hello")) if err != nil { log.Println(err) } }) log.Println(http.ListenAndServe(":9090", nil)) }
How does it work?
When we call http.HandleFunc
, the path and handler function
is added to net/http.DefaultServeMux
struct,
that is holding all of them.
And when we call particular URL, the net/http.DefaultServeMux
is used
to find particular handler. If no handler found — standard 404 response is generated.
Standard router implements the only plain URL comparison to each
of registered URLs.
Pattern matching, such as matching to expression like /hello/{page}
,
where page parameter can be different between several calls, is impossible with standard router.
To implement that we need either to implement pattern matching by ourselves or
to use one of router libraries.
We could use any other router, for example gorilla/mux. It supports pattern matching.
func main() { r := mux.NewRouter() r.HandleFunc("/products/{key}", ProductHandler) r.HandleFunc("/articles/{category}/", ArticlesCategoryHandler) r.HandleFunc("/articles/{category}/{id:[0-9]+}", ArticleHandler) http.Handle("/", r) }
Next, in handler we can grab the variables from the URL:
func ArticlesCategoryHandler(w http.ResponseWriter, r *http.Request) { vars := mux.Vars(r) w.WriteHeader(http.StatusOK) fmt.Fprintf(w, "Category: %v\n", vars["category"]) }
In standard web service there is not only handler's code that is running on every request. Also there is some common code that performs some general work — logging, statistics grabbing, authorization checking, etc. This common code is called middleware.
There no explicit implementation of middlewares in GO standard library. But anyone can implement something like that by using nested handlers. Let's take a look to a logging middleware, that prints every requested URL to a console:
a lot of codepackage main import ( "log" "net/http" ) type LoggingMiddleware struct { handler http.Handler } func NewLoggingMiddleware(handler http.Handler) *LoggingMiddleware { return &LoggingMiddleware{ handler: handler, } } func (l *LoggingMiddleware) ServeHTTP(w http.ResponseWriter, req *http.Request) { log.Println(req.URL, "requested") l.handler.ServeHTTP(w, req) } type Handler struct {} func (h *Handler) ServeHTTP(w http.ResponseWriter, req *http.Request){ _,err := w.Write([]byte("hello")) if err != nil { log.Println(err) } } func main() { log.Println(http.ListenAndServe(":9090", NewLoggingMiddleware(&Handler{}))) }
On every request to our server we now can see following data in the console:
2020/05/01 18:09:42 / requested 2020/05/01 18:09:58 /someurl requested
Libraries like gorilla/mux
или
echo
, gin
can singnifically help with middleware implementation.
Also, the most popular tasks for middleware are already covered in many libraries.
In net/http
package there is Client
struct,
that is responsible for HTTP-client requests:
client := http.Client{} resp, err := client.Get("https://golangforall.com/en/") if err != nil { log.Fatal(err) } buf, err := ioutil.ReadAll(resp.Body) if err != nil { log.Fatal(err) } fmt.Println(string(buf)) // some html is printed
Standard HTTP-client in GO looks simple, but it covers all possible tasks.
Some of the configuration is covered by Transport
field in Client
struct.