May 2, 2020 Http Client Server Middleware
All examples from the article are located in the github repository.
Let’s take a look at HTTP tools in GO.
Among other standard GO packages, there is the net/http package, which implements both HTTP client and server functionality.
The simplest way to start an HTTP server is 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 a server on port 9090.
The second parameter of http.ListenAndServe is an http.Handler interface implementer. The interface requires a ServeHTTP(w http.ResponseWriter, req *http.Request) function. This function will process all requests on our server:
curl localhost:9090/someurl
hello
curl localhost:9090/secondurl
hello
We can also configure the server to process a 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 are added to the net/http.DefaultServeMux struct, which holds all of them. When we call a particular URL, net/http.DefaultServeMux is used to find the particular handler. If no handler is found, a standard 404 response is generated.
The standard router implements only plain URL comparison to each of the registered URLs. Pattern matching, such as matching an expression like /hello/{page}, where the page parameter can be different between several calls, is impossible with the standard router. To implement that, we need either to implement pattern matching ourselves or to use one of the 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 the 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 a standard web service, there is not only handler code that runs on every request. There is also some common code that performs general work — logging, statistics gathering, authorization checking, etc. This common code is called middleware.
There is no explicit implementation of middlewares in the GO standard library. But anyone can implement something like that by using nested handlers. Let’s take a look at a logging middleware that prints every requested URL to the console:
package main
import (
"log"
"net/http"
)
func loggingMiddleware(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
log.Printf("%s requested", r.URL.Path)
next.ServeHTTP(w, r)
})
}
func main() {
handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.Write([]byte("hello"))
})
http.Handle("/", loggingMiddleware(handler))
log.Println(http.ListenAndServe(":9090", nil))
}
On every request to our server, we now can see the 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, or gin can significantly help with middleware implementation. Also, the most popular tasks for middleware are already covered in many libraries.
In the net/http package, there is a 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
The standard HTTP client in GO looks simple, but it covers all possible tasks.
Some of the configuration is covered by the Transport field in the Client struct.
An example of a simple TCP chat in Go with logic explanation.
Read More → Tcp Server Chat