DEV Community

Mike Nithaworn
Mike Nithaworn

Posted on

Golang net.http handling request/responses

Introduction

In this post, I want to go over what I've learnt in studying how to create a web server that listens on a TCP network that handles client-server request/responses. Here I will discuss Golang's Handler from the net.http package and how it is used to achieve this.

What is an Interface?

An interface is a type that defines functionality

What is a Handler?

In order to process requests and responses on a network we need a Handler.

A Handler is an interface and anything that implements ServeHTTP is implicity a Handler as well.

Take a look at the following from the http.Handler interface:


type Handler interface {
    ServeHTTP(ResponseWriter, *Request)
}

Here we have a function ServeHTTP that takes in a ResponseWriter and a pointer to a Request.

  • Request is a pointer to a struct that contains all the data needed to process the request.
  • ResponseWriter allows us to set headers and send response text back to the client.

Bottom line: A Handler is type that has a ServeHTTP function that takes in a ResponseWriter and a pointer to a Request.

As mentioned before, anything that implements ServeHTTP would be a Handler as well. This allows for polymorphism where different types can be passed in anywhere a Handler is required. In particular, the http.ListenAndServe function that listens and serves incoming requests is an example of this.

How to Use a Handler?

The ListenAndServe function requires an address string and a Handler:


http.ListenAndServe

func ListenAndServe(addr string, handler Handler) error
To paraphrase the Golang documentation, the ListenAndServe function listens on the TCP network address addr and makes a call to Serve with handler to handle requests on incoming connections. Generally the handler is nil, and the DefaultServeMux is used:

http.ListenAndServe(":8080", nil)

The following tells the ListenAndServe function to listen on port 8080 using the DefaultServeMux.

A mux in laymen's terms is a data multiplexer that provides routing for incoming and outgoing traffic. It is responsible for deciding where the data should go and what to do with it. The mux is essentially responsible for URL routing and processing GET and POST requests. More to be talked about in a future post.

Although not a common practice, you can also pass in a custom handler as long as the type implements a Handler. But most of the times, the DefaultServeMux is usually sufficient.

How to Process a Request?

The second argument of a Handler is a Request. A Request is a struct with many fields such as Method, URL, Form, PostForm, and etc. It is here where we can extract request data and determine which functions to call to process the request.

When working with a POST request, Request has fields that allow for accessing form data:

  • Form gets data from both the URL query string and form data
  • PostForm gets data from form data only
  • FormValue is a helper function to get the value from a given name

The Form and PostForm fields are only available after ParseFrom is called.

http.ParseForm is a function that has a pointer reference to a Request

Example on how to call ParseForm:


func (m myHandler) ServeHTTP(res http.ResponseWriter, req *http.Request) {
  err := req.ParseForm()
  if err != nil {
    log.Println(err)
  }
}

After calling ParseForm, accessing the Form or PostForm will return url.Values. url.Values is a map that takes a string and returns a slice of strings. The returned value will be a series of key-value pairs from the form data being sent.

How to Send Back a Response?

The ResponseWriter will be used to send responses back to the client. A
ResponseWriter is an interface that has a Header. Use the Header to set values for the response such as Content-Type and etc.

res.Header().Set("Content-Type", "text/html; charset=utf-8")

From there, you can then finish off processing the response by passing in the ResponseWriter to another function that will write and send back to the client. If templates are being used, the code can look like this:

tpl.ExecuteTemplate(res, "mypage.gohtml", data)

The following are other types of responses that can be done:

HTML


res.Header().Set("Content-Type", "text/html; charset=utf-8")
fmt.Fprintln(res, "Put html code in here")

Text


res.Header().Set("Content-Type", "text/plain; charset=utf-8")
fmt.Fprintln(res, "This is some text")

JSON


res.Header().Set("Content-Type", "text/json; charset=utf-8")
fmt.Fprintln(res, "{\"item\": \"value\"}")

Additionally you may need to use json.Marshal(data) from the json package when dealing with large data sets rather than hard-coding like the above example.

This post is still in the works and will continually get updated. Hit me up with any comments and feedback. Peace.

Top comments (0)