GoWeb – Session and Cookie

Introduction to Session and Cookie

The HTTP protocol is a stateless protocol, that is, every time the server receives a request from the client, it is a brand new request, and the server does not know the historical request records of the client. The main purpose of session and cookie is to make up for the stateless nature of HTTP.

what is session

When the client requests the server, the server will open up a memory space for this request. This object is the session object, and the storage structure is ConcurrentHashMap.

The session makes up for the stateless nature of HTTP, and the server can use the session to store some operation records of the client during the same session.

How does session determine whether it is the same session

When the server receives the request for the first time, it will open up a session space (create a session object), generate a sessionld at the same time, and send the request setting to the client through the “Set-Cookie: JSESSIONID=XXXXXXX” command in the response header cookie response.

After receiving the response, the client sets a “JSESSIONID=XXXXXXX” cookie information on the local client, and the expiration time of the cookie is the end of the browser session.

Next, every time the client sends a request to the same server, the cooke information (including sessionld) will be included in the request header. The server side reads the cookie information in the request header, obtains the value named JSESSIONID, and obtains the sessionId of this request.

Disadvantages of sessions

The session mechanism has a disadvantage: if the A server stores the session (i.e. load balancing), if A’s visits surge within a period of time, the visits will be forwarded to the B server, but the B server does not store the A server’s session, This causes the session to fail.

what is a cookie

A cookie in the HTTP protocol is a small piece of data sent from the server to the client’s web browser, including web cookies and browser cookies. The cookie sent by the server to the client browser will be stored by the browser and sent to the server together with the next request. Usually, it is used to judge whether two requests come from the same client browser, for example, if the user remains logged in
recording status.
Cookies are mainly used in the following three aspects:

  1. Session management: Session management is often required in logins, shopping carts, game scores, or servers to remember their content.
  2. Personalization: Personalization refers to user preferences, themes, or other settings.
  3. Tracking: Record and analyze user behavior.

Cookes used to be used as general client-side storage, which was legal back then because they were the only way to store data on the client. But nowadays it is recommended to use modern storage API. Cookies are sent with every request, so they can slow down performance (especially on mobile data connections).

The difference between session and cookie.

First of all, no matter what settings the client browser makes, the session should work normally. The client can choose to disable cookies, but the session can still work because the client cannot disable the server-side session.

Secondly, in terms of the amount of data stored, session and cookie are also different. Session can store any type of object, and cookie can only store objects of type String.

Create cookie

When receiving an HTTP request from a client, the server can send a Set-Cookie header with the response. Cookies are usually stored by the browser, and the browser sends the request to the server side by combining the cookie with the HTTP header.

  1. Set-Cookie header and cookie.
    The role of the Set-Cookie HTTP response header is to send cookies from the server side to the user agent.
  2. session cookie
    The session cookie has a characteristic that the cookie will be deleted when the client is closed, because it does not specify the Expires or Max-Age directive. However, web browsers may use session restoration, which makes most session cookies “permanent”, as if the browser was never closed.
  3. Persistent cookies.
    Persistent cookies do not expire when the client is closed, but expire after reaching a specific date (Expires) or a specific length of time (Max-Age). For example “Set-Cookie:id=b8gNc;Expires=Sun,21Dec202007:28:00
    GMT;” means to set a cookie whose id is b8gNc and whose expiration time is December 21, 2020 07:28:00, Greenwich Mean Time.
  4. Secure cookies.
    Secure cookies require the HTTPS protocol to be sent to the server in an encrypted manner. Sensitive information should not be stored in Cooke even if it is safe, as they are inherently insecure and this flag provides no real protection.

Scope of cookies

The Domain and Path identifiers define the scope of the cookie, that is, which URLs the cookie should be sent to. Domain identifies which hosts can accept cookies. If Domain is not specified, it defaults to the current host (excluding subdomains); if Domain is specified, subdomains are generally included. For example, if Domain=baidu.com is set, the cookie is also included in the subdomain (such as news.baidu.coml).

For example, if Path=test is set, the following addresses will match:

  • /test
  • /test/news/
  • /test/news/id

Go and cookies

A structure called Cookie is defined in the net/http package of the Go standard library. The Cookie structure represents a Set-Cookie value that appears in the HTTP response header, or the cookie value in the HTTP request header.

The cookie structure is defined as follows:

type Cookie struct {<!-- -->
name string
Value string
Path string
Domain string
Expires time. Time
Raw Expires string

//MaxAge=0 indicates that the Max-Age attribute is not set
//MaxAge<0 flag deletes the cookie immediately, which is equivalent to Max-Age:0
//MaxAge>0 indicates that there is a Max-Age attribute, and the unit is s
MaxAge int
Secure bool
HttpOnly bool
Raw string
Unparsed []string //The original text of the unparsed "attribute-value" pair
}

Set cookies

The SetCookie() function is provided in the net/http package of the Go language to set cookies.
The definition of the SetCookie() function is as follows:

func SetCookie(w ResponseWriter,cookie *Cookie)

An example of using the SetCookie() function is as follows.

func main() {<!-- -->
http.HandleFunc("/", testHandle)
http.ListenAndServe(":8080", nil)
}

func testHandle(w http.ResponseWriter, r *http.Request) {<!-- -->
c, err := r. Cookie("test_cookie")
fmt.Printf("cookie:%#v, err:%v\
", c, err)

cookie := &http.Cookie{<!-- -->
Name: "test_cookie",
Value: "DKLJFDSKIOfddFDSFIDSP",
MaxAge: 3600,
Domain: "localhost",
Path: "/",
}

http. SetCookie(w, cookie)
//The cookie should be set before the specific data is returned, otherwise the cookie setting is unsuccessful
w.Write([]byte("hello"))
}

Get cookie

The Request object in the Go language net/http package has a total of 3 methods for handling cookies: 2 methods for obtaining cookies and 1 method for adding cookies. To get cookies, use the Cookies() or Cookie() method.

  1. Cookies() method. The Cookies() method is defined as follows:
func (r *Request)Cookies() []*Cookie

The Cookies() method is used to parse and return all cookies for the request.

  1. Cookie() method. The Cookie() method is defined as follows:
func (r *Request)Cookie(name string)(*Cookie, error)

The Cookie() method is used to return the cookie named name in the request, or “nil, ErrNoCookie” if the cookie is not found.

  1. AddCookie() method. The AddCookie() method is used to add a cookie to the request.
    The AddCookie() method is defined as follows:
func (r *Request)AddCookie(c *Cookie)

The usage examples of Cookie() method and AddCookie() method are as follows.

func main() {<!-- -->
CopeHandle("GET", "http://www.baidu.com", "")
}

func CopeHandle(method, urlVal, data string) {<!-- -->
client := & http. Client{<!-- -->}
var req *http.Request

if data == "" {<!-- -->
urlArr := strings. Split(urlVal, "?")
if len(urlArr) == 2 {<!-- -->
urlVal = urlArr[0] + "?" + getParseParam(urlArr[1])
}
req, _ = http. NewRequest(method, urlVal, nil)
} else {<!-- -->
req, _ = http. NewRequest(method, urlVal, strings. NewReader(data))
}

cookie := &http.Cookie{<!-- -->
Name: "X-Xsrftoken",
Value: "adfsf233lkj29432mdvmsdfdfdsfd",
HttpOnly: true,
}
req. AddCookie(cookie)
req.Header.Add("X-Xsrftoken", "werierpoweip2342tuiou543543")

resp, err := client. Do(req)

if err != nil {<!-- -->
fmt.Println(err)
}
defer resp.Body.Close()
b, _ := ioutil. ReadAll(resp. Body)
fmt. Println(string(b))

}

//Escape the parameters of the GEt request
func getParseParam(param string) string {<!-- -->
return url.PathEscape(param)
}

Go uses session

Go’s standard library does not provide a method for implementing sessions, but many web frameworks do. The following uses a specific example of the Go language to simply implement the function of a session.

1. Define an interface named Session

The Session structure has only 4 operations: set value, get value, delete value and get the current sessionld, so the Session interface should have 4 methods to perform this operation:

//session interface
type Session interface {<!-- -->
Set(key, value interface{<!-- -->}) error //Set session
Get(key interface{<!-- -->}) interface{<!-- -->} //Get session
Delete(key interface{<!-- -->}) error //Delete session
SessionID() string //return sessionId
}

2. Create a session manager

Since the session is stored in the server-side data, a Provider interface can be abstracted to represent the underlying structure of the session manager. The Provider interface will access and manage sessions through sessionld:

//session manager underlying structure
type Provider interface {<!-- -->
//session initialization
SessionInit(sessionId string) (Session, error)
//Return the session object corresponding to sessionId
SessionRead(sessionId string) (Session, error)
//Delete the session corresponding to the given sessionId
SessionDestroy(sessionId string) error
//Delete expired sessions according to maxLifeTime
GarbageCollector(maxLifeTime int64)
}

After defining the Provider interface, we write another registration method so that we can find the corresponding provider manager according to the name of the provider manager:

var providers = make(map[string]Provider)

// Register a session provider manager that can be obtained by name
func RegisterProvider(name string, provider Provider) {<!-- -->
if provider == nil {<!-- -->
panic("session: Register provider is nil")
}
if _, p := providers[name]; p {<!-- -->
panic("session: Register provider is existed")
}
providers[name] = provider
}

Then encapsulate the provider manager and define a global session manager:

//Global session manager
type SessionManager struct {<!-- -->
cookieName string //The name of the cookie
lock sync.Mutex //lock to ensure data consistency during concurrency
provider Provider //Manage session
maxLifeTime int64 //timeout
}

func NewSessionManager(providerName, cookieName string, maxLifetime int64) (*SessionManager, error) {<!-- -->
provider, ok := providers[providerName]
if !ok {<!-- -->
return nil, fmt.Errorf("session: unknown provide %q (forgotten import?)", providerName)
}
\t
//Return a SessionManager object
return &SessionManager{<!-- -->
cookieName: cookieName,
maxLifeTime: maxLifetime,
provider: provider,
}, nil
}

Then create a global session manager in the main package:

var globalSession *SessionManager

func init() {<!-- -->
globalSession, _ = NewSessionManager("memory", "sessionId", 3600)
}

3. Create a method GetSessionId() to get sessionId

sessionld is used to identify each user accessing the web application, so it needs to be guaranteed to be globally unique. The sample code is as follows:

func (manager *SessionManager) GetSessionId() string {<!-- -->
b := make([]byte, 32)
if _, err := io.ReadFull(rand.Reader, b); err != nil {<!-- -->
return ""
}
return base64.URLEncoding.EncodeToString(b)
}

4. Create SessionBegin() method to create session

It is necessary to assign or obtain the session associated with each visiting user, so that the verification operation can be performed based on the session information later. The SessionBegin() function is used to detect whether a session has already been associated with the current visiting user, and if not, create it.

//According to the cookie of the current request to determine whether there is a valid session, if not, create it
func (manager *SessionManager) SessionBegin(w http.ResponseWriter, r *http.Request) (session Session) {<!-- -->
manager. lock. Lock()
defer manager. lock. Unlock()
cookie, err := r.Cookie(manager.cookieName)
if err != nil || cookie.Value == "" {<!-- -->
sessionId := manager. GetSessionId()
session, _ = manager.provider.SessionInit(sessionId)
cookie := http. Cookie{<!-- -->
Name: manager.cookieName,
Value: url.QueryEscape(sessionId),
Path: "/",
HttpOnly: true,
MaxAge: int(manager. maxLifeTime),
}
http. SetCookie(w, &cookie)
} else {<!-- -->
sessionId, _ := url.QueryUnescape(cookie.Value)
session, _ = manager.provider.SessionRead(sessionId)
}
return session
}

Now it is possible to return a variable that satisfies the Session interface through the SessionBegin() method.
The following is an example to show the read and write operations of the session:

//Judge whether there is a session of the user according to the user name, if not, create it
func login(w http.ResponseWriter, r *http.Request) {<!-- -->
session := globalSession.SessionBegin(w, r);
r.ParseForm();
name := session. Get("username")
if name != nil {<!-- -->
//Set the username value submitted by the form to the session
session. Set("username", r. Form["username"])
}
}

5. Create SessionDestroy() method to log out session

In Wb applications, there is usually an operation for the user to log out. When the user exits the application, we can log out the user’s session data. Let’s create a method called SessionDestroy0 to log out the session:

//Create SessionDestroy() method to log out session
func (manager *SessionManager) SessiionDestroy(w http.ResponseWriter, r *http.Request) {<!-- -->
cookie, err := r.Cookie(manager.cookieName)
if err != nil || cookie.Value == "" {<!-- -->
return
}
manager. lock. Lock()
defer manager. lock. Unlock()
\t
manager.provider.SessionDestroy(cookie.Value)
expiredTime := time. Now()
newCookie := http. Cookie{<!-- -->
Name: manager.cookieName,
Path: "/",
HttpOnly: true,
Expires: expiredTime,
MaxAge: -1,
}
http.SetCookie(w, & newCookie)
}

6. Create GarbageCollector() method to delete session

Next, let’s see how to let the session manager delete the session. The sample code is as follows:

func (manager *SessionManager) GarbageCollector() {<!-- -->
manager. lock. Lock()
defer manager. lock. Unlock()
manager.provider.GarbageCollector(manager.maxLifeTime)
\t
time.AfterFunc(time.Duration(manager.maxLifeTime), func() {<!-- -->
manager. GarbageCollector()
})
}