redis store for session
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

400 lines
7.6 KiB

// Copyright 2015 The Tango Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package redistore
import (
"bytes"
"fmt"
"net/http"
"net/http/httptest"
"reflect"
"strconv"
"strings"
"testing"
"time"
"github.com/lunny/tango"
"github.com/tango-contrib/session"
)
type SessionAction struct {
session.Session
}
type Test struct {
Id int64
Name string
}
func sliceEq(a, b []string) bool {
if len(a) != len(b) {
return false
}
for i := 0; i < len(a); i++ {
if a[i] != b[i] {
return false
}
}
return true
}
func mapEq(a, b map[string]int) bool {
if len(a) != len(b) {
return false
}
for k, v := range a {
if b[k] != v {
return false
}
}
return true
}
func (action *SessionAction) Get() string {
var test1 = []string{"1", "2"}
action.Session.Set("11", test1)
res := action.Session.Get("11")
fmt.Println(res.([]string))
if !sliceEq(res.([]string), test1) {
return "0"
}
var test2 = map[string]int{"1": 1, "2": 2}
action.Session.Set("22", test2)
res2 := action.Session.Get("22")
fmt.Println(res2.(map[string]int))
if !mapEq(res2.(map[string]int), test2) {
return "0"
}
for i := 0; i < 3; i++ {
var test3 = Test{1, "xlw"}
action.Session.Set("33", &test3)
res3 := action.Session.Get("33")
fmt.Println(res3.(*Test))
if *res3.(*Test) != test3 {
return "0"
}
}
action.Session.Set("test", "1")
return action.Session.Get("test").(string)
}
func TestSession(t *testing.T) {
buff := bytes.NewBufferString("")
recorder := httptest.NewRecorder()
recorder.Body = buff
tg := tango.Classic()
tg.Use(session.New(session.Options{
Store: New(Options{
Host: "127.0.0.1",
DbIndex: 0,
MaxAge: 30 * time.Minute,
}),
}))
tg.Get("/", new(SessionAction))
req, err := http.NewRequest("GET", "http://localhost:8000/", nil)
if err != nil {
t.Error(err)
}
tg.ServeHTTP(recorder, req)
expect(t, recorder.Code, http.StatusOK)
refute(t, len(buff.String()), 0)
expect(t, buff.String(), "1")
}
type SessionAction2 struct {
session.Session
}
func (s *SessionAction2) Get() string {
if s.Session.Get("a") != nil {
return s.Session.Get("a").(string)
}
s.Session.Set("a", "b")
return ""
}
func TestSession2(t *testing.T) {
buff := bytes.NewBufferString("")
recorder := httptest.NewRecorder()
recorder.Body = buff
tg := tango.Classic()
tg.Use(session.New(session.Options{
Store: New(Options{
Host: "127.0.0.1",
DbIndex: 0,
MaxAge: 10 * time.Second,
}),
}))
tg.Get("/", new(SessionAction2))
req, err := http.NewRequest("GET", "http://localhost:8000/", nil)
if err != nil {
t.Error(err)
}
tg.ServeHTTP(recorder, req)
expect(t, recorder.Code, http.StatusOK)
expect(t, len(buff.String()), 0)
expect(t, buff.String(), "")
time.Sleep(time.Second)
req, err = http.NewRequest("GET", "http://localhost:8000/", nil)
if err != nil {
t.Error(err)
}
cks := readSetCookies(recorder.Header())
for _, ck := range cks {
req.AddCookie(ck)
}
buff.Reset()
recorder = httptest.NewRecorder()
recorder.Body = buff
tg.ServeHTTP(recorder, req)
expect(t, recorder.Code, http.StatusOK)
expect(t, len(buff.String()), 1)
expect(t, buff.String(), "b")
time.Sleep(time.Second * 15)
req, err = http.NewRequest("GET", "http://localhost:8000/", nil)
if err != nil {
t.Error(err)
}
cks = readSetCookies(recorder.Header())
for _, ck := range cks {
req.AddCookie(ck)
}
buff.Reset()
recorder = httptest.NewRecorder()
recorder.Body = buff
tg.ServeHTTP(recorder, req)
expect(t, recorder.Code, http.StatusOK)
expect(t, len(buff.String()), 0)
expect(t, buff.String(), "")
}
/* Test Helpers */
func expect(t *testing.T, a interface{}, b interface{}) {
if a != b {
t.Errorf("Expected %v (type %v) - Got %v (type %v)", b, reflect.TypeOf(b), a, reflect.TypeOf(a))
}
}
func refute(t *testing.T, a interface{}, b interface{}) {
if a == b {
t.Errorf("Did not expect %v (type %v) - Got %v (type %v)", b, reflect.TypeOf(b), a, reflect.TypeOf(a))
}
}
func parseCookieValue(raw string, allowDoubleQuote bool) (string, bool) {
// Strip the quotes, if present.
if allowDoubleQuote && len(raw) > 1 && raw[0] == '"' && raw[len(raw)-1] == '"' {
raw = raw[1 : len(raw)-1]
}
for i := 0; i < len(raw); i++ {
if !validCookieValueByte(raw[i]) {
return "", false
}
}
return raw, true
}
var isTokenTable = [127]bool{
'!': true,
'#': true,
'$': true,
'%': true,
'&': true,
'\'': true,
'*': true,
'+': true,
'-': true,
'.': true,
'0': true,
'1': true,
'2': true,
'3': true,
'4': true,
'5': true,
'6': true,
'7': true,
'8': true,
'9': true,
'A': true,
'B': true,
'C': true,
'D': true,
'E': true,
'F': true,
'G': true,
'H': true,
'I': true,
'J': true,
'K': true,
'L': true,
'M': true,
'N': true,
'O': true,
'P': true,
'Q': true,
'R': true,
'S': true,
'T': true,
'U': true,
'W': true,
'V': true,
'X': true,
'Y': true,
'Z': true,
'^': true,
'_': true,
'`': true,
'a': true,
'b': true,
'c': true,
'd': true,
'e': true,
'f': true,
'g': true,
'h': true,
'i': true,
'j': true,
'k': true,
'l': true,
'm': true,
'n': true,
'o': true,
'p': true,
'q': true,
'r': true,
's': true,
't': true,
'u': true,
'v': true,
'w': true,
'x': true,
'y': true,
'z': true,
'|': true,
'~': true,
}
func isToken(r rune) bool {
i := int(r)
return i < len(isTokenTable) && isTokenTable[i]
}
func isNotToken(r rune) bool {
return !isToken(r)
}
func validCookieValueByte(b byte) bool {
return 0x20 <= b && b < 0x7f && b != '"' && b != ';' && b != '\\'
}
func isCookieNameValid(raw string) bool {
return strings.IndexFunc(raw, isNotToken) < 0
}
// readSetCookies parses all "Set-Cookie" values from
// the header h and returns the successfully parsed Cookies.
func readSetCookies(h http.Header) []*http.Cookie {
cookies := []*http.Cookie{}
for _, line := range h["Set-Cookie"] {
parts := strings.Split(strings.TrimSpace(line), ";")
if len(parts) == 1 && parts[0] == "" {
continue
}
parts[0] = strings.TrimSpace(parts[0])
j := strings.Index(parts[0], "=")
if j < 0 {
continue
}
name, value := parts[0][:j], parts[0][j+1:]
if !isCookieNameValid(name) {
continue
}
value, success := parseCookieValue(value, true)
if !success {
continue
}
c := &http.Cookie{
Name: name,
Value: value,
Raw: line,
}
for i := 1; i < len(parts); i++ {
parts[i] = strings.TrimSpace(parts[i])
if len(parts[i]) == 0 {
continue
}
attr, val := parts[i], ""
if j := strings.Index(attr, "="); j >= 0 {
attr, val = attr[:j], attr[j+1:]
}
lowerAttr := strings.ToLower(attr)
val, success = parseCookieValue(val, false)
if !success {
c.Unparsed = append(c.Unparsed, parts[i])
continue
}
switch lowerAttr {
case "secure":
c.Secure = true
continue
case "httponly":
c.HttpOnly = true
continue
case "domain":
c.Domain = val
continue
case "max-age":
secs, err := strconv.Atoi(val)
if err != nil || secs != 0 && val[0] == '0' {
break
}
if secs <= 0 {
c.MaxAge = -1
} else {
c.MaxAge = secs
}
continue
case "expires":
c.RawExpires = val
exptime, err := time.Parse(time.RFC1123, val)
if err != nil {
exptime, err = time.Parse("Mon, 02-Jan-2006 15:04:05 MST", val)
if err != nil {
c.Expires = time.Time{}
break
}
}
c.Expires = exptime.UTC()
continue
case "path":
c.Path = val
continue
}
c.Unparsed = append(c.Unparsed, parts[i])
}
cookies = append(cookies, c)
}
return cookies
}