This repository has been archived by the owner on Sep 21, 2022. It is now read-only.
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add session and flash message support
This add support on the *baseContext for sessions using gorilla/sessions package. Additionally a basic flash messages implementation is added which uses the session implementation.
- Loading branch information
Showing
8 changed files
with
363 additions
and
5 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,86 @@ | ||
package base | ||
|
||
import ( | ||
"io" | ||
"net/http" | ||
"net/http/httptest" | ||
"testing" | ||
|
||
"github.com/gernest/utron/config" | ||
"github.com/gorilla/mux" | ||
) | ||
|
||
type DummyView struct { | ||
} | ||
|
||
func (d *DummyView) Render(out io.Writer, name string, data interface{}) error { | ||
out.Write([]byte(name)) | ||
return nil | ||
} | ||
|
||
func TestContext(t *testing.T) { | ||
r := mux.NewRouter() | ||
name := "world" | ||
r.HandleFunc("/hello/{name}", testHandler(t, name)) | ||
req, _ := http.NewRequest("GET", "/hello/"+name, nil) | ||
w := httptest.NewRecorder() | ||
r.ServeHTTP(w, req) | ||
} | ||
|
||
func testHandler(t *testing.T, name string) func(http.ResponseWriter, *http.Request) { | ||
return func(w http.ResponseWriter, r *http.Request) { | ||
ctxHandler(t, name, w, r) | ||
} | ||
} | ||
|
||
func ctxHandler(t *testing.T, name string, w http.ResponseWriter, r *http.Request) { | ||
ctx := NewContext(w, r) | ||
ctx.Init() | ||
pname := ctx.Params["name"] | ||
if pname != name { | ||
t.Error("expected %s got %s", name, pname) | ||
} | ||
|
||
ctx.SetData("name", pname) | ||
|
||
data := ctx.GetData("name") | ||
if data == nil { | ||
t.Error("expected values to be stored in context") | ||
} | ||
ctx.JSON() | ||
h := w.Header().Get(Content.Type) | ||
if h != Content.Application.JSON { | ||
t.Errorf("expected %s got %s", Content.Application.JSON, h) | ||
} | ||
ctx.HTML() | ||
h = w.Header().Get(Content.Type) | ||
if h != Content.TextHTML { | ||
t.Errorf("expected %s got %s", Content.TextHTML, h) | ||
} | ||
ctx.TextPlain() | ||
h = w.Header().Get(Content.Type) | ||
if h != Content.TextPlain { | ||
t.Errorf("expected %s got %s", Content.TextPlain, h) | ||
} | ||
|
||
err := ctx.Commit() | ||
if err != nil { | ||
t.Error(err) | ||
} | ||
|
||
// make sure we can't commit twice | ||
err = ctx.Commit() | ||
if err == nil { | ||
t.Error("expected error") | ||
} | ||
|
||
// when there is template and view | ||
ctx.isCommited = false | ||
ctx.Template = pname | ||
ctx.Set(&DummyView{}) | ||
ctx.Cfg = &config.Config{} | ||
err = ctx.Commit() | ||
if err != nil { | ||
t.Error(err) | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,36 @@ | ||
package base | ||
|
||
import ( | ||
"errors" | ||
|
||
"github.com/gorilla/sessions" | ||
) | ||
|
||
var errNoStore = errors.New("no session store was found") | ||
|
||
//NewSession returns a new browser session whose key is set to name. This only | ||
//works when the *Context.SessionStore is not nil. | ||
// | ||
// The session returned is from grorilla/sessions package. | ||
func (ctx *Context) NewSession(name string) (*sessions.Session, error) { | ||
if ctx.SessionStore != nil { | ||
return ctx.SessionStore.New(ctx.Request(), name) | ||
} | ||
return nil, errNoStore | ||
} | ||
|
||
//GetSession retrieves session with a given name. | ||
func (ctx *Context) GetSession(name string) (*sessions.Session, error) { | ||
if ctx.SessionStore != nil { | ||
return ctx.SessionStore.New(ctx.Request(), name) | ||
} | ||
return nil, errNoStore | ||
} | ||
|
||
//SaveSession saves the given session. | ||
func (ctx *Context) SaveSession(s *sessions.Session) error { | ||
if ctx.SessionStore != nil { | ||
return ctx.SessionStore.Save(ctx.Request(), ctx.Response(), s) | ||
} | ||
return errNoStore | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,21 @@ | ||
package base | ||
|
||
import "testing" | ||
|
||
func TestContextSession(t *testing.T) { | ||
// should error when the session store is not set | ||
name := "sess" | ||
ctx := &Context{} | ||
_, err := ctx.NewSession(name) | ||
if err == nil { | ||
t.Error("expected error ", errNoStore) | ||
} | ||
_, err = ctx.GetSession(name) | ||
if err == nil { | ||
t.Error("expected error ", errNoStore) | ||
} | ||
err = ctx.SaveSession(nil) | ||
if err == nil { | ||
t.Error("expected error ", errNoStore) | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,120 @@ | ||
package base | ||
|
||
import ( | ||
"encoding/gob" | ||
"errors" | ||
|
||
"github.com/gernest/utron/base" | ||
) | ||
|
||
const ( | ||
// FlashSuccess is the context key for success flash messages | ||
FlashSuccess = "FlashSuccess" | ||
|
||
// FlashWarn is a context key for warning flash messages | ||
FlashWarn = "FlashWarn" | ||
|
||
// FlashErr is a context key for flash error message | ||
FlashErr = "FlashError" | ||
) | ||
|
||
func init() { | ||
gob.Register(&Flash{}) | ||
gob.Register(Flashes{}) | ||
} | ||
|
||
// Flash implements flash messages, like ones in gorilla/sessions | ||
type Flash struct { | ||
Kind string | ||
Message string | ||
} | ||
|
||
// Flashes is a collection of flash messages | ||
type Flashes []*Flash | ||
|
||
// GetFlashes retieves all flash messages found in a cookie session associated with ctx.. | ||
// | ||
// name is the session name which is used to store the flash messages. The flash | ||
// messages can be stored in any session, but it is a good idea to separate | ||
// session for flash messages from other sessions. | ||
// | ||
// key is the key that is used to identiry which flash messages are of interest. | ||
func GetFlashes(ctx *base.Context, name, key string) (Flashes, error) { | ||
ss, err := ctx.GetSession(name) | ||
if err != nil { | ||
return nil, err | ||
} | ||
if v, ok := ss.Values[key]; ok { | ||
delete(ss.Values, key) | ||
serr := ss.Save(ctx.Request(), ctx.Response()) | ||
if serr != nil { | ||
return nil, serr | ||
} | ||
return v.(Flashes), nil | ||
} | ||
return nil, errors.New("no flashes found") | ||
} | ||
|
||
// AddFlashToCtx takes flash messages stored in a cookie which is associated with the | ||
// request found in ctx, and puts them inside the ctx object. The flash messages can then | ||
// be retrived by calling ctx.Get( FlashKey). | ||
// | ||
// NOTE When there are no flash messages then nothing is set. | ||
func AddFlashToCtx(ctx *base.Context, name, key string) error { | ||
f, err := GetFlashes(ctx, name, key) | ||
if err != nil { | ||
return err | ||
} | ||
ctx.SetData(key, f) | ||
return nil | ||
} | ||
|
||
//Flasher tracks flash messages | ||
type Flasher struct { | ||
f Flashes | ||
} | ||
|
||
//New creates new flasher. This alllows accumulation of lash messages. To save the flash messages | ||
//the Save method should be called explicitly. | ||
func New() *Flasher { | ||
return &Flasher{} | ||
} | ||
|
||
// Add adds the flash message | ||
func (f *Flasher) Add(kind, message string) { | ||
fl := &Flash{kind, message} | ||
f.f = append(f.f, fl) | ||
} | ||
|
||
// Success adds success flash message | ||
func (f *Flasher) Success(msg string) { | ||
f.Add(FlashSuccess, msg) | ||
} | ||
|
||
// Err adds error flash message | ||
func (f *Flasher) Err(msg string) { | ||
f.Add(FlashErr, msg) | ||
} | ||
|
||
// Warn adds warning flash message | ||
func (f *Flasher) Warn(msg string) { | ||
f.Add(FlashWarn, msg) | ||
} | ||
|
||
// Save saves flash messages to context | ||
func (f *Flasher) Save(ctx *base.Context, name, key string) error { | ||
ss, err := ctx.GetSession(name) | ||
if err != nil { | ||
return err | ||
} | ||
var flashes Flashes | ||
if v, ok := ss.Values[key]; ok { | ||
flashes = v.(Flashes) | ||
} | ||
ss.Values[key] = append(flashes, f.f...) | ||
err = ss.Save(ctx.Request(), ctx.Response()) | ||
if err != nil { | ||
return err | ||
} | ||
return nil | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,83 @@ | ||
package base | ||
|
||
import ( | ||
"fmt" | ||
"net/http" | ||
"net/http/cookiejar" | ||
"net/http/httptest" | ||
"os" | ||
"testing" | ||
|
||
"github.com/gernest/utron/controller" | ||
"github.com/gernest/utron/logger" | ||
"github.com/gernest/utron/router" | ||
"github.com/gorilla/sessions" | ||
) | ||
|
||
type FlashTest struct { | ||
controller.BaseController | ||
Routes []string | ||
} | ||
|
||
const ( | ||
fname = "flash" | ||
fkey = "flash" | ||
) | ||
|
||
var result Flashes | ||
|
||
func (f *FlashTest) Index() { | ||
fl := New() | ||
fl.Success("Success") | ||
fl.Err("Err") | ||
fl.Warn("Warn") | ||
fl.Save(f.Ctx, fname, fkey) | ||
} | ||
|
||
func (f FlashTest) Flash() { | ||
r, err := GetFlashes(f.Ctx, fname, fkey) | ||
if err != nil { | ||
f.Ctx.Log.Errors(err) | ||
return | ||
} | ||
result = r | ||
} | ||
|
||
func NewFlashTest() controller.Controller { | ||
return &FlashTest{ | ||
Routes: []string{ | ||
"get;/;Index", | ||
"get;/flash;Flash", | ||
}, | ||
} | ||
} | ||
|
||
func TestFlash(t *testing.T) { | ||
codecKey1 := "ePAPW9vJv7gHoftvQTyNj5VkWB52mlza" | ||
codecKey2 := "N8SmpJ00aSpepNrKoyYxmAJhwVuKEWZD" | ||
jar, err := cookiejar.New(nil) | ||
if err != nil { | ||
t.Error(err) | ||
} | ||
client := &http.Client{Jar: jar} | ||
o := &router.Options{ | ||
Log: logger.NewDefaultLogger(os.Stdout), | ||
SessionStore: sessions.NewCookieStore([]byte(codecKey1), []byte(codecKey2)), | ||
} | ||
r := router.NewRouter(o) | ||
r.Add(NewFlashTest) | ||
ts := httptest.NewServer(r) | ||
defer ts.Close() | ||
_, err = client.Get(fmt.Sprintf("%s/", ts.URL)) | ||
if err != nil { | ||
t.Error(err) | ||
} | ||
_, err = client.Get(fmt.Sprintf("%s/flash", ts.URL)) | ||
if err != nil { | ||
t.Error(err) | ||
} | ||
|
||
if len(result) != 3 { | ||
t.Errorf("expected 3 got %d", len(result)) | ||
} | ||
} |
Oops, something went wrong.