Skip to content

Instantly share code, notes, and snippets.

@ptman
Created December 3, 2023 20:26
Show Gist options
  • Save ptman/acb9d86f2151eee9cc5477b8064aa9ca to your computer and use it in GitHub Desktop.
Save ptman/acb9d86f2151eee9cc5477b8064aa9ca to your computer and use it in GitHub Desktop.
Redis Session Module for Revel.
// Copyright © Paul Tötterman <paul.totterman@iki.fi>. All rights reserved.
// This program is free software: you can redistribute it and/or modify it under
// the terms of the GNU Affero General Public License version 3 as published by
// the Free Software Foundation.
// This program is distributed in the hope that it will be useful, but WITHOUT
// ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
// FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
// details.
// You should have received a copy of the GNU General Public License along with
// this program. If not, see <https://www.gnu.org/licenses/>.
package redissession
// Place this file in e.g. app/modules/redis_session/redis_session.go
import (
"bytes"
"encoding/gob"
"fmt"
"io"
"net/http"
"strconv"
"time"
"github.com/gomodule/redigo/redis"
"github.com/pkg/errors"
"github.com/revel/revel"
"github.com/revel/revel/logger"
"github.com/revel/revel/session"
)
var log logger.MultiLogger
type Engine struct {
redisPool *redis.Pool
cookieExpire time.Duration
sessionDuration time.Duration
}
func redisCloser(conn io.Closer) {
if err := conn.Close(); err != nil {
log.Error("Error closing redis connection",
"err", errors.WithStack(err))
}
}
func (e *Engine) Decode(c *revel.Controller) {
c.Session = session.Session{}
sessionCookieName := revel.CookiePrefix + session.SessionCookieSuffix
cookie, err := c.Request.Cookie(sessionCookieName)
if err != nil {
return
}
id := cookie.GetValue()
conn := e.redisPool.Get()
defer redisCloser(conn)
buf, err := redis.Bytes(conn.Do("GET", fmt.Sprint("session:", id)))
if err != nil {
log.Error("Error getting cookie", "err", errors.WithStack(err))
return
}
dec := gob.NewDecoder(bytes.NewBuffer(buf))
var sess session.Session
if err := dec.Decode(&sess); err != nil {
log.Error("Error decoding session",
"err", errors.WithStack(err))
return
}
c.Session = sess
}
func (e *Engine) Encode(c *revel.Controller) {
if c.Session.Empty() {
return
}
id := c.Session.ID()
var buf bytes.Buffer
enc := gob.NewEncoder(&buf)
if err := enc.Encode(c.Session); err != nil {
log.Error("Error encoding session",
"err", errors.WithStack(err))
return
}
conn := e.redisPool.Get()
defer redisCloser(conn)
params := []interface{}{"session:" + id, buf.Bytes()}
ts := c.Session.GetExpiration(e.cookieExpire)
if ts.IsZero() {
c.Session[session.TimestampKey] = session.SessionValueName
if e.sessionDuration != 0 {
params = append(params, "EX",
e.sessionDuration.Seconds())
}
} else {
c.Session[session.TimestampKey] = strconv.FormatInt(ts.Unix(),
10)
params = append(params, "EX", time.Until(ts).Seconds())
}
_, err := conn.Do("SET", params...)
if err != nil {
log.Error("Error saving session", "err", errors.WithStack(err))
return
}
c.SetCookie(&http.Cookie{
Name: revel.CookiePrefix + session.SessionCookieSuffix,
Value: id,
Expires: c.Session.GetExpiration(e.cookieExpire).UTC(),
MaxAge: int(e.cookieExpire.Seconds()),
Domain: revel.CookieDomain,
Path: "/",
HttpOnly: true,
Secure: revel.CookieSecure,
SameSite: http.SameSiteDefaultMode,
})
}
func RevokeSession(session session.Session, conn redis.Conn) error {
_, err := conn.Do("DEL", "session:"+session.ID())
return errors.WithStack(err)
}
func InitRedisEngine() revel.SessionEngine {
redisAddr := revel.Config.StringDefault("redis.addr", "localhost:6379")
var err error
var cookieExpire time.Duration
if expires, ok := revel.Config.String("session.expires"); !ok {
cookieExpire = 30 * 24 * time.Hour
} else if expires == session.SessionValueName {
cookieExpire = 0
} else if cookieExpire, err = time.ParseDuration(expires); err != nil {
panic(errors.Errorf("session.expires invalid %s", err))
}
duration := revel.Config.StringDefault("session.duration", "24h")
sessionDuration, err := time.ParseDuration(duration)
if err != nil {
panic(errors.Errorf("session.duration invalid %s", err))
}
return &Engine{
cookieExpire: cookieExpire,
sessionDuration: sessionDuration,
redisPool: &redis.Pool{
MaxIdle: 5,
IdleTimeout: 60 * time.Second,
Dial: func() (redis.Conn, error) {
return redis.Dial("tcp", redisAddr)
},
},
}
}
func init() {
revel.RegisterSessionEngine(InitRedisEngine, "revel-redis")
revel.RegisterModuleInit(func(module *revel.Module) {
log = module.Log
})
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment