This page looks best with JavaScript enabled

Building Web Server with Go - Part 2

 ·  ☕ 8 min read
This is part 2 of 09 part series on building web server with go.
Checkout https://www.gophersumit.com/series/web-server/ for more.

http request

Http request is represented by request struct in Go. This is responsible for holding all the information related to incoming http request while building server.

Request struct is defined in Go Std library


When an http request is received, we can extract request related information.
We can extract multiple details from incoming request like

  1. Body of request
  2. Query string parameters
  3. HTTP Headers
  4. Posted form etc.
    Let’s spin simple web server to extract basic information related to incoming request.

request.go

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
package main

import (
  "net/http"
  "strconv"
)

func requestInspection(w http.ResponseWriter, r *http.Request) {
  w.Write([]byte(string("Method: " + r.Method + "\n")))
  w.Write([]byte(string("Protocol Version: " + r.Proto + "\n")))
  w.Write([]byte(string("Host: " + r.Host + "\n")))
  w.Write([]byte(string("Referer: " + r.Referer() + "\n")))
  w.Write([]byte(string("User Agent: " + r.UserAgent() + "\n")))
  w.Write([]byte(string("Remote Addr: " + r.RemoteAddr + "\n")))
  w.Write([]byte(string("Requested URL: " + r.RequestURI + "\n")))
  w.Write([]byte(string("Content Length: " + strconv.FormatInt(r.ContentLength, 10) + "\n")))
  for key, value := range r.URL.Query() {
      w.Write([]byte(string("Query string: key=" + key + " value=" + value[0] + "\n")))
  }

}

func main() {
  mux := http.NewServeMux()
  mux.HandleFunc("/", requestInspection)

  http.ListenAndServe(":3000", mux)
}

response

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
$>curl http://localhost:3000/home?name=sumit
Method: GET
Protocol Version: HTTP/1.1
Host: localhost:3000
Referer: 
User Agent: curl/7.65.3
Remote Addr: 127.0.0.1:45496
Requested URL: /home?name=sumit
Content Length: 0
Query string: key=name value=sumit

http response

Http response is similar to http request, except is represents response from an HTTP request. Response struct is defined in Go Std Library

We do not directly work with Response struct, instead we can build the http response using ResponseWriter interface.
ResponseWriter interface is defined as

1
2
3
4
5
6
// source : https://golang.org/src/net/http/server.go?s=2985:5848#L84
type ResponseWriter interface {
	Header() Header
	Write([]byte) (int, error)
	WriteHeader(statusCode int)
}

We have already used Write method to write response body. Let’s spin a web server to write http header.

When returning status code other than 200 we must call w.WriteHeader() before any call to w.Write().

response.go

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
package main

import (
  "net/http"
)
func unauthorized(w http.ResponseWriter, r *http.Request) {
  w.WriteHeader(http.StatusUnauthorized)
  w.Write([]byte("you do not have permission to access this resource.\n"))
}
func main() {
  mux := http.NewServeMux()
  mux.HandleFunc("/", unauthorized)

  http.ListenAndServe(":3000", mux)
}

response

1
2
3
4
5
6
7
8
$curl http://localhost:3000/home?name=sumit
you do not have permission to access this resource.

$curl -I http://localhost:3000/home
HTTP/1.1 401 Unauthorized
Date: Sun, 02 Feb 2020 05:05:03 GMT
Content-Length: 52
Content-Type: text/plain; charset=utf-8

http headers

To work with http headers, Go has Header type, which is defined as

1
2
type Header map[string][]string

If we see the Request and Response structs, both have header available as part of struct type.
Let’s spin a simple web server to see headers coming from incoming http request.

header.go

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
package main

import (
  "fmt"
  "net/http"
)

func headers(w http.ResponseWriter, r *http.Request) {
  hed := r.Header
  fmt.Fprintln(w, hed)
}

func main() {
  mux := http.NewServeMux()
  mux.HandleFunc("/", headers)
  http.ListenAndServe(":3000", mux)
}

curl

1
2
$ curl http://localhost:3000
map[Accept:[*/*] User-Agent:[curl/7.65.3]]

browser

map[Accept:[text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8] Accept-Encoding:[gzip, deflate] Accept-Language:[en-US,en;q=0.5] Cache-Control:[max-age=0] Connection:[keep-alive] Dnt:[1] Upgrade-Insecure-Requests:[1] User-Agent:[Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:72.0) Gecko/20100101 Firefox/72.0]]

getting headers

To extract a single header value from incoming request there are 2 ways

  1. We can use Get method of Header. This returns string value of header.
1
2
3
hed1 := r.Header.Get("Accept")
	fmt.Fprintln(w, hed1)
	// text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8
  1. We can access map value of header directly
1
2
3
hed := r.Header["Accept"]
	fmt.Fprintln(w, hed)
	//[text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8]

Depending on how we need to treat header value, we can choose which way to use.

setting headers

We will also require to set headers while building response for http request. To write to header of response, we will use Set method.
Let’s spin a web server to set allowed HTTP methods supported by our web server in header.

setheader.go

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
package main

import (
  "net/http"
)

func setHeader(w http.ResponseWriter, r *http.Request) {
  w.Header().Set("ALLOWED", "GET, POST")
  w.Write([]byte("set allowed headers\n"))
}

func main() {
  mux := http.NewServeMux()
  mux.HandleFunc("/", setHeader)
  http.ListenAndServe(":3000", mux)
}

curl

1
2
3
4
5
6
7
8
$ curl -i http://localhost:3000/
HTTP/1.1 200 OK
Allowed: GET, POST
Date: Sun, 02 Feb 2020 06:51:03 GMT
Content-Length: 20
Content-Type: text/plain; charset=utf-8

set allowed headers
Unlike name suggests, WriteHeader() on ResponseWriter is not used to set headers in response. net/http package has documented this part:
1
2
3
// WriteHeader sends an HTTP response header with the provided
// status code.
WriteHeader(statusCode int)

use of WriteHeader()

WriteHeader is used to send http status code other than 200. Write calls WriteHeader(http.StatusOK) before writing the data. It is important to call WriteHeader before Write if status code we want to send is other than 200.

correct.go

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
package main

import (
  "net/http"
)

func setHeader(w http.ResponseWriter, r *http.Request) {
  w.WriteHeader(http.StatusBadRequest)
  w.Write([]byte("Bad request!\n"))
}

func main() {
  mux := http.NewServeMux()
  mux.HandleFunc("/", setHeader)
  http.ListenAndServe(":3000", mux)
}
1
2
3
4
5
6
7
8
$ curl -i http://localhost:3000
HTTP/1.1 400 Bad Request
Date: Sun, 02 Feb 2020 09:30:44 GMT
Content-Length: 13
Content-Type: text/plain; charset=utf-8

Bad request!

wrong.go

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
package main

import (
 "net/http"
)

func setHeader(w http.ResponseWriter, r *http.Request) {
w.Write([]byte("Bad request!\n")) // this will set status to 200 OK
w.WriteHeader(http.StatusBadRequest)
}

func main() {
 mux := http.NewServeMux()
 mux.HandleFunc("/", setHeader)
 http.ListenAndServe(":3000", mux)
}

When the order is incorrect, we will get 200 OK.

1
2
3
4
5
6
7
$ curl -i http://localhost:3000
HTTP/1.1 200 OK
Date: Sun, 02 Feb 2020 09:31:34 GMT
Content-Length: 13
Content-Type: text/plain; charset=utf-8

Bad request!

query strings

Fetching query string from incoming request one of most common scenarios.
Request struct has URL field which is itself a struct:

1
2
3
4
type Request struct {
  // other field omitted
  URL *url.URL
}

URL struct has method to fetch Query string:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
type URL struct {
  // fields omitted
  RawQuery   string    // encoded query values, without '?'
}

// Query parses RawQuery and returns the corresponding values.
// It silently discards malformed value pairs.
// To check errors use ParseQuery.
func (u *URL) Query() Values {
	v, _ := ParseQuery(u.RawQuery)
	return v
}

To get query string values, we simply need to call this Query method.
Let’s spin a web server which returns query strings passed in request.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
package main

import (
	"net/http"
)

func showQuery(w http.ResponseWriter, r *http.Request) {
	querystring := r.URL.Query()
	w.Write([]byte("query strings are\n"))
	w.Write([]byte("Name:" + querystring.Get("name") + "\n"))
	w.Write([]byte("Email:" + querystring.Get("email") + "\n"))
}

func main() {
	mux := http.NewServeMux()
	mux.HandleFunc("/", showQuery)
	http.ListenAndServe(":3000", mux)
}
1
2
3
4
5
6
$ curl -X GET 'http://localhost:3000/?name=sumit&email=gophersumit@gmail.com'

query strings are
Name:sumit
Email:gophersumit@gmail.com

This is how we can work with request, response, header and query string in Go.


Sumit
WRITTEN BY
Sumit
Gopher


What's on this Page