├── .gitignore ├── CONTRIBUTORS ├── LICENSE ├── README.md ├── cleanup.go ├── mysqlstore.go └── mysqlstore_test.go /.gitignore: -------------------------------------------------------------------------------- 1 | # Compiled Object files, Static and Dynamic libs (Shared Objects) 2 | *.o 3 | *.a 4 | *.so 5 | 6 | # Folders 7 | _obj 8 | _test 9 | 10 | # Architecture specific extensions/prefixes 11 | *.[568vq] 12 | [568vq].out 13 | 14 | *.cgo1.go 15 | *.cgo2.c 16 | _cgo_defun.c 17 | _cgo_gotypes.go 18 | _cgo_export.* 19 | 20 | _testmain.go 21 | 22 | *.exe 23 | -------------------------------------------------------------------------------- /CONTRIBUTORS: -------------------------------------------------------------------------------- 1 | Srinath G.S. 2 | Gregor Robinson. 3 | Brian Jones (from whom mysqlstore_test.go is derived). 4 | Andrejs Cainikovs. 5 | The Gorilla Web Toolkit Authors. 6 | The Go Authors. 7 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (C) Contributors. The list of Contributors can be found in the CONTRIBUTORS file 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of 6 | this software and associated documentation files (the "Software"), to deal in 7 | the Software without restriction, including without limitation the rights to 8 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 9 | the Software, and to permit persons to whom the Software is furnished to do so, 10 | subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 17 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 18 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 19 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 20 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | mysqlstore 2 | ========== 3 | 4 | Gorilla's Session Store Implementation for MySQL 5 | 6 | Installation 7 | =========== 8 | 9 | Run `go get github.com/srinathgs/mysqlstore` from command line. Gets installed in `$GOPATH` 10 | 11 | Usage 12 | ===== 13 | 14 | `NewMysqlStore` takes the following paramaters 15 | 16 | endpoint - A sql.Open style endpoint 17 | tableName - table where sessions are to be saved. Required fields are created automatically if the table doesnot exist. 18 | path - path for Set-Cookie header 19 | maxAge 20 | codecs 21 | 22 | Internally, `mysqlstore` uses [this](https://github.com/go-sql-driver/mysql) MySQL driver. 23 | 24 | e.g., 25 | 26 | 27 | package main 28 | 29 | import ( 30 | "fmt" 31 | "github.com/srinathgs/mysqlstore" 32 | "net/http" 33 | ) 34 | 35 | var store *mysqlstore.MySQLStore 36 | 37 | func sessTest(w http.ResponseWriter, r *http.Request) { 38 | session, err := store.Get(r, "foobar") 39 | session.Values["bar"] = "baz" 40 | session.Values["baz"] = "foo" 41 | err = session.Save(r, w) 42 | fmt.Printf("%#v\n", session) 43 | fmt.Println(err) 44 | } 45 | 46 | func main() { 47 | var err error 48 | store, err = mysqlstore.NewMySQLStore("UN:PASS@tcp(:)/?parseTime=true&loc=Local", , "/", 3600, []byte("")) 49 | if err != nil { 50 | panic(err) 51 | } 52 | defer store.Close() 53 | 54 | http.HandleFunc("/", sessTest) 55 | http.ListenAndServe(":8080", nil) 56 | } 57 | -------------------------------------------------------------------------------- /cleanup.go: -------------------------------------------------------------------------------- 1 | package mysqlstore 2 | 3 | import ( 4 | "log" 5 | "time" 6 | ) 7 | 8 | var defaultInterval = time.Minute * 5 9 | 10 | // Cleanup runs a background goroutine every interval that deletes expired 11 | // sessions from the database. 12 | // 13 | // The design is based on https://github.com/yosssi/boltstore 14 | func (m *MySQLStore) Cleanup(interval time.Duration) (chan<- struct{}, <-chan struct{}) { 15 | if interval <= 0 { 16 | interval = defaultInterval 17 | } 18 | 19 | quit, done := make(chan struct{}), make(chan struct{}) 20 | go m.cleanup(interval, quit, done) 21 | return quit, done 22 | } 23 | 24 | // StopCleanup stops the background cleanup from running. 25 | func (m *MySQLStore) StopCleanup(quit chan<- struct{}, done <-chan struct{}) { 26 | quit <- struct{}{} 27 | <-done 28 | } 29 | 30 | // cleanup deletes expired sessions at set intervals. 31 | func (m *MySQLStore) cleanup(interval time.Duration, quit <-chan struct{}, done chan<- struct{}) { 32 | ticker := time.NewTicker(interval) 33 | 34 | defer func() { 35 | ticker.Stop() 36 | }() 37 | 38 | for { 39 | select { 40 | case <-quit: 41 | // Handle the quit signal. 42 | done <- struct{}{} 43 | return 44 | case <-ticker.C: 45 | // Delete expired sessions on each tick. 46 | err := m.deleteExpired() 47 | if err != nil { 48 | log.Printf("mysqlstore: unable to delete expired sessions: %v", err) 49 | } 50 | } 51 | } 52 | } 53 | 54 | // deleteExpired deletes expired sessions from the database. 55 | func (m *MySQLStore) deleteExpired() error { 56 | var deleteStmt = "DELETE FROM " + m.table + " WHERE expires_on < NOW()" 57 | _, err := m.db.Exec(deleteStmt) 58 | return err 59 | } 60 | -------------------------------------------------------------------------------- /mysqlstore.go: -------------------------------------------------------------------------------- 1 | /* 2 | Gorilla Sessions backend for MySQL. 3 | 4 | Copyright (c) 2013 Contributors. See the list of contributors in the CONTRIBUTORS file for details. 5 | 6 | This software is licensed under a MIT style license available in the LICENSE file. 7 | */ 8 | package mysqlstore 9 | 10 | import ( 11 | "database/sql" 12 | "encoding/gob" 13 | "errors" 14 | "fmt" 15 | "log" 16 | "net/http" 17 | "strings" 18 | "time" 19 | 20 | "github.com/go-sql-driver/mysql" 21 | "github.com/gorilla/securecookie" 22 | "github.com/gorilla/sessions" 23 | ) 24 | 25 | type MySQLStore struct { 26 | db *sql.DB 27 | stmtInsert *sql.Stmt 28 | stmtDelete *sql.Stmt 29 | stmtUpdate *sql.Stmt 30 | stmtSelect *sql.Stmt 31 | 32 | Codecs []securecookie.Codec 33 | Options *sessions.Options 34 | table string 35 | } 36 | 37 | type sessionRow struct { 38 | id string 39 | data string 40 | createdOn time.Time 41 | modifiedOn time.Time 42 | expiresOn time.Time 43 | } 44 | 45 | func init() { 46 | gob.Register(time.Time{}) 47 | } 48 | 49 | func NewMySQLStore(endpoint string, tableName string, path string, maxAge int, keyPairs ...[]byte) (*MySQLStore, error) { 50 | db, err := sql.Open("mysql", endpoint) 51 | if err != nil { 52 | return nil, err 53 | } 54 | 55 | return NewMySQLStoreFromConnection(db, tableName, path, maxAge, keyPairs...) 56 | } 57 | 58 | func NewMySQLStoreFromConnection(db *sql.DB, tableName string, path string, maxAge int, keyPairs ...[]byte) (*MySQLStore, error) { 59 | // Make sure table name is enclosed. 60 | tableName = "`" + strings.Trim(tableName, "`") + "`" 61 | 62 | cTableQ := "CREATE TABLE IF NOT EXISTS " + 63 | tableName + " (id INT NOT NULL AUTO_INCREMENT, " + 64 | "session_data LONGBLOB, " + 65 | "created_on TIMESTAMP DEFAULT NOW(), " + 66 | "modified_on TIMESTAMP NOT NULL DEFAULT NOW() ON UPDATE CURRENT_TIMESTAMP, " + 67 | "expires_on TIMESTAMP DEFAULT NOW(), PRIMARY KEY(`id`)) ENGINE=InnoDB;" 68 | if _, err := db.Exec(cTableQ); err != nil { 69 | switch err.(type) { 70 | case *mysql.MySQLError: 71 | // Error 1142 means permission denied for create command 72 | if err.(*mysql.MySQLError).Number == 1142 { 73 | break 74 | } else { 75 | return nil, err 76 | } 77 | default: 78 | return nil, err 79 | } 80 | } 81 | 82 | insQ := "INSERT INTO " + tableName + 83 | "(id, session_data, created_on, modified_on, expires_on) VALUES (NULL, ?, ?, ?, ?)" 84 | stmtInsert, stmtErr := db.Prepare(insQ) 85 | if stmtErr != nil { 86 | return nil, stmtErr 87 | } 88 | 89 | delQ := "DELETE FROM " + tableName + " WHERE id = ?" 90 | stmtDelete, stmtErr := db.Prepare(delQ) 91 | if stmtErr != nil { 92 | return nil, stmtErr 93 | } 94 | 95 | updQ := "UPDATE " + tableName + " SET session_data = ?, created_on = ?, expires_on = ? " + 96 | "WHERE id = ?" 97 | stmtUpdate, stmtErr := db.Prepare(updQ) 98 | if stmtErr != nil { 99 | return nil, stmtErr 100 | } 101 | 102 | selQ := "SELECT id, session_data, created_on, modified_on, expires_on from " + 103 | tableName + " WHERE id = ?" 104 | stmtSelect, stmtErr := db.Prepare(selQ) 105 | if stmtErr != nil { 106 | return nil, stmtErr 107 | } 108 | 109 | return &MySQLStore{ 110 | db: db, 111 | stmtInsert: stmtInsert, 112 | stmtDelete: stmtDelete, 113 | stmtUpdate: stmtUpdate, 114 | stmtSelect: stmtSelect, 115 | Codecs: securecookie.CodecsFromPairs(keyPairs...), 116 | Options: &sessions.Options{ 117 | Path: path, 118 | MaxAge: maxAge, 119 | }, 120 | table: tableName, 121 | }, nil 122 | } 123 | 124 | func (m *MySQLStore) Close() { 125 | m.stmtSelect.Close() 126 | m.stmtUpdate.Close() 127 | m.stmtDelete.Close() 128 | m.stmtInsert.Close() 129 | m.db.Close() 130 | } 131 | 132 | func (m *MySQLStore) Get(r *http.Request, name string) (*sessions.Session, error) { 133 | return sessions.GetRegistry(r).Get(m, name) 134 | } 135 | 136 | func (m *MySQLStore) New(r *http.Request, name string) (*sessions.Session, error) { 137 | session := sessions.NewSession(m, name) 138 | session.Options = &sessions.Options{ 139 | Path: m.Options.Path, 140 | Domain: m.Options.Domain, 141 | MaxAge: m.Options.MaxAge, 142 | Secure: m.Options.Secure, 143 | HttpOnly: m.Options.HttpOnly, 144 | SameSite: m.Options.SameSite, 145 | } 146 | session.IsNew = true 147 | var err error 148 | if cook, errCookie := r.Cookie(name); errCookie == nil { 149 | err = securecookie.DecodeMulti(name, cook.Value, &session.ID, m.Codecs...) 150 | if err == nil { 151 | err = m.load(session) 152 | if err == nil { 153 | session.IsNew = false 154 | } else { 155 | err = nil 156 | } 157 | } 158 | } 159 | return session, err 160 | } 161 | 162 | func (m *MySQLStore) Save(r *http.Request, w http.ResponseWriter, session *sessions.Session) error { 163 | var err error 164 | if session.ID == "" { 165 | if err = m.insert(session); err != nil { 166 | return err 167 | } 168 | } else if err = m.save(session); err != nil { 169 | return err 170 | } 171 | encoded, err := securecookie.EncodeMulti(session.Name(), session.ID, m.Codecs...) 172 | if err != nil { 173 | return err 174 | } 175 | http.SetCookie(w, sessions.NewCookie(session.Name(), encoded, session.Options)) 176 | return nil 177 | } 178 | 179 | func (m *MySQLStore) insert(session *sessions.Session) error { 180 | var createdOn time.Time 181 | var modifiedOn time.Time 182 | var expiresOn time.Time 183 | crOn := session.Values["created_on"] 184 | if crOn == nil { 185 | createdOn = time.Now() 186 | } else { 187 | createdOn = crOn.(time.Time) 188 | } 189 | modifiedOn = createdOn 190 | exOn := session.Values["expires_on"] 191 | if exOn == nil { 192 | expiresOn = time.Now().Add(time.Second * time.Duration(session.Options.MaxAge)) 193 | } else { 194 | expiresOn = exOn.(time.Time) 195 | } 196 | delete(session.Values, "created_on") 197 | delete(session.Values, "expires_on") 198 | delete(session.Values, "modified_on") 199 | 200 | encoded, encErr := securecookie.EncodeMulti(session.Name(), session.Values, m.Codecs...) 201 | if encErr != nil { 202 | return encErr 203 | } 204 | res, insErr := m.stmtInsert.Exec(encoded, createdOn, modifiedOn, expiresOn) 205 | if insErr != nil { 206 | return insErr 207 | } 208 | lastInserted, lInsErr := res.LastInsertId() 209 | if lInsErr != nil { 210 | return lInsErr 211 | } 212 | session.ID = fmt.Sprintf("%d", lastInserted) 213 | return nil 214 | } 215 | 216 | func (m *MySQLStore) Delete(r *http.Request, w http.ResponseWriter, session *sessions.Session) error { 217 | 218 | // Set cookie to expire. 219 | options := *session.Options 220 | options.MaxAge = -1 221 | http.SetCookie(w, sessions.NewCookie(session.Name(), "", &options)) 222 | // Clear session values. 223 | for k := range session.Values { 224 | delete(session.Values, k) 225 | } 226 | 227 | _, delErr := m.stmtDelete.Exec(session.ID) 228 | if delErr != nil { 229 | return delErr 230 | } 231 | return nil 232 | } 233 | 234 | func (m *MySQLStore) save(session *sessions.Session) error { 235 | if session.IsNew == true { 236 | return m.insert(session) 237 | } 238 | var createdOn time.Time 239 | var expiresOn time.Time 240 | crOn := session.Values["created_on"] 241 | if crOn == nil { 242 | createdOn = time.Now() 243 | } else { 244 | createdOn = crOn.(time.Time) 245 | } 246 | 247 | exOn := session.Values["expires_on"] 248 | if exOn == nil { 249 | expiresOn = time.Now().Add(time.Second * time.Duration(session.Options.MaxAge)) 250 | log.Print("nil") 251 | } else { 252 | expiresOn = exOn.(time.Time) 253 | if expiresOn.Sub(time.Now().Add(time.Second*time.Duration(session.Options.MaxAge))) < 0 { 254 | expiresOn = time.Now().Add(time.Second * time.Duration(session.Options.MaxAge)) 255 | } 256 | } 257 | 258 | delete(session.Values, "created_on") 259 | delete(session.Values, "expires_on") 260 | delete(session.Values, "modified_on") 261 | encoded, encErr := securecookie.EncodeMulti(session.Name(), session.Values, m.Codecs...) 262 | if encErr != nil { 263 | return encErr 264 | } 265 | _, updErr := m.stmtUpdate.Exec(encoded, createdOn, expiresOn, session.ID) 266 | if updErr != nil { 267 | return updErr 268 | } 269 | return nil 270 | } 271 | 272 | func (m *MySQLStore) load(session *sessions.Session) error { 273 | row := m.stmtSelect.QueryRow(session.ID) 274 | sess := sessionRow{} 275 | scanErr := row.Scan(&sess.id, &sess.data, &sess.createdOn, &sess.modifiedOn, &sess.expiresOn) 276 | if scanErr != nil { 277 | return scanErr 278 | } 279 | if sess.expiresOn.Sub(time.Now()) < 0 { 280 | log.Printf("Session expired on %s, but it is %s now.", sess.expiresOn, time.Now()) 281 | return errors.New("Session expired") 282 | } 283 | err := securecookie.DecodeMulti(session.Name(), sess.data, &session.Values, m.Codecs...) 284 | if err != nil { 285 | return err 286 | } 287 | session.Values["created_on"] = sess.createdOn 288 | session.Values["modified_on"] = sess.modifiedOn 289 | session.Values["expires_on"] = sess.expiresOn 290 | return nil 291 | 292 | } 293 | -------------------------------------------------------------------------------- /mysqlstore_test.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2013 Gregor Robinson. 2 | // Copyright (c) 2013 Brian Jones. 3 | // All rights reserved. 4 | // Use of this source code is governed by a MIT-style 5 | // license that can be found in the LICENSE file. 6 | 7 | package mysqlstore 8 | 9 | import ( 10 | "encoding/gob" 11 | "github.com/gorilla/sessions" 12 | "net/http" 13 | "net/http/httptest" 14 | "testing" 15 | ) 16 | 17 | type FlashMessage struct { 18 | Type int 19 | Message string 20 | } 21 | 22 | func TestMySQLStore(t *testing.T) { 23 | var req *http.Request 24 | var rsp *httptest.ResponseRecorder 25 | var hdr http.Header 26 | var err error 27 | var ok bool 28 | var cookies []string 29 | var session *sessions.Session 30 | var flashes []interface{} 31 | 32 | // Copyright 2012 The Gorilla Authors. All rights reserved. 33 | // Use of this source code is governed by a BSD-style 34 | // license that can be found in the LICENSE file. 35 | 36 | // Round 1 ---------------------------------------------------------------- 37 | 38 | store, err := NewMySQLStore("testuser:testpw@tcp(localhost:3306)/testdb?parseTime=true&loc=Local", 39 | "sessionstore", "/", 3600, []byte("secret-key")) 40 | if err != nil { 41 | t.Fatalf("Error connecting to MySQL: ", err) 42 | } 43 | defer store.Close() 44 | 45 | req, _ = http.NewRequest("GET", "http://localhost:8080/", nil) 46 | rsp = httptest.NewRecorder() 47 | // Get a session. 48 | if session, err = store.Get(req, "session-key"); err != nil { 49 | t.Fatalf("Error getting session: %v", err) 50 | } 51 | // Get a flash. 52 | flashes = session.Flashes() 53 | if len(flashes) != 0 { 54 | t.Errorf("Expected empty flashes; Got %v", flashes) 55 | } 56 | // Add some flashes. 57 | session.AddFlash("foo") 58 | session.AddFlash("bar") 59 | // Custom key. 60 | session.AddFlash("baz", "custom_key") 61 | // Save. 62 | if err = sessions.Save(req, rsp); err != nil { 63 | t.Fatalf("Error saving session: %v", err) 64 | } 65 | hdr = rsp.Header() 66 | cookies, ok = hdr["Set-Cookie"] 67 | if !ok || len(cookies) != 1 { 68 | t.Fatalf("No cookies. Header:", hdr) 69 | } 70 | 71 | // Round 2 ---------------------------------------------------------------- 72 | 73 | req, _ = http.NewRequest("GET", "http://localhost:8080/", nil) 74 | req.Header.Add("Cookie", cookies[0]) 75 | rsp = httptest.NewRecorder() 76 | // Get a session. 77 | if session, err = store.Get(req, "session-key"); err != nil { 78 | t.Fatalf("Error getting session: %v", err) 79 | } 80 | // Check all saved values. 81 | flashes = session.Flashes() 82 | if len(flashes) != 2 { 83 | t.Fatalf("Expected flashes; Got %v", flashes) 84 | } 85 | if flashes[0] != "foo" || flashes[1] != "bar" { 86 | t.Errorf("Expected foo,bar; Got %v", flashes) 87 | } 88 | flashes = session.Flashes() 89 | if len(flashes) != 0 { 90 | t.Errorf("Expected dumped flashes; Got %v", flashes) 91 | } 92 | // Custom key. 93 | flashes = session.Flashes("custom_key") 94 | if len(flashes) != 1 { 95 | t.Errorf("Expected flashes; Got %v", flashes) 96 | } else if flashes[0] != "baz" { 97 | t.Errorf("Expected baz; Got %v", flashes) 98 | } 99 | flashes = session.Flashes("custom_key") 100 | if len(flashes) != 0 { 101 | t.Errorf("Expected dumped flashes; Got %v", flashes) 102 | } 103 | 104 | session.Options.MaxAge = -1 105 | // Save. 106 | if err = sessions.Save(req, rsp); err != nil { 107 | t.Fatalf("Error saving session: %v", err) 108 | } 109 | 110 | // Round 3 ---------------------------------------------------------------- 111 | // Custom type 112 | 113 | req, _ = http.NewRequest("GET", "http://localhost:8080/", nil) 114 | rsp = httptest.NewRecorder() 115 | // Get a session. 116 | if session, err = store.Get(req, "session-key"); err != nil { 117 | t.Fatalf("Error getting session: %v", err) 118 | } 119 | // Get a flash. 120 | flashes = session.Flashes() 121 | if len(flashes) != 0 { 122 | t.Errorf("Expected empty flashes; Got %v", flashes) 123 | } 124 | // Add some flashes. 125 | session.AddFlash(&FlashMessage{42, "foo"}) 126 | // Save. 127 | if err = sessions.Save(req, rsp); err != nil { 128 | t.Fatalf("Error saving session: %v", err) 129 | } 130 | hdr = rsp.Header() 131 | cookies, ok = hdr["Set-Cookie"] 132 | if !ok || len(cookies) != 1 { 133 | t.Fatalf("No cookies. Header:", hdr) 134 | } 135 | 136 | // Round 4 ---------------------------------------------------------------- 137 | // Custom type 138 | 139 | req, _ = http.NewRequest("GET", "http://localhost:8080/", nil) 140 | req.Header.Add("Cookie", cookies[0]) 141 | rsp = httptest.NewRecorder() 142 | // Get a session. 143 | if session, err = store.Get(req, "session-key"); err != nil { 144 | t.Fatalf("Error getting session: %v", err) 145 | } 146 | // Check all saved values. 147 | flashes = session.Flashes() 148 | if len(flashes) != 1 { 149 | t.Fatalf("Expected flashes; Got %v", flashes) 150 | } 151 | custom := flashes[0].(FlashMessage) 152 | if custom.Type != 42 || custom.Message != "foo" { 153 | t.Errorf("Expected %#v, got %#v", FlashMessage{42, "foo"}, custom) 154 | } 155 | 156 | // Delete session. 157 | session.Options.MaxAge = -1 158 | // Save. 159 | if err = sessions.Save(req, rsp); err != nil { 160 | t.Fatalf("Error saving session: %v", err) 161 | } 162 | 163 | // Round 5 ---------------------------------------------------------------- 164 | // MySQLStore Delete session (not exposed by gorilla sessions interface). 165 | 166 | req, _ = http.NewRequest("GET", "http://localhost:8080/", nil) 167 | req.Header.Add("Cookie", cookies[0]) 168 | rsp = httptest.NewRecorder() 169 | // Get a session. 170 | if session, err = store.Get(req, "session-key"); err != nil { 171 | t.Fatalf("Error getting session: %v", err) 172 | } 173 | 174 | if err = store.Delete(req, rsp, session); err != nil { 175 | t.Fatalf("Error deleting session: %v", err) 176 | } 177 | 178 | // Get a flash. 179 | flashes = session.Flashes() 180 | if len(flashes) != 0 { 181 | t.Errorf("Expected empty flashes; Got %v", flashes) 182 | } 183 | hdr = rsp.Header() 184 | cookies, ok = hdr["Set-Cookie"] 185 | if !ok || len(cookies) != 1 { 186 | t.Fatalf("No cookies. Header:", hdr) 187 | } 188 | 189 | // Round 6 ---------------------------------------------------------------- 190 | // change MaxLength of session 191 | //req, err = http.NewRequest("GET", "http://www.example.com", nil) 192 | //if err != nil { 193 | // t.Fatal("failed to create request", err) 194 | //} 195 | //w := httptest.NewRecorder() 196 | 197 | //session, err = store.New(req, "my session") 198 | //session.Values["big"] = make([]byte, base64.StdEncoding.DecodedLen(4096*2)) 199 | //err = session.Save(req, w) 200 | //if err == nil { 201 | // t.Fatal("expected an error, got nil") 202 | //} 203 | 204 | //store.MaxLength(4096 * 3) // A bit more than the value size to account for encoding overhead. 205 | //err = session.Save(req, w) 206 | //if err != nil { 207 | // t.Fatal("failed to Save:", err) 208 | //} 209 | } 210 | 211 | func init() { 212 | gob.Register(FlashMessage{}) 213 | } 214 | --------------------------------------------------------------------------------