sha1..
This commit is contained in:
parent
09632be46c
commit
1c3000e532
7 changed files with 569 additions and 0 deletions
9
LICENSE
Normal file
9
LICENSE
Normal file
|
@ -0,0 +1,9 @@
|
||||||
|
MIT License
|
||||||
|
|
||||||
|
Copyright (c) 2025 DarkGopher
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
5
files/file.go
Normal file
5
files/file.go
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
// Package file implements sessions saved into files which contains gob data
|
||||||
|
package file
|
||||||
|
|
||||||
|
// SessionFile implement manager.Session interface
|
||||||
|
type SessionFile struct{}
|
3
go.mod
Normal file
3
go.mod
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
module git.mtux.eu/darkgopher/session
|
||||||
|
|
||||||
|
go 1.23.4
|
161
session.go
Normal file
161
session.go
Normal file
|
@ -0,0 +1,161 @@
|
||||||
|
// Package session (session manager) control process session lifetime create, update and destroy
|
||||||
|
package session
|
||||||
|
|
||||||
|
import (
|
||||||
|
"crypto/rand"
|
||||||
|
"encoding/base64"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"net/http"
|
||||||
|
"net/url"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
var milis int64 = 1000
|
||||||
|
|
||||||
|
// for provider fun
|
||||||
|
var provides = make(map[string]Provider)
|
||||||
|
|
||||||
|
// ProviderNames return slice of strings - registered Provider names
|
||||||
|
func ProviderNames() []string {
|
||||||
|
var prdnames []string
|
||||||
|
for pn := range provides {
|
||||||
|
prdnames = append(prdnames, pn)
|
||||||
|
}
|
||||||
|
return prdnames
|
||||||
|
}
|
||||||
|
|
||||||
|
// MilisPerSec return time resolution (milliseconds / 1sec)
|
||||||
|
func MilisPerSec() int64 {
|
||||||
|
return milis
|
||||||
|
}
|
||||||
|
|
||||||
|
// Session interface implement storage for one session and have maxLifetime and lastAccessTime
|
||||||
|
type Session interface {
|
||||||
|
//set session value and update last access time
|
||||||
|
Set(key, value interface{}) error
|
||||||
|
//get session value and update last access time
|
||||||
|
Get(key interface{}) (v any, err error)
|
||||||
|
//delete session value
|
||||||
|
Delete(key interface{}) error
|
||||||
|
//get session id for session
|
||||||
|
SessionID() string
|
||||||
|
}
|
||||||
|
|
||||||
|
// Provider interace implement lifecycle for one session
|
||||||
|
type Provider interface {
|
||||||
|
//create new session using sid value
|
||||||
|
Init(sid string) (Session, error)
|
||||||
|
//read return existing session by id or if not exist create new session
|
||||||
|
Read(sid string) (Session, error)
|
||||||
|
//destroy remove session with sid from storage if exist
|
||||||
|
Destroy(sid string) error
|
||||||
|
//regenerate id change old sid to newsid and preserve existing session data
|
||||||
|
RegenerateID(oldsid, newsid string) (err error)
|
||||||
|
//gc remove all sessions with > maxLifetime
|
||||||
|
GC(maxlifetime int64)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Register makes a session provide available by the provided name.
|
||||||
|
// If Register is called twice with the same name or if driver is nil, it panics.
|
||||||
|
func Register(name string, provide Provider) {
|
||||||
|
if provide == nil {
|
||||||
|
panic("session: Register provide is nil, must be import any session storage implementation")
|
||||||
|
}
|
||||||
|
if _, dup := provides[name]; dup {
|
||||||
|
panic("session: Already registered provider: " + name)
|
||||||
|
}
|
||||||
|
provides[name] = provide
|
||||||
|
}
|
||||||
|
|
||||||
|
// Manager controls all sessions with registered storage provider
|
||||||
|
type Manager struct {
|
||||||
|
cookieName string
|
||||||
|
provider Provider
|
||||||
|
maxlifetime int64
|
||||||
|
secure bool
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewManager create new *Manager for provideName, cookieName and maxlifetime in seconds
|
||||||
|
func NewManager(providerName, cookieName string, maxlifetime int64, ssl bool) (*Manager, error) {
|
||||||
|
var provider Provider
|
||||||
|
var ok bool
|
||||||
|
if provider, ok = provides[providerName]; !ok {
|
||||||
|
return nil, fmt.Errorf("session: Provider: %q not found (forgotten import?)", providerName)
|
||||||
|
}
|
||||||
|
m := &Manager{
|
||||||
|
cookieName: cookieName, provider: provider,
|
||||||
|
maxlifetime: maxlifetime, secure: ssl,
|
||||||
|
}
|
||||||
|
m.GC()
|
||||||
|
return m, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// generate new secure 32 byte sessionID
|
||||||
|
func (manager *Manager) sessionID() (sid string, err error) {
|
||||||
|
b := make([]byte, 32)
|
||||||
|
if _, err = io.ReadFull(rand.Reader, b); err != nil {
|
||||||
|
return "", fmt.Errorf("Session manager error: generate sessionID failed: %w", err)
|
||||||
|
}
|
||||||
|
return base64.URLEncoding.EncodeToString(b), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// SessionStart start session for next http response
|
||||||
|
func (manager *Manager) SessionStart(w http.ResponseWriter, r *http.Request) (session Session, err error) {
|
||||||
|
var cookie *http.Cookie
|
||||||
|
var sid string
|
||||||
|
if cookie, err = r.Cookie(manager.cookieName); err != nil || cookie == nil {
|
||||||
|
if sid, err = manager.sessionID(); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if session, err = manager.provider.Init(sid); err != nil {
|
||||||
|
return nil, fmt.Errorf("Session init failed: %w", err)
|
||||||
|
}
|
||||||
|
cookie := http.Cookie{
|
||||||
|
Name: manager.cookieName, Value: url.QueryEscape(sid), Path: "/",
|
||||||
|
HttpOnly: true, MaxAge: int(manager.maxlifetime),
|
||||||
|
Secure: manager.secure,
|
||||||
|
}
|
||||||
|
http.SetCookie(w, &cookie)
|
||||||
|
} else {
|
||||||
|
if sid, err = url.QueryUnescape(cookie.Value); err != nil {
|
||||||
|
return nil, fmt.Errorf("Session cookie decode error: %v", err)
|
||||||
|
}
|
||||||
|
if session, err = manager.provider.Read(sid); err != nil {
|
||||||
|
return nil, fmt.Errorf("Session provider read error: %w", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// SessionDestroy end session and delete session data at the server
|
||||||
|
func (manager *Manager) SessionDestroy(w http.ResponseWriter, r *http.Request) (err error) {
|
||||||
|
var cookie *http.Cookie
|
||||||
|
if cookie, err = r.Cookie(manager.cookieName); err != nil || cookie.Value == "" {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
manager.provider.Destroy(cookie.Value)
|
||||||
|
rmcookie := http.Cookie{
|
||||||
|
Name: manager.cookieName, Path: "/", HttpOnly: true,
|
||||||
|
Expires: time.Now(), MaxAge: -1,
|
||||||
|
}
|
||||||
|
http.SetCookie(w, &rmcookie)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// RegenerateID vhange sid and preserve all session data
|
||||||
|
func (manager *Manager) RegenerateID(w http.ResponseWriter, r *http.Request) {
|
||||||
|
if ck, err := r.Cookie(manager.cookieName); err == nil && ck.Value != "" {
|
||||||
|
if newid, err := manager.sessionID(); err != nil {
|
||||||
|
manager.provider.RegenerateID(ck.Value, newid)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// GC remove sessions which exceeded manager.maxLifetime
|
||||||
|
func (manager *Manager) GC() {
|
||||||
|
manager.provider.GC(manager.maxlifetime)
|
||||||
|
msec := milis * manager.maxlifetime
|
||||||
|
time.AfterFunc(time.Duration(msec), func() { manager.GC() })
|
||||||
|
}
|
5
session_test.go
Normal file
5
session_test.go
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
package session
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
milis = 1
|
||||||
|
}
|
137
storage/memory/memory.go
Normal file
137
storage/memory/memory.go
Normal file
|
@ -0,0 +1,137 @@
|
||||||
|
// Package memory implements sessions saved into memory is gone when stop the server
|
||||||
|
package memory
|
||||||
|
|
||||||
|
import (
|
||||||
|
"container/list"
|
||||||
|
"sync"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"git.mtux.eu/darkgopher/session"
|
||||||
|
)
|
||||||
|
|
||||||
|
var pder = &ProviderMemory{list: list.New()}
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
pder.sessions = make(map[string]*list.Element, 0)
|
||||||
|
session.Register("memory", pder)
|
||||||
|
}
|
||||||
|
|
||||||
|
// SessionMemory implement sessionma.Session interface only in memory
|
||||||
|
type SessionMemory struct {
|
||||||
|
sid string
|
||||||
|
atime time.Time
|
||||||
|
data map[any]any
|
||||||
|
}
|
||||||
|
|
||||||
|
func (sm *SessionMemory) resolvepanic(err *error) {
|
||||||
|
if r := recover(); r != nil {
|
||||||
|
*err = r.(error)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set -
|
||||||
|
func (sm *SessionMemory) Set(k any, v any) (err error) {
|
||||||
|
defer pder.updateAtime(sm.sid)
|
||||||
|
defer sm.resolvepanic(&err)
|
||||||
|
sm.data[k] = v
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get -
|
||||||
|
func (sm *SessionMemory) Get(k any) (v any, err error) {
|
||||||
|
defer pder.updateAtime(sm.sid)
|
||||||
|
defer sm.resolvepanic(&err)
|
||||||
|
return sm.data[k], err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Delete -
|
||||||
|
func (sm *SessionMemory) Delete(k any) (err error) {
|
||||||
|
defer pder.updateAtime(sm.sid)
|
||||||
|
|
||||||
|
delete(sm.data, k)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// SessionID -
|
||||||
|
func (sm *SessionMemory) SessionID() string {
|
||||||
|
return sm.sid
|
||||||
|
}
|
||||||
|
|
||||||
|
// ProviderMemory implement memory session provider
|
||||||
|
type ProviderMemory struct {
|
||||||
|
lock sync.Mutex
|
||||||
|
sessions map[string]*list.Element //save to RAM
|
||||||
|
list *list.List //for GC
|
||||||
|
}
|
||||||
|
|
||||||
|
func (pder *ProviderMemory) updateAtime(sid string) {
|
||||||
|
pder.lock.Lock()
|
||||||
|
defer pder.lock.Unlock()
|
||||||
|
if ssel, ok := pder.sessions[sid]; ok {
|
||||||
|
ssel.Value.(*SessionMemory).atime = time.Now()
|
||||||
|
pder.list.MoveToFront(ssel)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Init create new session store for sid
|
||||||
|
func (pder *ProviderMemory) Init(sid string) (ses session.Session, err error) {
|
||||||
|
pder.lock.Lock()
|
||||||
|
defer pder.lock.Unlock()
|
||||||
|
data := make(map[any]any, 0)
|
||||||
|
sess := &SessionMemory{sid, time.Now(), data}
|
||||||
|
sessel := pder.list.PushBack(sess)
|
||||||
|
pder.sessions[sid] = sessel
|
||||||
|
return sess, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// read return existing unexpired session or create new
|
||||||
|
func (pder *ProviderMemory) Read(sid string) (ses session.Session, err error) {
|
||||||
|
pder.lock.Lock()
|
||||||
|
defer pder.lock.Unlock()
|
||||||
|
if ssel, ok := pder.sessions[sid]; ok {
|
||||||
|
return ssel.Value.(*SessionMemory), nil
|
||||||
|
}
|
||||||
|
return pder.Init(sid)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Destroy delete session storage with sid
|
||||||
|
func (pder *ProviderMemory) Destroy(sid string) (err error) {
|
||||||
|
pder.lock.Lock()
|
||||||
|
defer pder.lock.Unlock()
|
||||||
|
if ssel, ok := pder.sessions[sid]; ok {
|
||||||
|
delete(pder.sessions, sid)
|
||||||
|
pder.list.Remove(ssel)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// RegenerateID replace session ID to new one and preserve all data
|
||||||
|
func (pder *ProviderMemory) RegenerateID(oldsid, newsid string) (err error) {
|
||||||
|
pder.lock.Lock()
|
||||||
|
defer pder.lock.Unlock()
|
||||||
|
if ssel, ok := pder.sessions[oldsid]; ok {
|
||||||
|
ssel.Value.(*SessionMemory).sid = newsid
|
||||||
|
delete(pder.sessions, oldsid)
|
||||||
|
pder.sessions[newsid] = ssel
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// GC iterate all sessions and delete expired
|
||||||
|
func (pder *ProviderMemory) GC(maxlifetime int64) {
|
||||||
|
pder.lock.Lock()
|
||||||
|
defer pder.lock.Unlock()
|
||||||
|
for {
|
||||||
|
ssel := pder.list.Back()
|
||||||
|
if ssel == nil {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
if ssel.Value.(*SessionMemory).atime.UnixMilli()+(maxlifetime*session.MilisPerSec()) < time.Now().UnixMilli() {
|
||||||
|
pder.list.Remove(ssel)
|
||||||
|
delete(pder.sessions, ssel.Value.(*SessionMemory).sid)
|
||||||
|
} else {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
249
tests/t_test.go
Normal file
249
tests/t_test.go
Normal file
|
@ -0,0 +1,249 @@
|
||||||
|
package tests
|
||||||
|
|
||||||
|
// Package tests --
|
||||||
|
// 1) StartSession -----
|
||||||
|
// 2) DestroySessioon -----
|
||||||
|
// 3) Set session variable--|
|
||||||
|
// 4) Get session variable--|
|
||||||
|
// 5) Update -- // --
|
||||||
|
// 6) Delete -- // --
|
||||||
|
// 7) GC session after lifetime
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"net/http"
|
||||||
|
"net/http/cookiejar"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"git.mtux.eu/darkgopher/session"
|
||||||
|
|
||||||
|
//all impls imports here ...
|
||||||
|
_ "git.mtux.eu/darkgopher/session/storage/memory"
|
||||||
|
)
|
||||||
|
|
||||||
|
const sid = "sid"
|
||||||
|
const lifet = 180
|
||||||
|
const testsrvaddr = ":8888"
|
||||||
|
|
||||||
|
var sm *session.Manager //current session impl.
|
||||||
|
var tc *TestClient //current test client
|
||||||
|
|
||||||
|
type TestServer struct {
|
||||||
|
addr string
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewTestServer(addr string) (ts *TestServer) {
|
||||||
|
return &TestServer{addr}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ts *TestServer) Serve() {
|
||||||
|
http.HandleFunc("/startsession", ts.startSession) //ck
|
||||||
|
http.HandleFunc("/destroysession", ts.destroySession) //ck
|
||||||
|
http.HandleFunc("/setsessionvar", ts.setSessionVar)
|
||||||
|
http.HandleFunc("/getsessionvar", ts.getSessionVar)
|
||||||
|
http.HandleFunc("/updatesessionvar", ts.updateSessionVar)
|
||||||
|
http.HandleFunc("/validateupdatedvalue", ts.validateUpdatedValue)
|
||||||
|
http.HandleFunc("/deletesessionvalue", ts.deleteSessionValue)
|
||||||
|
http.HandleFunc("/validatedeletedvalue", ts.validateDeletedValue)
|
||||||
|
http.HandleFunc("/sessionGC", ts.sessionGC) //ck
|
||||||
|
http.ListenAndServe(ts.addr, nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ts *TestServer) startSession(w http.ResponseWriter, r *http.Request) {
|
||||||
|
var err error
|
||||||
|
if _, err = sm.SessionStart(w, r); err != nil {
|
||||||
|
io.WriteString(w, err.Error())
|
||||||
|
}
|
||||||
|
io.WriteString(w, "OK")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ts *TestServer) destroySession(w http.ResponseWriter, r *http.Request) {
|
||||||
|
var err error
|
||||||
|
if _, err = sm.SessionStart(w, r); err != nil {
|
||||||
|
io.WriteString(w, err.Error())
|
||||||
|
}
|
||||||
|
if err = sm.SessionDestroy(w, r); err != nil {
|
||||||
|
io.WriteString(w, err.Error())
|
||||||
|
}
|
||||||
|
io.WriteString(w, "OK")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ts *TestServer) setSessionVar(w http.ResponseWriter, r *http.Request) {
|
||||||
|
var err error
|
||||||
|
var sess session.Session
|
||||||
|
if sess, err = sm.SessionStart(w, r); err != nil {
|
||||||
|
io.WriteString(w, err.Error())
|
||||||
|
}
|
||||||
|
if err = sess.Set("Pes", "Minule"); err != nil {
|
||||||
|
io.WriteString(w, err.Error())
|
||||||
|
}
|
||||||
|
if err = sess.Set("Vek", 16); err != nil {
|
||||||
|
io.WriteString(w, err.Error())
|
||||||
|
}
|
||||||
|
io.WriteString(w, "OK")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ts *TestServer) getSessionVar(w http.ResponseWriter, r *http.Request) {
|
||||||
|
var err error
|
||||||
|
var sess session.Session
|
||||||
|
if sess, err = sm.SessionStart(w, r); err != nil {
|
||||||
|
io.WriteString(w, err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
v any
|
||||||
|
n any
|
||||||
|
)
|
||||||
|
|
||||||
|
if v, err = sess.Get("Pes"); err != nil {
|
||||||
|
io.WriteString(w, err.Error())
|
||||||
|
}
|
||||||
|
if n, err = sess.Get("Vek"); err != nil {
|
||||||
|
io.WriteString(w, err.Error())
|
||||||
|
}
|
||||||
|
if v.(string) != "Minule" {
|
||||||
|
fmt.Fprintf(w, "Value of session key: 'Pes' must be 'Minule', not: %s", v)
|
||||||
|
}
|
||||||
|
if n.(int) != 10 {
|
||||||
|
fmt.Fprintf(w, "Value of session key: 'Vek' must be '16', not: %d", n)
|
||||||
|
}
|
||||||
|
io.WriteString(w, "OK")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ts *TestServer) updateSessionVar(w http.ResponseWriter, r *http.Request) {
|
||||||
|
var err error
|
||||||
|
var sess session.Session
|
||||||
|
if sess, err = sm.SessionStart(w, r); err != nil {
|
||||||
|
io.WriteString(w, err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
if err = sess.Set("Vek", 3); err != nil {
|
||||||
|
io.WriteString(w, err.Error())
|
||||||
|
}
|
||||||
|
io.WriteString(w, "OK")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ts *TestServer) validateUpdatedValue(w http.ResponseWriter, r *http.Request) {
|
||||||
|
var err error
|
||||||
|
var sess session.Session
|
||||||
|
if sess, err = sm.SessionStart(w, r); err != nil {
|
||||||
|
io.WriteString(w, err.Error())
|
||||||
|
}
|
||||||
|
fmt.Println(sess)
|
||||||
|
var n any
|
||||||
|
if n, err = sess.Get("Vek"); err != nil {
|
||||||
|
io.WriteString(w, err.Error())
|
||||||
|
}
|
||||||
|
if n.(int) != 3 {
|
||||||
|
io.WriteString(w, fmt.Sprintf("Value vek ater update must be: '3', not: '%d'", n.(int)))
|
||||||
|
}
|
||||||
|
io.WriteString(w, "OK")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ts *TestServer) deleteSessionValue(w http.ResponseWriter, r *http.Request) {
|
||||||
|
var err error
|
||||||
|
var sess session.Session
|
||||||
|
if sess, err = sm.SessionStart(w, r); err != nil {
|
||||||
|
io.WriteString(w, err.Error())
|
||||||
|
}
|
||||||
|
if sess.Delete("Vek"); err != nil {
|
||||||
|
io.WriteString(w, err.Error())
|
||||||
|
}
|
||||||
|
io.WriteString(w, "OK")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ts *TestServer) validateDeletedValue(w http.ResponseWriter, r *http.Request) {
|
||||||
|
var err error
|
||||||
|
var sess session.Session
|
||||||
|
if sess, err = sm.SessionStart(w, r); err != nil {
|
||||||
|
io.WriteString(w, err.Error())
|
||||||
|
}
|
||||||
|
var v any
|
||||||
|
if v, err = sess.Get("Vek"); err != nil {
|
||||||
|
io.WriteString(w, err.Error())
|
||||||
|
}
|
||||||
|
if v != nil {
|
||||||
|
io.WriteString(w, fmt.Sprintf("Session value 'Vek' must be nil after delete, not: %v", v))
|
||||||
|
}
|
||||||
|
io.WriteString(w, "OK")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ts *TestServer) sessionGC(w http.ResponseWriter, r *http.Request) {
|
||||||
|
var err error
|
||||||
|
if _, err = sm.SessionStart(w, r); err != nil {
|
||||||
|
io.WriteString(w, err.Error())
|
||||||
|
}
|
||||||
|
time.Sleep(200 * time.Millisecond)
|
||||||
|
io.WriteString(w, "OK")
|
||||||
|
}
|
||||||
|
|
||||||
|
type TestClient struct {
|
||||||
|
cl *http.Client
|
||||||
|
ckstore *cookiejar.Jar
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewTestClient() (tc *TestClient, err error) {
|
||||||
|
var ckstore *cookiejar.Jar
|
||||||
|
if ckstore, err = cookiejar.New(nil); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
httpclient := &http.Client{Jar: ckstore}
|
||||||
|
tc = &TestClient{httpclient, ckstore}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Request send http request into test server and return ResponseResult or error
|
||||||
|
func (tc *TestClient) Request(meth, uri string) (resb []byte, err error) {
|
||||||
|
var req *http.Request
|
||||||
|
if req, err = http.NewRequest(meth, uri, nil); err != nil {
|
||||||
|
return nil, fmt.Errorf("Create request to: %s ailed err: %v", uri, err)
|
||||||
|
}
|
||||||
|
var rs *http.Response
|
||||||
|
if rs, err = tc.cl.Do(req); err != nil {
|
||||||
|
return nil, fmt.Errorf("Request failed err: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
defer rs.Body.Close()
|
||||||
|
var body []byte
|
||||||
|
if body, err = io.ReadAll(rs.Body); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return body, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func t1(t *testing.T) {
|
||||||
|
var res []byte
|
||||||
|
var err error
|
||||||
|
if res, err = tc.Request("GET", "http://localhost:8080/startsession/"); err != nil {
|
||||||
|
t.Logf("err: %v", err)
|
||||||
|
t.Error(err)
|
||||||
|
}
|
||||||
|
if string(res) != "OK" {
|
||||||
|
t.Errorf("Response from test request not OK, but: %s", res)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func t2(t *testing.T) {}
|
||||||
|
func t3(t *testing.T) {}
|
||||||
|
func t4(t *testing.T) {}
|
||||||
|
func t5(t *testing.T) {}
|
||||||
|
func t6(t *testing.T) {}
|
||||||
|
func t7(t *testing.T) {}
|
||||||
|
|
||||||
|
// iterate all session storage impls. and run t1 - t7
|
||||||
|
func TestRunAll(t *testing.T) {
|
||||||
|
ts := NewTestServer("localhost:8080")
|
||||||
|
ts.Serve()
|
||||||
|
for _, pn := range session.ProviderNames() {
|
||||||
|
var err error
|
||||||
|
tc, err = NewTestClient()
|
||||||
|
if sm, err = session.NewManager(pn, sid, lifet, true); err != nil {
|
||||||
|
t.Errorf("Session provider %s failed initialize err: %v", pn, err)
|
||||||
|
}
|
||||||
|
fns := []func(t *testing.T){t1, t2, t3, t4, t5, t6, t7}
|
||||||
|
for idx, fn := range fns {
|
||||||
|
t.Run(fmt.Sprintf("Test%d", idx+1), fn)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
Loading…
Add table
Add a link
Reference in a new issue