This page looks best with JavaScript enabled

Building Web Server with Go - Part 3

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

working with cookies

Since http is stateless, various alternatives are used to maintain data between various requests and one such construct is cookie.

An HTTP cookie (web cookie, browser cookie) is a small piece of data that a server sends to the user’s web browser. The browser may store it and send it back with the next request to the same server. Typically, it’s used to tell if two requests came from the same browser — keeping a user logged-in, for example. It remembers stateful information for the stateless HTTP protocol. - MDN Web docs

net/http package defines cookie struct type as

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
// source : https://github.com/golang/go/blob/go1.13.7/src/net/http/cookie.go#L19
type Cookie struct {
	Name  string
	Value string

	Path       string    // optional
	Domain     string    // optional
	Expires    time.Time // optional
	RawExpires string    // for reading cookies only

	// MaxAge=0 means no 'Max-Age' attribute specified.
	// MaxAge<0 means delete cookie now, equivalently 'Max-Age: 0'
	// MaxAge>0 means Max-Age attribute present and given in seconds
	MaxAge   int
	Secure   bool
	HttpOnly bool
	SameSite SameSite
	Raw      string
	Unparsed []string // Raw text of unparsed attribute-value pairs
}

To set cookie, server needs to send Set-Cookie header. Browser will read this header and create cookie.
Go provides SetCookie function for setting cookie which takes responseWriter and cookie struct.

1
2
3
4
5
6
7
8
9
//source : https://golang.org/src/net/http/cookie.go?s=3954:4002#L150
// SetCookie adds a Set-Cookie header to the provided ResponseWriter's headers.
// The provided cookie must have a valid Name. Invalid cookies may be
// silently dropped.
func SetCookie(w ResponseWriter, cookie *Cookie) {
	if v := cookie.String(); v != "" {
		w.Header().Add("Set-Cookie", v)
	}
}

Let’s spin a simple web server to set cookies

cookie.go

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

import (
  "net/http"
)

func setCookies(w http.ResponseWriter, r *http.Request) {
  cookie := http.Cookie{
      Name:  "cookie-1",
      Value: "hello world",
  }
  http.SetCookie(w, &cookie)
}

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

curl

1
2
3
4
5
$ curl -i http://localhost:3000
HTTP/1.1 200 OK
Set-Cookie: cookie-1="hello world"
Date: Tue, 04 Feb 2020 03:38:00 GMT
Content-Length: 0

browser

If cookie was set in previous calls, browser will send cookie back to server.
Request type has Cookies() method to fetch the cookies coming from incoming http requests.

1
2
3
4
5
// source : https://github.com/golang/go/blob/go1.13.7/src/net/http/request.go#L408
// Cookies parses and returns the HTTP cookies sent with the request.
func (r *Request) Cookies() []*Cookie {
	return readCookies(r.Header, "")
}

In addition we also have Cookie method to get a single cookie by name.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
//source : https://github.com/golang/go/blob/go1.13.7/src/net/http/request.go#L419
// Cookie returns the named cookie provided in the request or
// ErrNoCookie if not found.
// If multiple cookies match the given name, only one cookie will
// be returned.
func (r *Request) Cookie(name string) (*Cookie, error) {
	for _, c := range readCookies(r.Header, name) {
		return c, nil
	}
	return nil, ErrNoCookie
}

Let’s spin a web server to get cookies

cookie.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
package main

import (
  "fmt"
  "net/http"
)

func getCookies(w http.ResponseWriter, r *http.Request) {
  // get all cookies
  cookies := r.Cookies()
  for _, cookie := range cookies {
      fmt.Fprintln(w, cookie)
  }
  // get named cookie
  cookie, err := r.Cookie("cookie-1")
  if err != nil {
      fmt.Fprintln(w, err.Error())
  }
  fmt.Fprintln(w, cookie)
}

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

browser

cookie-1="hello world”
cookie-1="hello world”

working with sessions

Session is yet another technique to persist data fot stateless http. Sessions are used to store state on server-side as opposed to cookies which are used to store state in browser. Since cookies are stored in user browser, they are generally used to enhance user experience.

On other hand, sessions are managed by web server and we have more control and
security with sessions. Sessions are widely to used to sensitive information like if user has logged in to system.

Sessions are generally hard to implement correctly we will most likely use third party session managers.

get and set session data

Let’s spin a simple web server which uses github.com/gorilla/sessions package for managing session.
This uses CookieStore to store session data. We can also plug in other storage like FileSystemStore or databases as well.

code

 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
30
31
32
33
34
35
36
37
package main

import (
  "fmt"
  "net/http"

  "github.com/gorilla/sessions"
)

// for prod use secure key, not hard-coded
var store = sessions.NewCookieStore([]byte("1234"))

func sessionHandler(w http.ResponseWriter, r *http.Request) {

  session, err := store.Get(r, "custom-session")
  if err != nil {
      http.Error(w, err.Error(), http.StatusInternalServerError)
      return
  }
  val := session.Values["hello"]
  if val != nil {
      fmt.Fprintln(w, "retrieving existing session: ")
      fmt.Fprintln(w, val)
      return
  }
  session.Values["hello"] = "world"
  session.Save(r, w)
  fmt.Fprintln(w, "no existing session found, set value for session")

}

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

browser

First Request

Second Request

flash messages

Flash messages are useful to set information to be read after a redirection, like after form submissions. - https://www.gorillatoolkit.org/pkg/sessions

Flash messages are convenience session data that can only be read once. Use case for flash messages could be one time confirmation message.
gorilla toolkit has session.AddFlash() method that can be used to work with flash messages.

Getting flash messages

1
2
session, err := store.Get(r, "session-name")
flashes := session.Flashes()

Setting flash message

1
2
  session.AddFlash(message)
  

This is how we work with cookies and sessions in Go.


Sumit
WRITTEN BY
Sumit
Gopher


What's on this Page