Session Extension

Session is a special object created by the server to hold user’s state.

Hertz also provides an implementation of Session, which references Gin’s implementation.

Install

Download and install

go get github.com/hertz-contrib/sessions

Import into your code

import "github.com/hertz-contrib/sessions"

Example

package main

import (
	"context"

	"github.com/cloudwego/hertz/pkg/app"
	"github.com/cloudwego/hertz/pkg/app/server"
	"github.com/cloudwego/hertz/pkg/common/utils"
	"github.com/hertz-contrib/sessions"
	"github.com/hertz-contrib/sessions/cookie"
)

func main() {
	h := server.New(server.WithHostPorts(":8000"))
	store := cookie.NewStore([]byte("secret"))
	h.Use(sessions.New("mysession", store))
	h.GET("/incr", func(ctx context.Context, c *app.RequestContext) {
		session := sessions.Default(c)
		var count int
		v := session.Get("count")
		if v != nil {
			count = v.(int)
			count++
		}
		session.Set("count", count)
		_ = session.Save()
		c.JSON(200, utils.H{"count": count})
	})
	h.Spin()
}

Config

Hertz can configure the Session for a series of operations by using middleware. The Session interface defines the main methods to configure the Session operation. The introduction of the interface methods is as follows:

Note: Session Wraps thinly gorilla-session methods.

Method Function Signatures Description
ID ID() string Used to fetch the Session ID generated by stores, it should not be used for user data.
Get Get(key interface{}) interface{} Used to get the session value associated to the given key.
Set Set(key, val interface{}) Used to set the session value associated to the given key.
Delete Delete(key interface{}) Used to remove the session value associated to the given key.
Clear Clear() Used to delete all values in the session.
AddFlash AddFlash(value interface{}, vars ...string) Used to add a flash message to the session.
Flashes Flashes(vars ...string) []interface{} Used to get a slice of flash messages from the session.
Options Options(Options) Used to set configuration for a session.
Save Save() error Used to save all sessions used during the current request.

NewStore

The sessions middleware provides NewStore to store sessions in Cookie or Redis.

Function signatures of cookie.NewStore:

func NewStore(keyPairs ...[]byte) Store

Sample Code:

package main

import (
	"context"

	"github.com/cloudwego/hertz/pkg/app"
	"github.com/cloudwego/hertz/pkg/app/server"
	"github.com/cloudwego/hertz/pkg/common/utils"
	"github.com/hertz-contrib/sessions"
	"github.com/hertz-contrib/sessions/cookie"
)

func main() {
	h := server.New(server.WithHostPorts(":8000"))
	store := cookie.NewStore([]byte("secret"))
	h.Use(sessions.New("mysession", store))
	h.GET("/incr", func(ctx context.Context, c *app.RequestContext) {
		session := sessions.Default(c)
		var count int
		v := session.Get("count")
		if v == nil {
			count = 0
		} else {
			count = v.(int)
			count++
		}
		session.Set("count", count)
		_ = session.Save()
		c.JSON(200, utils.H{"count": count})
	})
	h.Spin()
}

Redis

Function signatures of redis.NewStore:

func NewStore(size int, network, addr, passwd string, keyPairs ...[]byte) (Store, error)

Sample Code:

package main

import (
	"context"

	"github.com/cloudwego/hertz/pkg/app"
	"github.com/cloudwego/hertz/pkg/app/server"
	"github.com/cloudwego/hertz/pkg/common/utils"
	"github.com/hertz-contrib/sessions"
	"github.com/hertz-contrib/sessions/redis"
)

func main() {
	h := server.Default(server.WithHostPorts(":8000"))
	store, _ := redis.NewStore(10, "tcp", "localhost:6379", "", []byte("secret"))
	h.Use(sessions.New("mysession", store))

	h.GET("/incr", func(ctx context.Context, c *app.RequestContext) {
		session := sessions.Default(c)
		var count int
		v := session.Get("count")
		if v == nil {
			count = 0
		} else {
			count = v.(int)
			count++
		}
		session.Set("count", count)
		session.Save()
		c.JSON(200, utils.H{"count": count})
	})
	h.Spin()
}

New

The sessions middleware provides New to create a single Session.

Function signatures:

func New(name string, store Store) app.HandlerFunc

Sample Code:

package main

import (
	"context"

	"github.com/cloudwego/hertz/pkg/app"
	"github.com/cloudwego/hertz/pkg/app/server"
	"github.com/cloudwego/hertz/pkg/common/utils"
	"github.com/hertz-contrib/sessions"
	"github.com/hertz-contrib/sessions/cookie"
)

func main() {
	h := server.New(server.WithHostPorts(":8000"))
	store := cookie.NewStore([]byte("secret"))
	h.Use(sessions.New("mysession", store))
	h.GET("/hello", func(ctx context.Context, c *app.RequestContext) {
		session := sessions.Default(c)

		if session.Get("hello") != "world" {
			session.Set("hello", "world")
			_ = session.Save()
		}

		c.JSON(200, utils.H{"hello": session.Get("hello")})
	})
	h.Spin()
}

Many

The sessions middleware provides Many to create multiple sessions.

Function signatures:

func Many(names []string, store Store) app.HandlerFunc

Sample Code:

package main

import (
	"context"

	"github.com/cloudwego/hertz/pkg/app"
	"github.com/cloudwego/hertz/pkg/app/server"
	"github.com/cloudwego/hertz/pkg/common/utils"
	"github.com/hertz-contrib/sessions"
	"github.com/hertz-contrib/sessions/cookie"
)

func main() {
	h := server.New(server.WithHostPorts(":8000"))
	store := cookie.NewStore([]byte("secret"))
	sessionNames := []string{"a", "b"}
	h.Use(sessions.Many(sessionNames, store))
	h.GET("/hello", func(ctx context.Context, c *app.RequestContext) {
		sessionA := sessions.DefaultMany(c, "a")
		sessionB := sessions.DefaultMany(c, "b")

		if sessionA.Get("hello") != "world!" {
			sessionA.Set("hello", "world!")
			_ = sessionA.Save()
		}

		if sessionB.Get("hello") != "world?" {
			sessionB.Set("hello", "world?")
			_ = sessionB.Save()
		}

		c.JSON(200, utils.H{
			"a": sessionA.Get("hello"),
			"b": sessionB.Get("hello"),
		})
	})
	h.Spin()
}

Default

The sessions middleware provides Default to fetch a single Session.

Function signatures:

func Default(c *app.RequestContext) Session

Sample Code:

package main

import (
	"context"

	"github.com/cloudwego/hertz/pkg/app"
	"github.com/cloudwego/hertz/pkg/app/server"
	"github.com/cloudwego/hertz/pkg/common/utils"
	"github.com/hertz-contrib/sessions"
	"github.com/hertz-contrib/sessions/cookie"
)

func main() {
	h := server.New(server.WithHostPorts(":8000"))
	store := cookie.NewStore([]byte("secret"))
	h.Use(sessions.New("mysession", store))
	h.GET("/hello", func(ctx context.Context, c *app.RequestContext) {
		session := sessions.Default(c)

		if session.Get("hello") != "world" {
			session.Set("hello", "world")
			_ = session.Save()
		}

		c.JSON(200, utils.H{"hello": session.Get("hello")})
	})
	h.Spin()
}

DefaultMany

The sessions middleware provides DefaultMany to get the Session based on its name.

Function signatures:

func DefaultMany(c *app.RequestContext, name string) Session

Sample Code:

package main

import (
	"context"

	"github.com/cloudwego/hertz/pkg/app"
	"github.com/cloudwego/hertz/pkg/app/server"
	"github.com/cloudwego/hertz/pkg/common/utils"
	"github.com/hertz-contrib/sessions"
	"github.com/hertz-contrib/sessions/cookie"
)

func main() {
	h := server.New(server.WithHostPorts(":8000"))
	store := cookie.NewStore([]byte("secret"))
	sessionNames := []string{"a", "b"}
	h.Use(sessions.Many(sessionNames, store))
	h.GET("/hello", func(ctx context.Context, c *app.RequestContext) {
		sessionA := sessions.DefaultMany(c, "a")
		sessionB := sessions.DefaultMany(c, "b")

		if sessionA.Get("hello") != "world!" {
			sessionA.Set("hello", "world!")
			_ = sessionA.Save()
		}

		if sessionB.Get("hello") != "world?" {
			sessionB.Set("hello", "world?")
			_ = sessionB.Save()
		}

		c.JSON(200, utils.H{
			"a": sessionA.Get("hello"),
			"b": sessionB.Get("hello"),
		})
	})
	h.Spin()
}

Distributed Session

Hertz also provides a bizdemo for distributed session solution based on Redis.

Note: This demo is only a simple demonstration of the distributed session, the specific business code needs to be modified by the user combined with the corresponding business logic

The distributed session solution based on redis is to store the sessions of different servers in redis or redis cluster, which aims to solve the problem that the sessions of multiple servers are not synchronized in the case of distributed system.

Display of core code

  1. Initialize session middleware:
// biz/mw/session.go
func InitSession(h *server.Hertz) {
    store, err := redis.NewStore(consts.MaxIdleNum, consts.TCP, consts.RedisAddr, consts.RedisPasswd, []byte(consts.SessionSecretKey))
    if err != nil {
    panic(err)
}
h.Use(sessions.New(consts.HertzSession, store))
}
  1. Store the session after user sign in:
// biz/handler/user/user_service.go/Login
// ...
session := sessions.Default(c)
session.Set(consts.Username, req.Username)
_ = session.Save()
// ...
  1. When the user visits the home page directly, determine whether the corresponding Session exists, and if not, then redirect to the login page (in this example) or restricts the resources that can be browsed or used after login:
// pkg/render/render.go
// ...
session := sessions.Default(c)
username := session.Get(consts.Username)
if username == nil {
    // ...
    c.Redirect(http.StatusMovedPermanently, []byte("/login.html"))
    return
}
// ...
  1. Clear the session after user sign out:
// biz/handler/user/user_service.go/Logout
// ...
session := sessions.Default(c)
session.Delete(consts.Username)
_ = session.Save()
// ...

Session middleware encapsulates most of the complex logic, users only need to call the simple interfaces to complete the corresponding business process.

Full Example

As for usage, you may refer to example and hertz_session


Last modified July 3, 2023 : Update response.md (0a36fb2)