mirror of
https://github.com/kataras/iris.git
synced 2025-12-18 02:17:05 +00:00
Add notes for the new lead maintainer of the open-source iris project and align with @get-ion/ion by @hiveminded
Former-commit-id: da4f38eb9034daa49446df3ee529423b98f9b331
This commit is contained in:
5
sessions/AUTHORS
Normal file
5
sessions/AUTHORS
Normal file
@@ -0,0 +1,5 @@
|
||||
# This is the official list of Iris Sessions authors for copyright
|
||||
# purposes.
|
||||
|
||||
Gerasimos Maropoulos <kataras2006@hotmail.com>
|
||||
Bill Qeras, Jr. <hiveminded@tutanota.com>
|
||||
@@ -1,4 +1,4 @@
|
||||
Copyright (c) 2017 Gerasimos Maropoulos, ΓΜ. All rights reserved.
|
||||
Copyright (c) 2017 The Iris Sessions Authors. All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions are
|
||||
@@ -10,8 +10,8 @@ notice, this list of conditions and the following disclaimer.
|
||||
copyright notice, this list of conditions and the following disclaimer
|
||||
in the documentation and/or other materials provided with the
|
||||
distribution.
|
||||
* Neither the name of Gerasimos Maropoulos nor the name of his
|
||||
username, kataras, may be used to endorse or promote products derived from
|
||||
* Neither the name of Iris nor the names of its
|
||||
contributors may be used to endorse or promote products derived from
|
||||
this software without specific prior written permission.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
|
||||
11
sessions/README.md
Normal file
11
sessions/README.md
Normal file
@@ -0,0 +1,11 @@
|
||||
# Sessions
|
||||
|
||||
Fast HTTP Sessions for the [iris](https://github.com/kataras/iris) web framework.
|
||||
|
||||
## Table of contents
|
||||
|
||||
* [Overview](_examples/overview/main.go)
|
||||
* [Standalone](_examples/standalone/main.go)
|
||||
* [Secure Cookie](_examples/securecookie/main.go)
|
||||
* [Flash Messages](_examples/flash-messages/main.go)
|
||||
* [Database](_examples/database/main.go)
|
||||
69
sessions/_examples/database/main.go
Normal file
69
sessions/_examples/database/main.go
Normal file
@@ -0,0 +1,69 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"github.com/kataras/iris"
|
||||
"github.com/kataras/iris/context"
|
||||
|
||||
"github.com/kataras/iris/sessions"
|
||||
"github.com/kataras/iris/sessions/sessiondb/redis"
|
||||
"github.com/kataras/iris/sessions/sessiondb/redis/service"
|
||||
)
|
||||
|
||||
func main() {
|
||||
// replace with your running redis' server settings:
|
||||
db := redis.New(service.Config{Network: service.DefaultRedisNetwork,
|
||||
Addr: service.DefaultRedisAddr,
|
||||
Password: "",
|
||||
Database: "",
|
||||
MaxIdle: 0,
|
||||
MaxActive: 0,
|
||||
IdleTimeout: service.DefaultRedisIdleTimeout,
|
||||
Prefix: "",
|
||||
MaxAgeSeconds: service.DefaultRedisMaxAgeSeconds}) // optionally configure the bridge between your redis server
|
||||
|
||||
sess := sessions.New(sessions.Config{Cookie: "sessionscookieid"})
|
||||
|
||||
//
|
||||
// IMPORTANT:
|
||||
//
|
||||
sess.UseDatabase(db)
|
||||
|
||||
// the rest of the code stays the same.
|
||||
app := iris.New()
|
||||
|
||||
app.Get("/", func(ctx context.Context) {
|
||||
ctx.Writef("You should navigate to the /set, /get, /delete, /clear,/destroy instead")
|
||||
})
|
||||
app.Get("/set", func(ctx context.Context) {
|
||||
|
||||
//set session values
|
||||
sess.Start(ctx).Set("name", "iris")
|
||||
|
||||
//test if setted here
|
||||
ctx.Writef("All ok session setted to: %s", sess.Start(ctx).GetString("name"))
|
||||
})
|
||||
|
||||
app.Get("/get", func(ctx context.Context) {
|
||||
// get a specific key, as string, if no found returns just an empty string
|
||||
name := sess.Start(ctx).GetString("name")
|
||||
|
||||
ctx.Writef("The name on the /set was: %s", name)
|
||||
})
|
||||
|
||||
app.Get("/delete", func(ctx context.Context) {
|
||||
// delete a specific key
|
||||
sess.Start(ctx).Delete("name")
|
||||
})
|
||||
|
||||
app.Get("/clear", func(ctx context.Context) {
|
||||
// removes all entries
|
||||
sess.Start(ctx).Clear()
|
||||
})
|
||||
|
||||
app.Get("/destroy", func(ctx context.Context) {
|
||||
//destroy, removes the entire session data and cookie
|
||||
sess.Destroy(ctx)
|
||||
})
|
||||
|
||||
app.Run(iris.Addr(":8080"))
|
||||
}
|
||||
43
sessions/_examples/flash-messages/main.go
Normal file
43
sessions/_examples/flash-messages/main.go
Normal file
@@ -0,0 +1,43 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"github.com/kataras/iris"
|
||||
"github.com/kataras/iris/context"
|
||||
|
||||
"github.com/kataras/iris/sessions"
|
||||
)
|
||||
|
||||
func main() {
|
||||
app := iris.New()
|
||||
sess := sessions.New(sessions.Config{Cookie: "myappsessionid"})
|
||||
|
||||
app.Get("/set", func(ctx context.Context) {
|
||||
s := sess.Start(ctx)
|
||||
s.SetFlash("name", "iris")
|
||||
ctx.Writef("Message setted, is available for the next request")
|
||||
})
|
||||
|
||||
app.Get("/get", func(ctx context.Context) {
|
||||
s := sess.Start(ctx)
|
||||
name := s.GetFlashString("name")
|
||||
if name == "" {
|
||||
ctx.Writef("Empty name!!")
|
||||
return
|
||||
}
|
||||
ctx.Writef("Hello %s", name)
|
||||
})
|
||||
|
||||
app.Get("/test", func(ctx context.Context) {
|
||||
s := sess.Start(ctx)
|
||||
name := s.GetFlashString("name")
|
||||
if name == "" {
|
||||
ctx.Writef("Empty name!!")
|
||||
return
|
||||
}
|
||||
|
||||
ctx.Writef("Ok you are coming from /set ,the value of the name is %s", name)
|
||||
ctx.Writef(", and again from the same context: %s", name)
|
||||
})
|
||||
|
||||
app.Run(iris.Addr(":8080"))
|
||||
}
|
||||
52
sessions/_examples/overview/main.go
Normal file
52
sessions/_examples/overview/main.go
Normal file
@@ -0,0 +1,52 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"github.com/kataras/iris"
|
||||
"github.com/kataras/iris/context"
|
||||
|
||||
"github.com/kataras/iris/sessions"
|
||||
)
|
||||
|
||||
var (
|
||||
cookieNameForSessionID = "mycookiesessionnameid"
|
||||
sess = sessions.New(sessions.Config{Cookie: cookieNameForSessionID})
|
||||
)
|
||||
|
||||
func secret(ctx context.Context) {
|
||||
|
||||
// Check if user is authenticated
|
||||
if auth, _ := sess.Start(ctx).GetBoolean("authenticated"); !auth {
|
||||
ctx.StatusCode(iris.StatusForbidden)
|
||||
return
|
||||
}
|
||||
|
||||
// Print secret message
|
||||
ctx.WriteString("The cake is a lie!")
|
||||
}
|
||||
|
||||
func login(ctx context.Context) {
|
||||
session := sess.Start(ctx)
|
||||
|
||||
// Authentication goes here
|
||||
// ...
|
||||
|
||||
// Set user as authenticated
|
||||
session.Set("authenticated", true)
|
||||
}
|
||||
|
||||
func logout(ctx context.Context) {
|
||||
session := sess.Start(ctx)
|
||||
|
||||
// Revoke users authentication
|
||||
session.Set("authenticated", false)
|
||||
}
|
||||
|
||||
func main() {
|
||||
app := iris.New()
|
||||
|
||||
app.Get("/secret", secret)
|
||||
app.Get("/login", login)
|
||||
app.Get("/logout", logout)
|
||||
|
||||
app.Run(iris.Addr(":8080"))
|
||||
}
|
||||
75
sessions/_examples/securecookie/main.go
Normal file
75
sessions/_examples/securecookie/main.go
Normal file
@@ -0,0 +1,75 @@
|
||||
package main
|
||||
|
||||
// developers can use any library to add a custom cookie encoder/decoder.
|
||||
// At this example we use the gorilla's securecookie package:
|
||||
// $ go get github.com/gorilla/securecookie
|
||||
// $ go run main.go
|
||||
|
||||
import (
|
||||
"github.com/kataras/iris"
|
||||
"github.com/kataras/iris/context"
|
||||
|
||||
"github.com/kataras/iris/sessions"
|
||||
|
||||
"github.com/gorilla/securecookie"
|
||||
)
|
||||
|
||||
func main() {
|
||||
app := iris.New()
|
||||
|
||||
cookieName := "mycustomsessionid"
|
||||
// AES only supports key sizes of 16, 24 or 32 bytes.
|
||||
// You either need to provide exactly that amount or you derive the key from what you type in.
|
||||
hashKey := []byte("the-big-and-secret-fash-key-here")
|
||||
blockKey := []byte("lot-secret-of-characters-big-too")
|
||||
secureCookie := securecookie.New(hashKey, blockKey)
|
||||
|
||||
mySessions := sessions.New(sessions.Config{
|
||||
Cookie: cookieName,
|
||||
Encode: secureCookie.Encode,
|
||||
Decode: secureCookie.Decode,
|
||||
})
|
||||
|
||||
app.Get("/", func(ctx context.Context) {
|
||||
ctx.Writef("You should navigate to the /set, /get, /delete, /clear,/destroy instead")
|
||||
})
|
||||
app.Get("/set", func(ctx context.Context) {
|
||||
|
||||
//set session values
|
||||
s := mySessions.Start(ctx)
|
||||
s.Set("name", "iris")
|
||||
|
||||
//test if setted here
|
||||
ctx.Writef("All ok session setted to: %s", s.GetString("name"))
|
||||
})
|
||||
|
||||
app.Get("/get", func(ctx context.Context) {
|
||||
// get a specific key, as string, if no found returns just an empty string
|
||||
s := mySessions.Start(ctx)
|
||||
name := s.GetString("name")
|
||||
|
||||
ctx.Writef("The name on the /set was: %s", name)
|
||||
})
|
||||
|
||||
app.Get("/delete", func(ctx context.Context) {
|
||||
// delete a specific key
|
||||
s := mySessions.Start(ctx)
|
||||
s.Delete("name")
|
||||
})
|
||||
|
||||
app.Get("/clear", func(ctx context.Context) {
|
||||
// removes all entries
|
||||
mySessions.Start(ctx).Clear()
|
||||
})
|
||||
|
||||
app.Get("/destroy", func(ctx context.Context) {
|
||||
//destroy, removes the entire session data and cookie
|
||||
mySessions.Destroy(ctx)
|
||||
}) // Note about destroy:
|
||||
//
|
||||
// You can destroy a session outside of a handler too, using the:
|
||||
// mySessions.DestroyByID
|
||||
// mySessions.DestroyAll
|
||||
|
||||
app.Run(iris.Addr(":8080"))
|
||||
}
|
||||
105
sessions/_examples/securecookie/main_test.go
Normal file
105
sessions/_examples/securecookie/main_test.go
Normal file
@@ -0,0 +1,105 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/kataras/iris"
|
||||
"github.com/kataras/iris/context"
|
||||
"github.com/kataras/iris/httptest"
|
||||
"github.com/kataras/iris/sessions"
|
||||
|
||||
"github.com/gorilla/securecookie"
|
||||
)
|
||||
|
||||
func TestSessionsEncodeDecode(t *testing.T) {
|
||||
// test the sessions encode decode via gorilla.securecookie
|
||||
app := iris.New()
|
||||
// IMPORTANT
|
||||
cookieName := "mycustomsessionid"
|
||||
// AES only supports key sizes of 16, 24 or 32 bytes.
|
||||
// You either need to provide exactly that amount or you derive the key from what you type in.
|
||||
hashKey := []byte("the-big-and-secret-fash-key-here")
|
||||
blockKey := []byte("lot-secret-of-characters-big-too")
|
||||
secureCookie := securecookie.New(hashKey, blockKey)
|
||||
sess := sessions.New(sessions.Config{
|
||||
Cookie: cookieName,
|
||||
Encode: secureCookie.Encode,
|
||||
Decode: secureCookie.Decode,
|
||||
})
|
||||
|
||||
testSessions(t, sess, app)
|
||||
}
|
||||
|
||||
func testSessions(t *testing.T, sess *sessions.Sessions, app *iris.Application) {
|
||||
values := map[string]interface{}{
|
||||
"Name": "iris",
|
||||
"Months": "4",
|
||||
"Secret": "dsads£2132215£%%Ssdsa",
|
||||
}
|
||||
|
||||
writeValues := func(ctx context.Context) {
|
||||
s := sess.Start(ctx)
|
||||
sessValues := s.GetAll()
|
||||
|
||||
ctx.JSON(sessValues)
|
||||
}
|
||||
|
||||
app.Post("/set", func(ctx context.Context) {
|
||||
s := sess.Start(ctx)
|
||||
vals := make(map[string]interface{}, 0)
|
||||
if err := ctx.ReadJSON(&vals); err != nil {
|
||||
t.Fatalf("Cannot readjson. Trace %s", err.Error())
|
||||
}
|
||||
for k, v := range vals {
|
||||
s.Set(k, v)
|
||||
}
|
||||
})
|
||||
|
||||
app.Get("/get", func(ctx context.Context) {
|
||||
writeValues(ctx)
|
||||
})
|
||||
|
||||
app.Get("/clear", func(ctx context.Context) {
|
||||
sess.Start(ctx).Clear()
|
||||
writeValues(ctx)
|
||||
})
|
||||
|
||||
app.Get("/destroy", func(ctx context.Context) {
|
||||
sess.Destroy(ctx)
|
||||
writeValues(ctx)
|
||||
// the cookie and all values should be empty
|
||||
})
|
||||
|
||||
// request cookie should be empty
|
||||
app.Get("/after_destroy", func(ctx context.Context) {
|
||||
})
|
||||
|
||||
app.Get("/multi_start_set_get", func(ctx context.Context) {
|
||||
s := sess.Start(ctx)
|
||||
s.Set("key", "value")
|
||||
ctx.Next()
|
||||
}, func(ctx context.Context) {
|
||||
s := sess.Start(ctx)
|
||||
ctx.Writef(s.GetString("key"))
|
||||
})
|
||||
|
||||
e := httptest.New(t, app, httptest.URL("http://example.com"))
|
||||
|
||||
e.POST("/set").WithJSON(values).Expect().Status(iris.StatusOK).Cookies().NotEmpty()
|
||||
e.GET("/get").Expect().Status(iris.StatusOK).JSON().Object().Equal(values)
|
||||
|
||||
// test destroy which also clears first
|
||||
d := e.GET("/destroy").Expect().Status(iris.StatusOK)
|
||||
d.JSON().Object().Empty()
|
||||
// This removed: d.Cookies().Empty(). Reason:
|
||||
// httpexpect counts the cookies setted or deleted at the response time, but cookie is not removed, to be really removed needs to SetExpire(now-1second) so,
|
||||
// test if the cookies removed on the next request, like the browser's behavior.
|
||||
e.GET("/after_destroy").Expect().Status(iris.StatusOK).Cookies().Empty()
|
||||
// set and clear again
|
||||
e.POST("/set").WithJSON(values).Expect().Status(iris.StatusOK).Cookies().NotEmpty()
|
||||
e.GET("/clear").Expect().Status(iris.StatusOK).JSON().Object().Empty()
|
||||
|
||||
// test start on the same request but more than one times
|
||||
|
||||
e.GET("/multi_start_set_get").Expect().Status(iris.StatusOK).Body().Equal("value")
|
||||
}
|
||||
120
sessions/_examples/standalone/main.go
Normal file
120
sessions/_examples/standalone/main.go
Normal file
@@ -0,0 +1,120 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"github.com/kataras/iris"
|
||||
"github.com/kataras/iris/context"
|
||||
|
||||
"github.com/kataras/iris/sessions"
|
||||
)
|
||||
|
||||
type businessModel struct {
|
||||
Name string
|
||||
}
|
||||
|
||||
func main() {
|
||||
app := iris.New()
|
||||
sess := sessions.New(sessions.Config{
|
||||
// Cookie string, the session's client cookie name, for example: "mysessionid"
|
||||
//
|
||||
// Defaults to "irissessionid"
|
||||
Cookie: "mysessionid",
|
||||
// it's time.Duration, from the time cookie is created, how long it can be alive?
|
||||
// 0 means no expire.
|
||||
// -1 means expire when browser closes
|
||||
// or set a value, like 2 hours:
|
||||
Expires: time.Hour * 2,
|
||||
// if you want to invalid cookies on different subdomains
|
||||
// of the same host, then enable it
|
||||
DisableSubdomainPersistence: false,
|
||||
// want to be crazy safe? Take a look at the "securecookie" example folder.
|
||||
})
|
||||
|
||||
app.Get("/", func(ctx context.Context) {
|
||||
ctx.Writef("You should navigate to the /set, /get, /delete, /clear,/destroy instead")
|
||||
})
|
||||
app.Get("/set", func(ctx context.Context) {
|
||||
|
||||
//set session values.
|
||||
s := sess.Start(ctx)
|
||||
s.Set("name", "iris")
|
||||
|
||||
//test if setted here
|
||||
ctx.Writef("All ok session setted to: %s", s.GetString("name"))
|
||||
|
||||
// Set will set the value as-it-is,
|
||||
// if it's a slice or map
|
||||
// you will be able to change it on .Get directly!
|
||||
// Keep note that I don't recommend saving big data neither slices or maps on a session
|
||||
// but if you really need it then use the `SetImmutable` instead of `Set`.
|
||||
// Use `SetImmutable` consistently, it's slower.
|
||||
// Read more about muttable and immutable go types: https://stackoverflow.com/a/8021081
|
||||
})
|
||||
|
||||
app.Get("/get", func(ctx context.Context) {
|
||||
// get a specific value, as string, if no found returns just an empty string
|
||||
name := sess.Start(ctx).GetString("name")
|
||||
|
||||
ctx.Writef("The name on the /set was: %s", name)
|
||||
})
|
||||
|
||||
app.Get("/delete", func(ctx context.Context) {
|
||||
// delete a specific key
|
||||
sess.Start(ctx).Delete("name")
|
||||
})
|
||||
|
||||
app.Get("/clear", func(ctx context.Context) {
|
||||
// removes all entries
|
||||
sess.Start(ctx).Clear()
|
||||
})
|
||||
|
||||
app.Get("/destroy", func(ctx context.Context) {
|
||||
|
||||
//destroy, removes the entire session data and cookie
|
||||
sess.Destroy(ctx)
|
||||
})
|
||||
// Note about Destroy:
|
||||
//
|
||||
// You can destroy a session outside of a handler too, using the:
|
||||
// mySessions.DestroyByID
|
||||
// mySessions.DestroyAll
|
||||
|
||||
// remember: slices and maps are muttable by-design
|
||||
// The `SetImmutable` makes sure that they will be stored and received
|
||||
// as immutable, so you can't change them directly by mistake.
|
||||
//
|
||||
// Use `SetImmutable` consistently, it's slower than `Set`.
|
||||
// Read more about muttable and immutable go types: https://stackoverflow.com/a/8021081
|
||||
app.Get("/set_immutable", func(ctx context.Context) {
|
||||
business := []businessModel{{Name: "Edward"}, {Name: "value 2"}}
|
||||
s := sess.Start(ctx)
|
||||
s.SetImmutable("businessEdit", business)
|
||||
businessGet := s.Get("businessEdit").([]businessModel)
|
||||
|
||||
// try to change it, if we used `Set` instead of `SetImmutable` this
|
||||
// change will affect the underline array of the session's value "businessEdit", but now it will not.
|
||||
businessGet[0].Name = "Gabriel"
|
||||
|
||||
})
|
||||
|
||||
app.Get("/get_immutable", func(ctx context.Context) {
|
||||
valSlice := sess.Start(ctx).Get("businessEdit")
|
||||
if valSlice == nil {
|
||||
ctx.HTML("please navigate to the <a href='/set_immutable'>/set_immutable</a> first")
|
||||
return
|
||||
}
|
||||
|
||||
firstModel := valSlice.([]businessModel)[0]
|
||||
// businessGet[0].Name is equal to Edward initially
|
||||
if firstModel.Name != "Edward" {
|
||||
panic("Report this as a bug, immutable data cannot be changed from the caller without re-SetImmutable")
|
||||
}
|
||||
|
||||
ctx.Writef("[]businessModel[0].Name remains: %s", firstModel.Name)
|
||||
|
||||
// the name should remains "Edward"
|
||||
})
|
||||
|
||||
app.Run(iris.Addr(":8080"))
|
||||
}
|
||||
@@ -1,7 +1,3 @@
|
||||
// Copyright 2017 Gerasimos Maropoulos, ΓΜ. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package sessions
|
||||
|
||||
import (
|
||||
@@ -13,8 +9,6 @@ import (
|
||||
const (
|
||||
// DefaultCookieName the secret cookie's name for sessions
|
||||
DefaultCookieName = "irissessionid"
|
||||
// DefaultCookieLength is the default Session Manager's CookieLength, which is 32
|
||||
DefaultCookieLength = 32
|
||||
)
|
||||
|
||||
type (
|
||||
@@ -73,7 +67,7 @@ type (
|
||||
// that, but developers can change that with simple assignment.
|
||||
SessionIDGenerator func() string
|
||||
|
||||
// DisableSubdomainPersistence set it to true in order dissallow your q subdomains to have access to the session cookie
|
||||
// DisableSubdomainPersistence set it to true in order dissallow your subdomains to have access to the session cookie
|
||||
//
|
||||
// Defaults to false
|
||||
DisableSubdomainPersistence bool
|
||||
|
||||
@@ -1,7 +1,3 @@
|
||||
// Copyright 2017 Gerasimos Maropoulos, ΓΜ. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package sessions
|
||||
|
||||
import (
|
||||
@@ -9,6 +5,8 @@ import (
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/kataras/iris/context"
|
||||
)
|
||||
|
||||
var (
|
||||
@@ -21,24 +19,27 @@ var (
|
||||
|
||||
// GetCookie returns cookie's value by it's name
|
||||
// returns empty string if nothing was found
|
||||
func GetCookie(name string, req *http.Request) string {
|
||||
c, err := req.Cookie(name)
|
||||
func GetCookie(ctx context.Context, name string) string {
|
||||
c, err := ctx.Request().Cookie(name)
|
||||
if err != nil {
|
||||
return ""
|
||||
}
|
||||
|
||||
return c.Value
|
||||
|
||||
// return ctx.GetCookie(name)
|
||||
}
|
||||
|
||||
// AddCookie adds a cookie
|
||||
func AddCookie(cookie *http.Cookie, res http.ResponseWriter) {
|
||||
if v := cookie.String(); v != "" {
|
||||
http.SetCookie(res, cookie)
|
||||
}
|
||||
func AddCookie(ctx context.Context, cookie *http.Cookie) {
|
||||
// http.SetCookie(ctx.ResponseWriter(), cookie)
|
||||
// ctx.Request().AddCookie(cookie)
|
||||
ctx.SetCookie(cookie)
|
||||
}
|
||||
|
||||
// RemoveCookie deletes a cookie by it's name/key
|
||||
func RemoveCookie(name string, res http.ResponseWriter, req *http.Request) {
|
||||
c, err := req.Cookie(name)
|
||||
func RemoveCookie(ctx context.Context, name string) {
|
||||
c, err := ctx.Request().Cookie(name)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
@@ -48,7 +49,7 @@ func RemoveCookie(name string, res http.ResponseWriter, req *http.Request) {
|
||||
c.MaxAge = -1
|
||||
c.Value = ""
|
||||
c.Path = "/"
|
||||
AddCookie(c, res)
|
||||
AddCookie(ctx, c)
|
||||
}
|
||||
|
||||
// IsValidCookieDomain returns true if the receiver is a valid domain to set
|
||||
|
||||
@@ -1,7 +1,3 @@
|
||||
// Copyright 2017 Gerasimos Maropoulos, ΓΜ. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package sessions
|
||||
|
||||
// Database is the interface which all session databases should implement
|
||||
@@ -10,7 +6,7 @@ package sessions
|
||||
// The scope of the database is to session somewhere the sessions in order to
|
||||
// keep them after restarting the server, nothing more.
|
||||
// the values are sessiond by the underline session, the check for new sessions, or
|
||||
// 'this session value should added' are made automatically by q, you are able just to set the values to your backend database with Load function.
|
||||
// 'this session value should added' are made automatically you are able just to set the values to your backend database with Load function.
|
||||
// session database doesn't have any write or read access to the session, the loading of
|
||||
// the initial data is done by the Load(string) map[string]interfface{} function
|
||||
// synchronization are made automatically, you can register more than one session database
|
||||
|
||||
@@ -1,7 +1,3 @@
|
||||
// Copyright 2017 Gerasimos Maropoulos, ΓΜ. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package sessions
|
||||
|
||||
import (
|
||||
@@ -16,10 +12,10 @@ type (
|
||||
// It's the session memory manager
|
||||
provider struct {
|
||||
// we don't use RWMutex because all actions have read and write at the same action function.
|
||||
// (or write to a *session's value which is race if we don't lock)
|
||||
// (or write to a *Session's value which is race if we don't lock)
|
||||
// narrow locks are fasters but are useless here.
|
||||
mu sync.Mutex
|
||||
sessions map[string]*session
|
||||
sessions map[string]*Session
|
||||
databases []Database
|
||||
}
|
||||
)
|
||||
@@ -27,7 +23,7 @@ type (
|
||||
// newProvider returns a new sessions provider
|
||||
func newProvider() *provider {
|
||||
return &provider{
|
||||
sessions: make(map[string]*session, 0),
|
||||
sessions: make(map[string]*Session, 0),
|
||||
databases: make([]Database, 0),
|
||||
}
|
||||
}
|
||||
@@ -41,9 +37,8 @@ func (p *provider) RegisterDatabase(db Database) {
|
||||
}
|
||||
|
||||
// newSession returns a new session from sessionid
|
||||
func (p *provider) newSession(sid string, expires time.Duration) *session {
|
||||
|
||||
sess := &session{
|
||||
func (p *provider) newSession(sid string, expires time.Duration) *Session {
|
||||
sess := &Session{
|
||||
sid: sid,
|
||||
provider: p,
|
||||
values: p.loadSessionValuesFromDB(sid),
|
||||
@@ -92,7 +87,7 @@ func (p *provider) updateDatabases(sid string, store memstore.Store) {
|
||||
}
|
||||
|
||||
// Init creates the session and returns it
|
||||
func (p *provider) Init(sid string, expires time.Duration) Session {
|
||||
func (p *provider) Init(sid string, expires time.Duration) *Session {
|
||||
newSession := p.newSession(sid, expires)
|
||||
p.mu.Lock()
|
||||
p.sessions[sid] = newSession
|
||||
@@ -101,7 +96,7 @@ func (p *provider) Init(sid string, expires time.Duration) Session {
|
||||
}
|
||||
|
||||
// Read returns the store which sid parameter belongs
|
||||
func (p *provider) Read(sid string, expires time.Duration) Session {
|
||||
func (p *provider) Read(sid string, expires time.Duration) *Session {
|
||||
p.mu.Lock()
|
||||
if sess, found := p.sessions[sid]; found {
|
||||
sess.runFlashGC() // run the flash messages GC, new request here of existing session
|
||||
|
||||
@@ -1,7 +1,3 @@
|
||||
// Copyright 2017 Gerasimos Maropoulos, ΓΜ. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package sessions
|
||||
|
||||
import (
|
||||
@@ -14,10 +10,12 @@ import (
|
||||
)
|
||||
|
||||
type (
|
||||
|
||||
// session is an 'object' which wraps the session provider with its session databases, only frontend user has access to this session object.
|
||||
// implements the context.Session interface
|
||||
session struct {
|
||||
// Session should expose the Sessions's end-user API.
|
||||
// It is the session's storage controller which you can
|
||||
// save or retrieve values based on a key.
|
||||
//
|
||||
// This is what will be returned when sess := sessions.Start().
|
||||
Session struct {
|
||||
sid string
|
||||
values memstore.Store // here are the real values
|
||||
// we could set the flash messages inside values but this will bring us more problems
|
||||
@@ -39,15 +37,13 @@ type (
|
||||
}
|
||||
)
|
||||
|
||||
var _ Session = &session{}
|
||||
|
||||
// ID returns the session's id
|
||||
func (s *session) ID() string {
|
||||
// ID returns the session's ID.
|
||||
func (s *Session) ID() string {
|
||||
return s.sid
|
||||
}
|
||||
|
||||
// Get returns a value based on its "key".
|
||||
func (s *session) Get(key string) interface{} {
|
||||
func (s *Session) Get(key string) interface{} {
|
||||
s.mu.RLock()
|
||||
value := s.values.Get(key)
|
||||
s.mu.RUnlock()
|
||||
@@ -55,8 +51,8 @@ func (s *session) Get(key string) interface{} {
|
||||
return value
|
||||
}
|
||||
|
||||
// when running on the session manager removes any 'old' flash messages
|
||||
func (s *session) runFlashGC() {
|
||||
// when running on the session manager removes any 'old' flash messages.
|
||||
func (s *Session) runFlashGC() {
|
||||
s.mu.Lock()
|
||||
for key, v := range s.flashes {
|
||||
if v.shouldRemove {
|
||||
@@ -67,7 +63,7 @@ func (s *session) runFlashGC() {
|
||||
}
|
||||
|
||||
// HasFlash returns true if this session has available flash messages.
|
||||
func (s *session) HasFlash() bool {
|
||||
func (s *Session) HasFlash() bool {
|
||||
return len(s.flashes) > 0
|
||||
}
|
||||
|
||||
@@ -80,7 +76,7 @@ func (s *session) HasFlash() bool {
|
||||
//
|
||||
// Fetching a message deletes it from the session.
|
||||
// This means that a message is meant to be displayed only on the first page served to the user.
|
||||
func (s *session) GetFlash(key string) interface{} {
|
||||
func (s *Session) GetFlash(key string) interface{} {
|
||||
fv, ok := s.peekFlashMessage(key)
|
||||
if !ok {
|
||||
return nil
|
||||
@@ -92,7 +88,7 @@ func (s *session) GetFlash(key string) interface{} {
|
||||
// PeekFlash returns a stored flash message based on its "key".
|
||||
// Unlike GetFlash, this will keep the message valid for the next requests,
|
||||
// until GetFlashes or GetFlash("key").
|
||||
func (s *session) PeekFlash(key string) interface{} {
|
||||
func (s *Session) PeekFlash(key string) interface{} {
|
||||
fv, ok := s.peekFlashMessage(key)
|
||||
if !ok {
|
||||
return nil
|
||||
@@ -100,7 +96,7 @@ func (s *session) PeekFlash(key string) interface{} {
|
||||
return fv.value
|
||||
}
|
||||
|
||||
func (s *session) peekFlashMessage(key string) (*flashMessage, bool) {
|
||||
func (s *Session) peekFlashMessage(key string) (*flashMessage, bool) {
|
||||
s.mu.Lock()
|
||||
if fv, found := s.flashes[key]; found {
|
||||
return fv, true
|
||||
@@ -110,8 +106,8 @@ func (s *session) peekFlashMessage(key string) (*flashMessage, bool) {
|
||||
return nil, false
|
||||
}
|
||||
|
||||
// GetString same as Get but returns as string, if nil then returns an empty string
|
||||
func (s *session) GetString(key string) string {
|
||||
// GetString same as Get but returns as string, if nil then returns an empty string.
|
||||
func (s *Session) GetString(key string) string {
|
||||
if value := s.Get(key); value != nil {
|
||||
if v, ok := value.(string); ok {
|
||||
return v
|
||||
@@ -121,8 +117,8 @@ func (s *session) GetString(key string) string {
|
||||
return ""
|
||||
}
|
||||
|
||||
// GetFlashString same as GetFlash but returns as string, if nil then returns an empty string
|
||||
func (s *session) GetFlashString(key string) string {
|
||||
// GetFlashString same as GetFlash but returns as string, if nil then returns an empty string.
|
||||
func (s *Session) GetFlashString(key string) string {
|
||||
if value := s.GetFlash(key); value != nil {
|
||||
if v, ok := value.(string); ok {
|
||||
return v
|
||||
@@ -134,8 +130,8 @@ func (s *session) GetFlashString(key string) string {
|
||||
|
||||
var errFindParse = errors.New("Unable to find the %s with key: %s. Found? %#v")
|
||||
|
||||
// GetInt same as Get but returns as int, if not found then returns -1 and an error
|
||||
func (s *session) GetInt(key string) (int, error) {
|
||||
// GetInt same as Get but returns as int, if not found then returns -1 and an error.
|
||||
func (s *Session) GetInt(key string) (int, error) {
|
||||
v := s.Get(key)
|
||||
|
||||
if vint, ok := v.(int); ok {
|
||||
@@ -149,8 +145,8 @@ func (s *session) GetInt(key string) (int, error) {
|
||||
return -1, errFindParse.Format("int", key, v)
|
||||
}
|
||||
|
||||
// GetInt64 same as Get but returns as int64, if not found then returns -1 and an error
|
||||
func (s *session) GetInt64(key string) (int64, error) {
|
||||
// GetInt64 same as Get but returns as int64, if not found then returns -1 and an error.
|
||||
func (s *Session) GetInt64(key string) (int64, error) {
|
||||
v := s.Get(key)
|
||||
|
||||
if vint64, ok := v.(int64); ok {
|
||||
@@ -169,8 +165,8 @@ func (s *session) GetInt64(key string) (int64, error) {
|
||||
|
||||
}
|
||||
|
||||
// GetFloat32 same as Get but returns as float32, if not found then returns -1 and an error
|
||||
func (s *session) GetFloat32(key string) (float32, error) {
|
||||
// GetFloat32 same as Get but returns as float32, if not found then returns -1 and an error.
|
||||
func (s *Session) GetFloat32(key string) (float32, error) {
|
||||
v := s.Get(key)
|
||||
|
||||
if vfloat32, ok := v.(float32); ok {
|
||||
@@ -196,8 +192,8 @@ func (s *session) GetFloat32(key string) (float32, error) {
|
||||
return -1, errFindParse.Format("float32", key, v)
|
||||
}
|
||||
|
||||
// GetFloat64 same as Get but returns as float64, if not found then returns -1 and an error
|
||||
func (s *session) GetFloat64(key string) (float64, error) {
|
||||
// GetFloat64 same as Get but returns as float64, if not found then returns -1 and an error.
|
||||
func (s *Session) GetFloat64(key string) (float64, error) {
|
||||
v := s.Get(key)
|
||||
|
||||
if vfloat32, ok := v.(float32); ok {
|
||||
@@ -220,11 +216,11 @@ func (s *session) GetFloat64(key string) (float64, error) {
|
||||
}
|
||||
|
||||
// GetBoolean same as Get but returns as boolean, if not found then returns -1 and an error
|
||||
func (s *session) GetBoolean(key string) (bool, error) {
|
||||
func (s *Session) GetBoolean(key string) (bool, error) {
|
||||
v := s.Get(key)
|
||||
// here we could check for "true", "false" and 0 for false and 1 for true
|
||||
// but this may cause unexpected behavior from the developer if they expecting an error
|
||||
// so we just check if bool, if yes then return that bool, otherwise return false and an error
|
||||
// so we just check if bool, if yes then return that bool, otherwise return false and an error.
|
||||
if vb, ok := v.(bool); ok {
|
||||
return vb, nil
|
||||
}
|
||||
@@ -232,8 +228,8 @@ func (s *session) GetBoolean(key string) (bool, error) {
|
||||
return false, errFindParse.Format("bool", key, v)
|
||||
}
|
||||
|
||||
// GetAll returns a copy of all session's values
|
||||
func (s *session) GetAll() map[string]interface{} {
|
||||
// GetAll returns a copy of all session's values.
|
||||
func (s *Session) GetAll() map[string]interface{} {
|
||||
items := make(map[string]interface{}, len(s.values))
|
||||
s.mu.RLock()
|
||||
for _, kv := range s.values {
|
||||
@@ -244,8 +240,8 @@ func (s *session) GetAll() map[string]interface{} {
|
||||
}
|
||||
|
||||
// GetFlashes returns all flash messages as map[string](key) and interface{} value
|
||||
// NOTE: this will cause at remove all current flash messages on the next request of the same user
|
||||
func (s *session) GetFlashes() map[string]interface{} {
|
||||
// NOTE: this will cause at remove all current flash messages on the next request of the same user.
|
||||
func (s *Session) GetFlashes() map[string]interface{} {
|
||||
flashes := make(map[string]interface{}, len(s.flashes))
|
||||
s.mu.Lock()
|
||||
for key, v := range s.flashes {
|
||||
@@ -257,11 +253,11 @@ func (s *session) GetFlashes() map[string]interface{} {
|
||||
}
|
||||
|
||||
// VisitAll loop each one entry and calls the callback function func(key,value)
|
||||
func (s *session) VisitAll(cb func(k string, v interface{})) {
|
||||
func (s *Session) VisitAll(cb func(k string, v interface{})) {
|
||||
s.values.Visit(cb)
|
||||
}
|
||||
|
||||
func (s *session) set(key string, value interface{}, immutable bool) {
|
||||
func (s *Session) set(key string, value interface{}, immutable bool) {
|
||||
s.mu.Lock()
|
||||
if immutable {
|
||||
s.values.SetImmutable(key, value)
|
||||
@@ -274,7 +270,7 @@ func (s *session) set(key string, value interface{}, immutable bool) {
|
||||
}
|
||||
|
||||
// Set fills the session with an entry"value", based on its "key".
|
||||
func (s *session) Set(key string, value interface{}) {
|
||||
func (s *Session) Set(key string, value interface{}) {
|
||||
s.set(key, value, false)
|
||||
}
|
||||
|
||||
@@ -284,7 +280,7 @@ func (s *session) Set(key string, value interface{}) {
|
||||
// if the entry was immutable, for your own safety.
|
||||
// Use it consistently, it's far slower than `Set`.
|
||||
// Read more about muttable and immutable go types: https://stackoverflow.com/a/8021081
|
||||
func (s *session) SetImmutable(key string, value interface{}) {
|
||||
func (s *Session) SetImmutable(key string, value interface{}) {
|
||||
s.set(key, value, true)
|
||||
}
|
||||
|
||||
@@ -307,8 +303,8 @@ func (s *session) SetImmutable(key string, value interface{}) {
|
||||
// SetFlash("success", "Data saved!");
|
||||
//
|
||||
// In this example we used the key 'success'.
|
||||
// If you want to define more than one flash messages, you will have to use different keys
|
||||
func (s *session) SetFlash(key string, value interface{}) {
|
||||
// If you want to define more than one flash messages, you will have to use different keys.
|
||||
func (s *Session) SetFlash(key string, value interface{}) {
|
||||
s.mu.Lock()
|
||||
s.flashes[key] = &flashMessage{value: value}
|
||||
s.mu.Unlock()
|
||||
@@ -316,7 +312,7 @@ func (s *session) SetFlash(key string, value interface{}) {
|
||||
|
||||
// Delete removes an entry by its key,
|
||||
// returns true if actually something was removed.
|
||||
func (s *session) Delete(key string) bool {
|
||||
func (s *Session) Delete(key string) bool {
|
||||
s.mu.Lock()
|
||||
removed := s.values.Remove(key)
|
||||
s.mu.Unlock()
|
||||
@@ -325,19 +321,19 @@ func (s *session) Delete(key string) bool {
|
||||
return removed
|
||||
}
|
||||
|
||||
func (s *session) updateDatabases() {
|
||||
func (s *Session) updateDatabases() {
|
||||
s.provider.updateDatabases(s.sid, s.values)
|
||||
}
|
||||
|
||||
// DeleteFlash removes a flash message by its key
|
||||
func (s *session) DeleteFlash(key string) {
|
||||
// DeleteFlash removes a flash message by its key.
|
||||
func (s *Session) DeleteFlash(key string) {
|
||||
s.mu.Lock()
|
||||
delete(s.flashes, key)
|
||||
s.mu.Unlock()
|
||||
}
|
||||
|
||||
// Clear removes all entries
|
||||
func (s *session) Clear() {
|
||||
// Clear removes all entries.
|
||||
func (s *Session) Clear() {
|
||||
s.mu.Lock()
|
||||
s.values.Reset()
|
||||
s.mu.Unlock()
|
||||
@@ -345,8 +341,8 @@ func (s *session) Clear() {
|
||||
s.updateDatabases()
|
||||
}
|
||||
|
||||
// Clear removes all flash messages
|
||||
func (s *session) ClearFlashes() {
|
||||
// ClearFlashes removes all flash messages.
|
||||
func (s *Session) ClearFlashes() {
|
||||
s.mu.Lock()
|
||||
for key := range s.flashes {
|
||||
delete(s.flashes, key)
|
||||
|
||||
@@ -1,29 +0,0 @@
|
||||
## Session databases
|
||||
|
||||
Find more databases at [github.com/kataras/go-sessions/sessiondb](https://github.com/kataras/go-sessions/tree/master/sessiondb).
|
||||
|
||||
This folder contains only the redis database because the rest (two so far, 'file' and 'leveldb') were created by the Community.
|
||||
So go [there](https://github.com/kataras/go-sessions/tree/master/sessiondb) and find more about them. `Database` is just an
|
||||
interface so you're able to `UseDatabase(anyCompatibleDatabase)`. A Database should implement two functions, `Load` and `Update`.
|
||||
|
||||
**Database interface**
|
||||
|
||||
```go
|
||||
type Database interface {
|
||||
Load(string) map[string]interface{}
|
||||
Update(string, map[string]interface{})
|
||||
}
|
||||
```
|
||||
|
||||
```go
|
||||
import (
|
||||
"...myDatabase"
|
||||
)
|
||||
s := New(...)
|
||||
s.UseDatabase(myDatabase) // <---
|
||||
|
||||
app := iris.New()
|
||||
app.Adapt(s)
|
||||
|
||||
app.Listen(":8080")
|
||||
```
|
||||
@@ -1,7 +1,3 @@
|
||||
// Copyright 2017 Gerasimos Maropoulos. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package redis
|
||||
|
||||
import (
|
||||
@@ -11,22 +7,22 @@ import (
|
||||
"github.com/kataras/iris/sessions/sessiondb/redis/service"
|
||||
)
|
||||
|
||||
// Database the redis database for q sessions
|
||||
// Database the redis back-end session database for the sessions.
|
||||
type Database struct {
|
||||
redis *service.Service
|
||||
}
|
||||
|
||||
// New returns a new redis database
|
||||
// New returns a new redis database.
|
||||
func New(cfg ...service.Config) *Database {
|
||||
return &Database{redis: service.New(cfg...)}
|
||||
}
|
||||
|
||||
// Config returns the configuration for the redis server bridge, you can change them
|
||||
// Config returns the configuration for the redis server bridge, you can change them.
|
||||
func (d *Database) Config() *service.Config {
|
||||
return d.redis.Config
|
||||
}
|
||||
|
||||
// Load loads the values to the underline
|
||||
// Load loads the values to the underline.
|
||||
func (d *Database) Load(sid string) map[string]interface{} {
|
||||
values := make(map[string]interface{})
|
||||
|
||||
|
||||
@@ -1,13 +1,7 @@
|
||||
// Copyright 2017 Gerasimos Maropoulos. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package service
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"github.com/imdario/mergo"
|
||||
)
|
||||
|
||||
const (
|
||||
@@ -43,7 +37,7 @@ type Config struct {
|
||||
MaxAgeSeconds int
|
||||
}
|
||||
|
||||
// DefaultConfig returns the default configuration for Redis service
|
||||
// DefaultConfig returns the default configuration for Redis service.
|
||||
func DefaultConfig() Config {
|
||||
return Config{
|
||||
Network: DefaultRedisNetwork,
|
||||
@@ -57,26 +51,3 @@ func DefaultConfig() Config {
|
||||
MaxAgeSeconds: DefaultRedisMaxAgeSeconds,
|
||||
}
|
||||
}
|
||||
|
||||
// Merge merges the default with the given config and returns the result
|
||||
func (c Config) Merge(cfg []Config) (config Config) {
|
||||
|
||||
if len(cfg) > 0 {
|
||||
config = cfg[0]
|
||||
mergo.Merge(&config, c)
|
||||
} else {
|
||||
_default := c
|
||||
config = _default
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// MergeSingle merges the default with the given config and returns the result
|
||||
func (c Config) MergeSingle(cfg Config) (config Config) {
|
||||
|
||||
config = cfg
|
||||
mergo.Merge(&config, c)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
@@ -1,7 +1,3 @@
|
||||
// Copyright 2017 Gerasimos Maropoulos. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package service
|
||||
|
||||
import (
|
||||
@@ -268,9 +264,12 @@ func (r *Service) Connect() {
|
||||
}
|
||||
|
||||
// New returns a Redis service filled by the passed config
|
||||
// to connect call the .Connect()
|
||||
// to connect call the .Connect().
|
||||
func New(cfg ...Config) *Service {
|
||||
c := DefaultConfig().Merge(cfg)
|
||||
c := DefaultConfig()
|
||||
if len(cfg) > 0 {
|
||||
c = cfg[0]
|
||||
}
|
||||
r := &Service{pool: &redis.Pool{}, Config: &c}
|
||||
return r
|
||||
}
|
||||
|
||||
@@ -1,94 +1,48 @@
|
||||
// Copyright 2017 Gerasimos Maropoulos, ΓΜ. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package sessions
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/kataras/iris/context"
|
||||
)
|
||||
|
||||
type (
|
||||
|
||||
// Sessions must be implemented within a session manager.
|
||||
//
|
||||
// A Sessions should be responsible to Start a sesion based
|
||||
// on raw http.ResponseWriter and http.Request, which should return
|
||||
// a compatible Session interface, type. If the external session manager
|
||||
// doesn't qualifies, then the user should code the rest of the functions with empty implementation.
|
||||
//
|
||||
// Sessions should be responsible to Destroy a session based
|
||||
// on the http.ResponseWriter and http.Request, this function should works individually.
|
||||
Sessions interface {
|
||||
// Start should start the session for the particular net/http request.
|
||||
Start(http.ResponseWriter, *http.Request) Session
|
||||
|
||||
// Destroy should kills the net/http session and remove the associated cookie.
|
||||
Destroy(http.ResponseWriter, *http.Request)
|
||||
} // Sessions is being implemented by Manager
|
||||
|
||||
// Session should expose the Sessions's end-user API.
|
||||
// This will be returned at the sess := context.Session().
|
||||
Session interface {
|
||||
ID() string
|
||||
Get(string) interface{}
|
||||
HasFlash() bool
|
||||
GetFlash(string) interface{}
|
||||
GetString(key string) string
|
||||
GetFlashString(string) string
|
||||
GetInt(key string) (int, error)
|
||||
GetInt64(key string) (int64, error)
|
||||
GetFloat32(key string) (float32, error)
|
||||
GetFloat64(key string) (float64, error)
|
||||
GetBoolean(key string) (bool, error)
|
||||
GetAll() map[string]interface{}
|
||||
GetFlashes() map[string]interface{}
|
||||
VisitAll(cb func(k string, v interface{}))
|
||||
Set(string, interface{})
|
||||
SetImmutable(key string, value interface{})
|
||||
SetFlash(string, interface{})
|
||||
Delete(string) bool
|
||||
DeleteFlash(string)
|
||||
Clear()
|
||||
ClearFlashes()
|
||||
} // Session is being implemented inside session.go
|
||||
|
||||
// Manager implements the Sessions interface which Iris uses to start and destroy a session from its Context.
|
||||
Manager struct {
|
||||
config Config
|
||||
provider *provider
|
||||
}
|
||||
)
|
||||
// A Sessions manager should be responsible to Start a sesion, based
|
||||
// on a Context, which should return
|
||||
// a compatible Session interface, type. If the external session manager
|
||||
// doesn't qualifies, then the user should code the rest of the functions with empty implementation.
|
||||
//
|
||||
// Sessions should be responsible to Destroy a session based
|
||||
// on the Context.
|
||||
type Sessions struct {
|
||||
config Config
|
||||
provider *provider
|
||||
}
|
||||
|
||||
// New returns a new fast, feature-rich sessions manager
|
||||
// it can be adapted to an Iris station
|
||||
func New(cfg Config) *Manager {
|
||||
return &Manager{
|
||||
// it can be adapted to an iris station
|
||||
func New(cfg Config) *Sessions {
|
||||
return &Sessions{
|
||||
config: cfg.Validate(),
|
||||
provider: newProvider(),
|
||||
}
|
||||
}
|
||||
|
||||
var _ Sessions = &Manager{}
|
||||
|
||||
// UseDatabase adds a session database to the manager's provider,
|
||||
// a session db doesn't have write access
|
||||
func (s *Manager) UseDatabase(db Database) {
|
||||
func (s *Sessions) UseDatabase(db Database) {
|
||||
s.provider.RegisterDatabase(db)
|
||||
}
|
||||
|
||||
// Start starts the session for the particular net/http request
|
||||
func (s *Manager) Start(res http.ResponseWriter, req *http.Request) Session {
|
||||
var sess Session
|
||||
|
||||
cookieValue := GetCookie(s.config.Cookie, req)
|
||||
// Start should start the session for the particular request.
|
||||
func (s *Sessions) Start(ctx context.Context) *Session {
|
||||
cookieValue := GetCookie(ctx, s.config.Cookie)
|
||||
|
||||
if cookieValue == "" { // cookie doesn't exists, let's generate a session and add set a cookie
|
||||
sid := s.config.SessionIDGenerator()
|
||||
|
||||
sess = s.provider.Init(sid, s.config.Expires)
|
||||
sess := s.provider.Init(sid, s.config.Expires)
|
||||
cookie := &http.Cookie{}
|
||||
|
||||
// The RFC makes no mention of encoding url value, so here I think to encode both sessionid key and the value using the safe(to put and to use as cookie) url-encoding
|
||||
@@ -98,7 +52,7 @@ func (s *Manager) Start(res http.ResponseWriter, req *http.Request) Session {
|
||||
cookie.Path = "/"
|
||||
if !s.config.DisableSubdomainPersistence {
|
||||
|
||||
requestDomain := req.URL.Host
|
||||
requestDomain := ctx.Request().URL.Host
|
||||
if portIdx := strings.IndexByte(requestDomain, ':'); portIdx > 0 {
|
||||
requestDomain = requestDomain[0:portIdx]
|
||||
}
|
||||
@@ -125,8 +79,8 @@ func (s *Manager) Start(res http.ResponseWriter, req *http.Request) Session {
|
||||
// finally set the .localhost.com (for(1-level) || .mysubdomain.localhost.com (for 2-level subdomain allow)
|
||||
cookie.Domain = "." + requestDomain // . to allow persistence
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
cookie.HttpOnly = true
|
||||
// MaxAge=0 means no 'Max-Age' attribute specified.
|
||||
// MaxAge<0 means delete cookie now, equivalently 'Max-Age: 0'
|
||||
@@ -142,33 +96,33 @@ func (s *Manager) Start(res http.ResponseWriter, req *http.Request) Session {
|
||||
|
||||
// set the cookie to secure if this is a tls wrapped request
|
||||
// and the configuration allows it.
|
||||
if req.TLS != nil && s.config.CookieSecureTLS {
|
||||
if ctx.Request().TLS != nil && s.config.CookieSecureTLS {
|
||||
cookie.Secure = true
|
||||
}
|
||||
|
||||
// encode the session id cookie client value right before send it.
|
||||
cookie.Value = s.encodeCookieValue(cookie.Value)
|
||||
AddCookie(ctx, cookie)
|
||||
|
||||
AddCookie(cookie, res)
|
||||
} else {
|
||||
|
||||
cookieValue = s.decodeCookieValue(cookieValue)
|
||||
|
||||
sess = s.provider.Read(cookieValue, s.config.Expires)
|
||||
return sess
|
||||
}
|
||||
|
||||
cookieValue = s.decodeCookieValue(cookieValue)
|
||||
sess := s.provider.Read(cookieValue, s.config.Expires)
|
||||
return sess
|
||||
|
||||
}
|
||||
|
||||
// Destroy remove the session data and remove the associated cookie.
|
||||
func (s *Manager) Destroy(res http.ResponseWriter, req *http.Request) {
|
||||
cookieValue := GetCookie(s.config.Cookie, req)
|
||||
func (s *Sessions) Destroy(ctx context.Context) {
|
||||
cookieValue := GetCookie(ctx, s.config.Cookie)
|
||||
// decode the client's cookie value in order to find the server's session id
|
||||
// to destroy the session data.
|
||||
cookieValue = s.decodeCookieValue(cookieValue)
|
||||
if cookieValue == "" { // nothing to destroy
|
||||
return
|
||||
}
|
||||
RemoveCookie(s.config.Cookie, res, req)
|
||||
RemoveCookie(ctx, s.config.Cookie)
|
||||
|
||||
s.provider.Destroy(cookieValue)
|
||||
}
|
||||
@@ -181,19 +135,19 @@ func (s *Manager) Destroy(res http.ResponseWriter, req *http.Request) {
|
||||
//
|
||||
// Note: the sid should be the original one (i.e: fetched by a store )
|
||||
// it's not decoded.
|
||||
func (s *Manager) DestroyByID(sid string) {
|
||||
func (s *Sessions) DestroyByID(sid string) {
|
||||
s.provider.Destroy(sid)
|
||||
}
|
||||
|
||||
// DestroyAll removes all sessions
|
||||
// from the server-side memory (and database if registered).
|
||||
// Client's session cookie will still exist but it will be reseted on the next request.
|
||||
func (s *Manager) DestroyAll() {
|
||||
func (s *Sessions) DestroyAll() {
|
||||
s.provider.DestroyAll()
|
||||
}
|
||||
|
||||
// let's keep these funcs simple, we can do it with two lines but we may add more things in the future.
|
||||
func (s *Manager) decodeCookieValue(cookieValue string) string {
|
||||
func (s *Sessions) decodeCookieValue(cookieValue string) string {
|
||||
var cookieValueDecoded *string
|
||||
if decode := s.config.Decode; decode != nil {
|
||||
err := decode(s.config.Cookie, cookieValue, &cookieValueDecoded)
|
||||
@@ -206,7 +160,7 @@ func (s *Manager) decodeCookieValue(cookieValue string) string {
|
||||
return cookieValue
|
||||
}
|
||||
|
||||
func (s *Manager) encodeCookieValue(cookieValue string) string {
|
||||
func (s *Sessions) encodeCookieValue(cookieValue string) string {
|
||||
if encode := s.config.Encode; encode != nil {
|
||||
newVal, err := encode(s.config.Cookie, cookieValue)
|
||||
if err == nil {
|
||||
|
||||
194
sessions/sessions_test.go
Normal file
194
sessions/sessions_test.go
Normal file
@@ -0,0 +1,194 @@
|
||||
package sessions_test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/kataras/iris"
|
||||
"github.com/kataras/iris/context"
|
||||
"github.com/kataras/iris/httptest"
|
||||
"github.com/kataras/iris/sessions"
|
||||
)
|
||||
|
||||
func TestSessions(t *testing.T) {
|
||||
app := iris.New()
|
||||
|
||||
sess := sessions.New(sessions.Config{Cookie: "mycustomsessionid"})
|
||||
testSessions(t, sess, app)
|
||||
}
|
||||
|
||||
const (
|
||||
testEnableSubdomain = false
|
||||
)
|
||||
|
||||
func testSessions(t *testing.T, sess *sessions.Sessions, app *iris.Application) {
|
||||
values := map[string]interface{}{
|
||||
"Name": "iris",
|
||||
"Months": "4",
|
||||
"Secret": "dsads£2132215£%%Ssdsa",
|
||||
}
|
||||
|
||||
writeValues := func(ctx context.Context) {
|
||||
s := sess.Start(ctx)
|
||||
sessValues := s.GetAll()
|
||||
|
||||
ctx.JSON(sessValues)
|
||||
}
|
||||
|
||||
if testEnableSubdomain {
|
||||
app.Party("subdomain.").Get("/get", func(ctx context.Context) {
|
||||
writeValues(ctx)
|
||||
})
|
||||
}
|
||||
|
||||
app.Post("/set", func(ctx context.Context) {
|
||||
s := sess.Start(ctx)
|
||||
vals := make(map[string]interface{}, 0)
|
||||
if err := ctx.ReadJSON(&vals); err != nil {
|
||||
t.Fatalf("Cannot readjson. Trace %s", err.Error())
|
||||
}
|
||||
for k, v := range vals {
|
||||
s.Set(k, v)
|
||||
}
|
||||
})
|
||||
|
||||
app.Get("/get", func(ctx context.Context) {
|
||||
writeValues(ctx)
|
||||
})
|
||||
|
||||
app.Get("/clear", func(ctx context.Context) {
|
||||
sess.Start(ctx).Clear()
|
||||
writeValues(ctx)
|
||||
})
|
||||
|
||||
app.Get("/destroy", func(ctx context.Context) {
|
||||
sess.Destroy(ctx)
|
||||
writeValues(ctx)
|
||||
// the cookie and all values should be empty
|
||||
})
|
||||
|
||||
// request cookie should be empty
|
||||
app.Get("/after_destroy", func(ctx context.Context) {
|
||||
})
|
||||
|
||||
app.Get("/multi_start_set_get", func(ctx context.Context) {
|
||||
s := sess.Start(ctx)
|
||||
s.Set("key", "value")
|
||||
ctx.Next()
|
||||
}, func(ctx context.Context) {
|
||||
s := sess.Start(ctx)
|
||||
ctx.Writef(s.GetString("key"))
|
||||
})
|
||||
|
||||
e := httptest.New(t, app, httptest.URL("http://example.com"))
|
||||
|
||||
e.POST("/set").WithJSON(values).Expect().Status(iris.StatusOK).Cookies().NotEmpty()
|
||||
e.GET("/get").Expect().Status(iris.StatusOK).JSON().Object().Equal(values)
|
||||
if testEnableSubdomain {
|
||||
es := httptest.New(t, app, httptest.URL("http://subdomain.example.com"))
|
||||
es.Request("GET", "/get").Expect().Status(iris.StatusOK).JSON().Object().Equal(values)
|
||||
}
|
||||
|
||||
// test destroy which also clears first
|
||||
d := e.GET("/destroy").Expect().Status(iris.StatusOK)
|
||||
d.JSON().Object().Empty()
|
||||
// This removed: d.Cookies().Empty(). Reason:
|
||||
// httpexpect counts the cookies setted or deleted at the response time, but cookie is not removed, to be really removed needs to SetExpire(now-1second) so,
|
||||
// test if the cookies removed on the next request, like the browser's behavior.
|
||||
e.GET("/after_destroy").Expect().Status(iris.StatusOK).Cookies().Empty()
|
||||
// set and clear again
|
||||
e.POST("/set").WithJSON(values).Expect().Status(iris.StatusOK).Cookies().NotEmpty()
|
||||
e.GET("/clear").Expect().Status(iris.StatusOK).JSON().Object().Empty()
|
||||
|
||||
// test start on the same request but more than one times
|
||||
|
||||
e.GET("/multi_start_set_get").Expect().Status(iris.StatusOK).Body().Equal("value")
|
||||
}
|
||||
|
||||
func TestFlashMessages(t *testing.T) {
|
||||
app := iris.New()
|
||||
|
||||
sess := sessions.New(sessions.Config{Cookie: "mycustomsessionid"})
|
||||
|
||||
valueSingleKey := "Name"
|
||||
valueSingleValue := "iris-sessions"
|
||||
|
||||
values := map[string]interface{}{
|
||||
valueSingleKey: valueSingleValue,
|
||||
"Days": "1",
|
||||
"Secret": "dsads£2132215£%%Ssdsa",
|
||||
}
|
||||
|
||||
writeValues := func(ctx context.Context, values map[string]interface{}) error {
|
||||
_, err := ctx.JSON(values)
|
||||
return err
|
||||
}
|
||||
|
||||
app.Post("/set", func(ctx context.Context) {
|
||||
vals := make(map[string]interface{}, 0)
|
||||
if err := ctx.ReadJSON(&vals); err != nil {
|
||||
t.Fatalf("Cannot readjson. Trace %s", err.Error())
|
||||
}
|
||||
s := sess.Start(ctx)
|
||||
for k, v := range vals {
|
||||
s.SetFlash(k, v)
|
||||
}
|
||||
|
||||
ctx.StatusCode(iris.StatusOK)
|
||||
})
|
||||
|
||||
writeFlashValues := func(ctx context.Context) {
|
||||
s := sess.Start(ctx)
|
||||
|
||||
flashes := s.GetFlashes()
|
||||
if err := writeValues(ctx, flashes); err != nil {
|
||||
t.Fatalf("While serialize the flash values: %s", err.Error())
|
||||
}
|
||||
}
|
||||
|
||||
app.Get("/get_single", func(ctx context.Context) {
|
||||
s := sess.Start(ctx)
|
||||
flashMsgString := s.GetFlashString(valueSingleKey)
|
||||
ctx.WriteString(flashMsgString)
|
||||
})
|
||||
|
||||
app.Get("/get", func(ctx context.Context) {
|
||||
writeFlashValues(ctx)
|
||||
})
|
||||
|
||||
app.Get("/clear", func(ctx context.Context) {
|
||||
s := sess.Start(ctx)
|
||||
s.ClearFlashes()
|
||||
writeFlashValues(ctx)
|
||||
})
|
||||
|
||||
app.Get("/destroy", func(ctx context.Context) {
|
||||
sess.Destroy(ctx)
|
||||
writeFlashValues(ctx)
|
||||
ctx.StatusCode(iris.StatusOK)
|
||||
// the cookie and all values should be empty
|
||||
})
|
||||
|
||||
// request cookie should be empty
|
||||
app.Get("/after_destroy", func(ctx context.Context) {
|
||||
ctx.StatusCode(iris.StatusOK)
|
||||
})
|
||||
|
||||
e := httptest.New(t, app, httptest.URL("http://example.com"))
|
||||
|
||||
e.POST("/set").WithJSON(values).Expect().Status(iris.StatusOK).Cookies().NotEmpty()
|
||||
// get all
|
||||
e.GET("/get").Expect().Status(iris.StatusOK).JSON().Object().Equal(values)
|
||||
// get the same flash on other request should return nothing because the flash message is removed after fetch once
|
||||
e.GET("/get").Expect().Status(iris.StatusOK).JSON().Object().Empty()
|
||||
// test destroy which also clears first
|
||||
d := e.GET("/destroy").Expect().Status(iris.StatusOK)
|
||||
d.JSON().Object().Empty()
|
||||
e.GET("/after_destroy").Expect().Status(iris.StatusOK).Cookies().Empty()
|
||||
// set and clear again
|
||||
e.POST("/set").WithJSON(values).Expect().Status(iris.StatusOK).Cookies().NotEmpty()
|
||||
e.GET("/clear").Expect().Status(iris.StatusOK).JSON().Object().Empty()
|
||||
|
||||
// set again in order to take the single one ( we don't test Cookies.NotEmpty because httpexpect default conf reads that from the request-only)
|
||||
e.POST("/set").WithJSON(values).Expect().Status(iris.StatusOK)
|
||||
e.GET("/get_single").Expect().Status(iris.StatusOK).Body().Equal(valueSingleValue)
|
||||
}
|
||||
Reference in New Issue
Block a user