I am studying web development in Golang (Beginner) I came across some code I played around with and I'm not too sure why it works, I looked through the library source code and docs but I only have a vague idea it still isn't clicking. Note the code below:
package main
import (
"fmt"
"net/http"
)
type foo int
func (m foo) ServeHTTP(w http.ResponseWriter, r *http.Request) {
fmt.Fprintln(w, "Some text")
}
func main() {
var bar foo
http.ListenAndServe(":8080", bar)
}
From what I understand adding ServeHTTP(w http.ResponseWriter, r *http.Request) as a function method, invokes the handler interface (if I'm saying that correctly) and now foo is of type handler as well. I also understand that http.ListenAndServe takes an input of type handler so that is where my variable bar comes into play. When I run the code and go to localhost:8080 on my browser I get "Some Text" appearing.
EDIT: Implements the interface is the proper term NOT invoke.
Question:
How does this exactly work? How is that ServeHTTP function being accessed?
I tried looking at the source code of the libraries but couldn't pinpoint exactly how ServeHTTP worked. I found these two pieces of code (not sure if this is applicable) that sort of gave me the idea it was implementing a function but need clarification:
// The HandlerFunc type is an adapter to allow the use of
// ordinary functions as HTTP handlers. If f is a function
// with the appropriate signature, HandlerFunc(f) is a
// Handler that calls f.
type HandlerFunc func(ResponseWriter, *Request)
// ServeHTTP calls f(w, r).
func (f HandlerFunc) ServeHTTP(w ResponseWriter, r *Request) {
f(w, r)
}
I have never seen a type declaration as the one above with HandlerFunc that has a function after the name of the type. I have also seen how methods are declared but not sure what is happening in the code above.
The relevant type in net/http is type Handler interface { ServeHTTP(ResponseWriter, *Request) } an interface type. Any concrete type implementing this interface can be used to serve HTTP request. Your bar is of type foo and foo implements the Handler interface.
Handler is an interface that has a method called ServeHttp, which takes a. value of type ResponseWriter and another of type Request as Parameters.
The Handler interface is an interface with a single method ServeHTTP which takes a http. Response and a http. Request as inputs. type Handler interface { ServeHTTP(ResponseWriter, *Request)
type ResponseWriter interface { Header() Header Write([]byte) (int, error) WriteHeader(statusCode int) } The Golang net/http Handler interface has serveHTTP method that takes the Response Writer interface as input and this allows the Golang HTTP Server to construct HTTP Response.
How does this exactly work? How is that ServeHTTP function being accessed?
To answer this question we need to look how http.ListenAndServe
works:
func ListenAndServe(addr string, handler Handler) error {
server := &Server{Addr: addr, Handler: handler}
return server.ListenAndServe()
}
Here we create a Server with given address and handler and call the ListenAndServer method so let's take a look there:
func (srv *Server) ListenAndServe() error {
addr := srv.Addr
if addr == "" {
addr = ":http"
}
ln, err := net.Listen("tcp", addr)
if err != nil {
return err
}
return srv.Serve(tcpKeepAliveListener{ln.(*net.TCPListener)})
}
This method just starts listening given address and calls the Server method with our freshly created listener so let's follow the trail there:
func (srv *Server) Serve(l net.Listener) error {
defer l.Close()
...
for {
rw, e := l.Accept()
...
c := srv.newConn(rw)
c.setState(c.rwc, StateNew) // before Serve can return
go c.serve(ctx)
}
}
From the Serve method we can see that this is the point where we accept the new connection and start handling it in it's own goroutine.
// Serve a new connection.
func (c *conn) serve(ctx context.Context) {
...
for {
w, err := c.readRequest(ctx)
...
serverHandler{c.server}.ServeHTTP(w, w.req)
...
}
}
Here we finally call the ServeHTTP method but as we can see this is not yet our implementation of that function but something from the standard library so let's take a look what that serverHandler struct contains:
// serverHandler delegates to either the server's Handler or
// DefaultServeMux and also handles "OPTIONS *" requests.
type serverHandler struct {
srv *Server
}
func (sh serverHandler) ServeHTTP(rw ResponseWriter, req *Request) {
handler := sh.srv.Handler
if handler == nil {
handler = DefaultServeMux
}
if req.RequestURI == "*" && req.Method == "OPTIONS" {
handler = globalOptionsHandler{}
}
handler.ServeHTTP(rw, req)
}
So here it is finally. If we didn't provide any Handler the DefaultServeMux will be used but since we provided our foo handler ServeHTTP from foo get's called.
And that's it. All of this can be found from server.go
Go's HTTP server takes in an address to listen on, and a handler. Internally, it creates a TCP listener to accept connections on the given address, and whenever a request comes in, it:
http.Request
http.ResponseWriter
for sending the responseServeHTTP
method, passing in the Request
and ResponseWriter
The handler can be anything that satisfies the Handler
interface, which your foo
type does:
type Handler interface {
ServeHTTP(ResponseWriter, *Request)
}
The standard library also includes some conveniences, like HandlerFunc
(which allows you to pass any func(ResponseWriter, *Request)
and use it as a Handler
) and ServeMux
, which allows you to register many Handler
s and choose which one handles which request based on the incoming request path.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With