Browse Source

Initial commit

master
Unknown 5 years ago
commit
0719404ba1
15 changed files with 2931 additions and 0 deletions
  1. +191
    -0
      LICENSE
  2. +153
    -0
      README.md
  3. +219
    -0
      couchbase/sess_couchbase.go
  4. +220
    -0
      memcache/sess_memcache.go
  5. +213
    -0
      mysql/sess_mysql.go
  6. +243
    -0
      postgres/sess_postgresql.go
  7. +234
    -0
      redis/sess_redis.go
  8. +185
    -0
      sess_cookie.go
  9. +52
    -0
      sess_cookie_test.go
  10. +280
    -0
      sess_file.go
  11. +199
    -0
      sess_mem.go
  12. +49
    -0
      sess_mem_test.go
  13. +132
    -0
      sess_test.go
  14. +231
    -0
      sess_utils.go
  15. +330
    -0
      session.go

+ 191
- 0
LICENSE View File

@@ -0,0 +1,191 @@
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/

TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION

1. Definitions.

"License" shall mean the terms and conditions for use, reproduction, and
distribution as defined by Sections 1 through 9 of this document.

"Licensor" shall mean the copyright owner or entity authorized by the copyright
owner that is granting the License.

"Legal Entity" shall mean the union of the acting entity and all other entities
that control, are controlled by, or are under common control with that entity.
For the purposes of this definition, "control" means (i) the power, direct or
indirect, to cause the direction or management of such entity, whether by
contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.

"You" (or "Your") shall mean an individual or Legal Entity exercising
permissions granted by this License.

"Source" form shall mean the preferred form for making modifications, including
but not limited to software source code, documentation source, and configuration
files.

"Object" form shall mean any form resulting from mechanical transformation or
translation of a Source form, including but not limited to compiled object code,
generated documentation, and conversions to other media types.

"Work" shall mean the work of authorship, whether in Source or Object form, made
available under the License, as indicated by a copyright notice that is included
in or attached to the work (an example is provided in the Appendix below).

"Derivative Works" shall mean any work, whether in Source or Object form, that
is based on (or derived from) the Work and for which the editorial revisions,
annotations, elaborations, or other modifications represent, as a whole, an
original work of authorship. For the purposes of this License, Derivative Works
shall not include works that remain separable from, or merely link (or bind by
name) to the interfaces of, the Work and Derivative Works thereof.

"Contribution" shall mean any work of authorship, including the original version
of the Work and any modifications or additions to that Work or Derivative Works
thereof, that is intentionally submitted to Licensor for inclusion in the Work
by the copyright owner or by an individual or Legal Entity authorized to submit
on behalf of the copyright owner. For the purposes of this definition,
"submitted" means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems, and
issue tracking systems that are managed by, or on behalf of, the Licensor for
the purpose of discussing and improving the Work, but excluding communication
that is conspicuously marked or otherwise designated in writing by the copyright
owner as "Not a Contribution."

"Contributor" shall mean Licensor and any individual or Legal Entity on behalf
of whom a Contribution has been received by Licensor and subsequently
incorporated within the Work.

2. Grant of Copyright License.

Subject to the terms and conditions of this License, each Contributor hereby
grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free,
irrevocable copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the Work and such
Derivative Works in Source or Object form.

3. Grant of Patent License.

Subject to the terms and conditions of this License, each Contributor hereby
grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free,
irrevocable (except as stated in this section) patent license to make, have
made, use, offer to sell, sell, import, and otherwise transfer the Work, where
such license applies only to those patent claims licensable by such Contributor
that are necessarily infringed by their Contribution(s) alone or by combination
of their Contribution(s) with the Work to which such Contribution(s) was
submitted. If You institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work or a
Contribution incorporated within the Work constitutes direct or contributory
patent infringement, then any patent licenses granted to You under this License
for that Work shall terminate as of the date such litigation is filed.

4. Redistribution.

You may reproduce and distribute copies of the Work or Derivative Works thereof
in any medium, with or without modifications, and in Source or Object form,
provided that You meet the following conditions:

You must give any other recipients of the Work or Derivative Works a copy of
this License; and
You must cause any modified files to carry prominent notices stating that You
changed the files; and
You must retain, in the Source form of any Derivative Works that You distribute,
all copyright, patent, trademark, and attribution notices from the Source form
of the Work, excluding those notices that do not pertain to any part of the
Derivative Works; and
If the Work includes a "NOTICE" text file as part of its distribution, then any
Derivative Works that You distribute must include a readable copy of the
attribution notices contained within such NOTICE file, excluding those notices
that do not pertain to any part of the Derivative Works, in at least one of the
following places: within a NOTICE text file distributed as part of the
Derivative Works; within the Source form or documentation, if provided along
with the Derivative Works; or, within a display generated by the Derivative
Works, if and wherever such third-party notices normally appear. The contents of
the NOTICE file are for informational purposes only and do not modify the
License. You may add Your own attribution notices within Derivative Works that
You distribute, alongside or as an addendum to the NOTICE text from the Work,
provided that such additional attribution notices cannot be construed as
modifying the License.
You may add Your own copyright statement to Your modifications and may provide
additional or different license terms and conditions for use, reproduction, or
distribution of Your modifications, or for any such Derivative Works as a whole,
provided Your use, reproduction, and distribution of the Work otherwise complies
with the conditions stated in this License.

5. Submission of Contributions.

Unless You explicitly state otherwise, any Contribution intentionally submitted
for inclusion in the Work by You to the Licensor shall be under the terms and
conditions of this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify the terms of
any separate license agreement you may have executed with Licensor regarding
such Contributions.

6. Trademarks.

This License does not grant permission to use the trade names, trademarks,
service marks, or product names of the Licensor, except as required for
reasonable and customary use in describing the origin of the Work and
reproducing the content of the NOTICE file.

7. Disclaimer of Warranty.

Unless required by applicable law or agreed to in writing, Licensor provides the
Work (and each Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied,
including, without limitation, any warranties or conditions of TITLE,
NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are
solely responsible for determining the appropriateness of using or
redistributing the Work and assume any risks associated with Your exercise of
permissions under this License.

8. Limitation of Liability.

In no event and under no legal theory, whether in tort (including negligence),
contract, or otherwise, unless required by applicable law (such as deliberate
and grossly negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special, incidental,
or consequential damages of any character arising as a result of this License or
out of the use or inability to use the Work (including but not limited to
damages for loss of goodwill, work stoppage, computer failure or malfunction, or
any and all other commercial damages or losses), even if such Contributor has
been advised of the possibility of such damages.

9. Accepting Warranty or Additional Liability.

While redistributing the Work or Derivative Works thereof, You may choose to
offer, and charge a fee for, acceptance of support, warranty, indemnity, or
other liability obligations and/or rights consistent with this License. However,
in accepting such obligations, You may act only on Your own behalf and on Your
sole responsibility, not on behalf of any other Contributor, and only if You
agree to indemnify, defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason of your
accepting any such warranty or additional liability.

END OF TERMS AND CONDITIONS

APPENDIX: How to apply the Apache License to your work

To apply the Apache License to your work, attach the following boilerplate
notice, with the fields enclosed by brackets "[]" replaced with your own
identifying information. (Don't include the brackets!) The text should be
enclosed in the appropriate comment syntax for the file format. We also
recommend that a file or class name and description of purpose be included on
the same "printed page" as the copyright notice for easier identification within
third-party archives.

Copyright [yyyy] [name of copyright owner]

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.

+ 153
- 0
README.md View File

@@ -0,0 +1,153 @@
session
=======

Middleware cache is the session manager of [Macaron](https://github.com/Unknwon/macaron). It can use many session providers, including cookie, memory, file, redis, memcache, PostgreSQL, MySQL, and couchbase.

[API Reference](https://gowalker.org/github.com/macaron-contrib/session)

## Usage

```go
import (
"github.com/Unknwon/macaron"
"github.com/macaron-contrib/session"
)

func main() {
m := macaron.Classic()
m.Use(session.Sessioner())
m.Get("/", func(sess session.SessionStore) string {
sess.Set("session", "session middleware")
return sess.Get("session")
})

m.Run()
}
```

To use redis, memcache, PostgreSQL, MySQL, or couchbase as adapter, you should import their init functions:

```go
import (
_ "github.com/macaron-contrib/session/redis"
_ "github.com/macaron-contrib/session/memcache"
_ "github.com/macaron-contrib/session/postgres"
_ "github.com/macaron-contrib/session/mysql"
_ "github.com/macaron-contrib/session/couchbase"
)
```

## Options

`session.Sessioner` comes with a variety of configuration options:

```go
// ...
m.Use(session.Sessioner(session.SessionOptions{
Provider: "memory", // Name of provider.
CookieName: "macaronSession", // Key name store in cookie.
Interval: 3600, // GC interval for memory adapter.
ProviderConfig: "./tmp", // Provider configuration string.
}))
// ...
```

### Example Options

- memory:

```go
// ...
m.Use(session.Sessioner(session.SessionOptions{
Provider: "memory", // Name of provider.
CookieName: "macaronSession", // Key name store in cookie.
Interval: 3600, // GC interval for memory adapter.
ProviderConfig: "./tmp", // Provider configuration string.
}))
// ...
```

- file:

```go
// ...
m.Use(session.Sessioner(session.SessionOptions{
Provider: "file", // Name of provider.
CookieName: "macaronSession", // Key name store in cookie.
Interval: 3600, // GC interval for memory adapter.
ProviderConfig: "./tmp", // Provider configuration string.
}))
// ...
```

- Redis:

```go
// ...
m.Use(session.Sessioner(session.SessionOptions{
Provider: "redis", // Name of provider.
CookieName: "macaronSession", // Key name store in cookie.
Interval: 3600, // GC interval for memory adapter.
ProviderConfig: "127.0.0.1:6379,100,macaron", // Provider configuration string.
}))
// ...
```

- MySQL:

```go
// ...
m.Use(session.Sessioner(session.SessionOptions{
Provider: "mysql", // Name of provider.
CookieName: "macaronSession", // Key name store in cookie.
Interval: 3600, // GC interval for memory adapter.
ProviderConfig: "username:password@protocol(address)/dbname?param=value", // Provider configuration string.
}))
// ...
```

- Cookie:

```go
// ...
m.Use(session.Sessioner(session.SessionOptions{
Provider: "cookie", // Name of provider.
CookieName: "macaronSession", // Key name store in cookie.
Interval: 3600, // GC interval for memory adapter.
ProviderConfig: "{\"cookieName\":\"gosessionid\",\"securityKey\":\"beegocookiehashkey\"}", // Provider configuration string.
}))
// ...
```

## How to write own provider?

When you develop a web app, maybe you want to write own provider because you must meet the requirements.

Writing a provider is easy. You only need to define two struct types
(Session and Provider), which satisfy the interface definition.
Maybe you will find the **memory** provider is a good example.

type SessionStore interface {
Set(key, value interface{}) error //set session value
Get(key interface{}) interface{} //get session value
Delete(key interface{}) error //delete session value
SessionID() string //back current sessionID
SessionRelease(w http.ResponseWriter) // release the resource & save data to provider & return the data
Flush() error //delete all data
}
type Provider interface {
SessionInit(gclifetime int64, config string) error
SessionRead(sid string) (SessionStore, error)
SessionExist(sid string) bool
SessionRegenerate(oldsid, sid string) (SessionStore, error)
SessionDestroy(sid string) error
SessionAll() int //get all active session
SessionGC()
}


## License

This project is under Apache v2 License. See the [LICENSE](LICENSE) file for the full license text.

+ 219
- 0
couchbase/sess_couchbase.go View File

@@ -0,0 +1,219 @@
// Copyright 2013 Beego Authors
//
// Licensed under the Apache License, Version 2.0 (the "License"): you may
// not use this file except in compliance with the License. You may obtain
// a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
// License for the specific language governing permissions and limitations
// under the License.

package session

import (
"net/http"
"strings"
"sync"

"github.com/couchbaselabs/go-couchbase"

"github.com/macaron-contrib/session"
)

var couchbpder = &CouchbaseProvider{}

type CouchbaseSessionStore struct {
b *couchbase.Bucket
sid string
lock sync.RWMutex
values map[interface{}]interface{}
maxlifetime int64
}

type CouchbaseProvider struct {
maxlifetime int64
savePath string
pool string
bucket string
b *couchbase.Bucket
}

func (cs *CouchbaseSessionStore) Set(key, value interface{}) error {
cs.lock.Lock()
defer cs.lock.Unlock()
cs.values[key] = value
return nil
}

func (cs *CouchbaseSessionStore) Get(key interface{}) interface{} {
cs.lock.RLock()
defer cs.lock.RUnlock()
if v, ok := cs.values[key]; ok {
return v
} else {
return nil
}
}

func (cs *CouchbaseSessionStore) Delete(key interface{}) error {
cs.lock.Lock()
defer cs.lock.Unlock()
delete(cs.values, key)
return nil
}

func (cs *CouchbaseSessionStore) Flush() error {
cs.lock.Lock()
defer cs.lock.Unlock()
cs.values = make(map[interface{}]interface{})
return nil
}

func (cs *CouchbaseSessionStore) SessionID() string {
return cs.sid
}

func (cs *CouchbaseSessionStore) SessionRelease(w http.ResponseWriter) {
defer cs.b.Close()

// if rs.values is empty, return directly
if len(cs.values) < 1 {
cs.b.Delete(cs.sid)
return
}

bo, err := session.EncodeGob(cs.values)
if err != nil {
return
}

cs.b.Set(cs.sid, int(cs.maxlifetime), bo)
}

func (cp *CouchbaseProvider) getBucket() *couchbase.Bucket {
c, err := couchbase.Connect(cp.savePath)
if err != nil {
return nil
}

pool, err := c.GetPool(cp.pool)
if err != nil {
return nil
}

bucket, err := pool.GetBucket(cp.bucket)
if err != nil {
return nil
}

return bucket
}

// init couchbase session
// savepath like couchbase server REST/JSON URL
// e.g. http://host:port/, Pool, Bucket
func (cp *CouchbaseProvider) SessionInit(maxlifetime int64, savePath string) error {
cp.maxlifetime = maxlifetime
configs := strings.Split(savePath, ",")
if len(configs) > 0 {
cp.savePath = configs[0]
}
if len(configs) > 1 {
cp.pool = configs[1]
}
if len(configs) > 2 {
cp.bucket = configs[2]
}

return nil
}

// read couchbase session by sid
func (cp *CouchbaseProvider) SessionRead(sid string) (session.SessionStore, error) {
cp.b = cp.getBucket()

var doc []byte

err := cp.b.Get(sid, &doc)
var kv map[interface{}]interface{}
if doc == nil {
kv = make(map[interface{}]interface{})
} else {
kv, err = session.DecodeGob(doc)
if err != nil {
return nil, err
}
}

cs := &CouchbaseSessionStore{b: cp.b, sid: sid, values: kv, maxlifetime: cp.maxlifetime}
return cs, nil
}

func (cp *CouchbaseProvider) SessionExist(sid string) bool {
cp.b = cp.getBucket()
defer cp.b.Close()

var doc []byte

if err := cp.b.Get(sid, &doc); err != nil || doc == nil {
return false
} else {
return true
}
}

func (cp *CouchbaseProvider) SessionRegenerate(oldsid, sid string) (session.SessionStore, error) {
cp.b = cp.getBucket()

var doc []byte
if err := cp.b.Get(oldsid, &doc); err != nil || doc == nil {
cp.b.Set(sid, int(cp.maxlifetime), "")
} else {
err := cp.b.Delete(oldsid)
if err != nil {
return nil, err
}
_, _ = cp.b.Add(sid, int(cp.maxlifetime), doc)
}

err := cp.b.Get(sid, &doc)
if err != nil {
return nil, err
}
var kv map[interface{}]interface{}
if doc == nil {
kv = make(map[interface{}]interface{})
} else {
kv, err = session.DecodeGob(doc)
if err != nil {
return nil, err
}
}

cs := &CouchbaseSessionStore{b: cp.b, sid: sid, values: kv, maxlifetime: cp.maxlifetime}
return cs, nil
}

func (cp *CouchbaseProvider) SessionDestroy(sid string) error {
cp.b = cp.getBucket()
defer cp.b.Close()

cp.b.Delete(sid)
return nil
}

func (cp *CouchbaseProvider) SessionGC() {
return
}

func (cp *CouchbaseProvider) SessionAll() int {
return 0
}

func init() {
session.Register("couchbase", couchbpder)
}

+ 220
- 0
memcache/sess_memcache.go View File

@@ -0,0 +1,220 @@
// Copyright 2013 Beego Authors
//
// Licensed under the Apache License, Version 2.0 (the "License"): you may
// not use this file except in compliance with the License. You may obtain
// a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
// License for the specific language governing permissions and limitations
// under the License.

package session

import (
"net/http"
"sync"

"github.com/beego/memcache"

"github.com/macaron-contrib/session"
)

var mempder = &MemProvider{}

// memcache session store
type MemcacheSessionStore struct {
c *memcache.Connection
sid string
lock sync.RWMutex
values map[interface{}]interface{}
maxlifetime int64
}

// set value in memcache session
func (rs *MemcacheSessionStore) Set(key, value interface{}) error {
rs.lock.Lock()
defer rs.lock.Unlock()
rs.values[key] = value
return nil
}

// get value in memcache session
func (rs *MemcacheSessionStore) Get(key interface{}) interface{} {
rs.lock.RLock()
defer rs.lock.RUnlock()
if v, ok := rs.values[key]; ok {
return v
} else {
return nil
}
}

// delete value in memcache session
func (rs *MemcacheSessionStore) Delete(key interface{}) error {
rs.lock.Lock()
defer rs.lock.Unlock()
delete(rs.values, key)
return nil
}

// clear all values in memcache session
func (rs *MemcacheSessionStore) Flush() error {
rs.lock.Lock()
defer rs.lock.Unlock()
rs.values = make(map[interface{}]interface{})
return nil
}

// get redis session id
func (rs *MemcacheSessionStore) SessionID() string {
return rs.sid
}

// save session values to redis
func (rs *MemcacheSessionStore) SessionRelease(w http.ResponseWriter) {
defer rs.c.Close()
// if rs.values is empty, return directly
if len(rs.values) < 1 {
rs.c.Delete(rs.sid)
return
}

b, err := session.EncodeGob(rs.values)
if err != nil {
return
}
rs.c.Set(rs.sid, 0, uint64(rs.maxlifetime), b)
}

// redis session provider
type MemProvider struct {
maxlifetime int64
savePath string
poolsize int
password string
}

// init redis session
// savepath like
// e.g. 127.0.0.1:9090
func (rp *MemProvider) SessionInit(maxlifetime int64, savePath string) error {
rp.maxlifetime = maxlifetime
rp.savePath = savePath
return nil
}

// read redis session by sid
func (rp *MemProvider) SessionRead(sid string) (session.SessionStore, error) {
conn, err := rp.connectInit()
if err != nil {
return nil, err
}
kvs, err := conn.Get(sid)
if err != nil {
return nil, err
}
var contain []byte
if len(kvs) > 0 {
contain = kvs[0].Value
}
var kv map[interface{}]interface{}
if len(contain) == 0 {
kv = make(map[interface{}]interface{})
} else {
kv, err = session.DecodeGob(contain)
if err != nil {
return nil, err
}
}

rs := &MemcacheSessionStore{c: conn, sid: sid, values: kv, maxlifetime: rp.maxlifetime}
return rs, nil
}

// check redis session exist by sid
func (rp *MemProvider) SessionExist(sid string) bool {
conn, err := rp.connectInit()
if err != nil {
return false
}
defer conn.Close()
if kvs, err := conn.Get(sid); err != nil || len(kvs) == 0 {
return false
} else {
return true
}
}

// generate new sid for redis session
func (rp *MemProvider) SessionRegenerate(oldsid, sid string) (session.SessionStore, error) {
conn, err := rp.connectInit()
if err != nil {
return nil, err
}
var contain []byte
if kvs, err := conn.Get(sid); err != nil || len(kvs) == 0 {
// oldsid doesn't exists, set the new sid directly
// ignore error here, since if it return error
// the existed value will be 0
conn.Set(sid, 0, uint64(rp.maxlifetime), []byte(""))
} else {
conn.Delete(oldsid)
conn.Set(sid, 0, uint64(rp.maxlifetime), kvs[0].Value)
contain = kvs[0].Value
}

var kv map[interface{}]interface{}
if len(contain) == 0 {
kv = make(map[interface{}]interface{})
} else {
kv, err = session.DecodeGob(contain)
if err != nil {
return nil, err
}
}

rs := &MemcacheSessionStore{c: conn, sid: sid, values: kv, maxlifetime: rp.maxlifetime}
return rs, nil
}

// delete redis session by id
func (rp *MemProvider) SessionDestroy(sid string) error {
conn, err := rp.connectInit()
if err != nil {
return err
}
defer conn.Close()

_, err = conn.Delete(sid)
if err != nil {
return err
}
return nil
}

// Impelment method, no used.
func (rp *MemProvider) SessionGC() {
return
}

// @todo
func (rp *MemProvider) SessionAll() int {
return 0
}

// connect to memcache and keep the connection.
func (rp *MemProvider) connectInit() (*memcache.Connection, error) {
c, err := memcache.Connect(rp.savePath)
if err != nil {
return nil, err
}
return c, nil
}

func init() {
session.Register("memcache", mempder)
}

+ 213
- 0
mysql/sess_mysql.go View File

@@ -0,0 +1,213 @@
// Copyright 2013 Beego Authors
//
// Licensed under the Apache License, Version 2.0 (the "License"): you may
// not use this file except in compliance with the License. You may obtain
// a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
// License for the specific language governing permissions and limitations
// under the License.

package session

// mysql session support need create table as sql:
// CREATE TABLE `session` (
// `session_key` char(64) NOT NULL,
// session_data` blob,
// `session_expiry` int(11) unsigned NOT NULL,
// PRIMARY KEY (`session_key`)
// ) ENGINE=MyISAM DEFAULT CHARSET=utf8;

import (
"database/sql"
"net/http"
"sync"
"time"

_ "github.com/go-sql-driver/mysql"

"github.com/macaron-contrib/session"
)

var mysqlpder = &MysqlProvider{}

// mysql session store
type MysqlSessionStore struct {
c *sql.DB
sid string
lock sync.RWMutex
values map[interface{}]interface{}
}

// set value in mysql session.
// it is temp value in map.
func (st *MysqlSessionStore) Set(key, value interface{}) error {
st.lock.Lock()
defer st.lock.Unlock()
st.values[key] = value
return nil
}

// get value from mysql session
func (st *MysqlSessionStore) Get(key interface{}) interface{} {
st.lock.RLock()
defer st.lock.RUnlock()
if v, ok := st.values[key]; ok {
return v
} else {
return nil
}
}

// delete value in mysql session
func (st *MysqlSessionStore) Delete(key interface{}) error {
st.lock.Lock()
defer st.lock.Unlock()
delete(st.values, key)
return nil
}

// clear all values in mysql session
func (st *MysqlSessionStore) Flush() error {
st.lock.Lock()
defer st.lock.Unlock()
st.values = make(map[interface{}]interface{})
return nil
}

// get session id of this mysql session store
func (st *MysqlSessionStore) SessionID() string {
return st.sid
}

// save mysql session values to database.
// must call this method to save values to database.
func (st *MysqlSessionStore) SessionRelease(w http.ResponseWriter) {
defer st.c.Close()
b, err := session.EncodeGob(st.values)
if err != nil {
return
}
st.c.Exec("UPDATE session set `session_data`=?, `session_expiry`=? where session_key=?",
b, time.Now().Unix(), st.sid)

}

// mysql session provider
type MysqlProvider struct {
maxlifetime int64
savePath string
}

// connect to mysql
func (mp *MysqlProvider) connectInit() *sql.DB {
db, e := sql.Open("mysql", mp.savePath)
if e != nil {
return nil
}
return db
}

// init mysql session.
// savepath is the connection string of mysql.
func (mp *MysqlProvider) SessionInit(maxlifetime int64, savePath string) error {
mp.maxlifetime = maxlifetime
mp.savePath = savePath
return nil
}

// get mysql session by sid
func (mp *MysqlProvider) SessionRead(sid string) (session.SessionStore, error) {
c := mp.connectInit()
row := c.QueryRow("select session_data from session where session_key=?", sid)
var sessiondata []byte
err := row.Scan(&sessiondata)
if err == sql.ErrNoRows {
c.Exec("insert into session(`session_key`,`session_data`,`session_expiry`) values(?,?,?)",
sid, "", time.Now().Unix())
}
var kv map[interface{}]interface{}
if len(sessiondata) == 0 {
kv = make(map[interface{}]interface{})
} else {
kv, err = session.DecodeGob(sessiondata)
if err != nil {
return nil, err
}
}
rs := &MysqlSessionStore{c: c, sid: sid, values: kv}
return rs, nil
}

// check mysql session exist
func (mp *MysqlProvider) SessionExist(sid string) bool {
c := mp.connectInit()
defer c.Close()
row := c.QueryRow("select session_data from session where session_key=?", sid)
var sessiondata []byte
err := row.Scan(&sessiondata)
if err == sql.ErrNoRows {
return false
} else {
return true
}
}

// generate new sid for mysql session
func (mp *MysqlProvider) SessionRegenerate(oldsid, sid string) (session.SessionStore, error) {
c := mp.connectInit()
row := c.QueryRow("select session_data from session where session_key=?", oldsid)
var sessiondata []byte
err := row.Scan(&sessiondata)
if err == sql.ErrNoRows {
c.Exec("insert into session(`session_key`,`session_data`,`session_expiry`) values(?,?,?)", oldsid, "", time.Now().Unix())
}
c.Exec("update session set `session_key`=? where session_key=?", sid, oldsid)
var kv map[interface{}]interface{}
if len(sessiondata) == 0 {
kv = make(map[interface{}]interface{})
} else {
kv, err = session.DecodeGob(sessiondata)
if err != nil {
return nil, err
}
}
rs := &MysqlSessionStore{c: c, sid: sid, values: kv}
return rs, nil
}

// delete mysql session by sid
func (mp *MysqlProvider) SessionDestroy(sid string) error {
c := mp.connectInit()
c.Exec("DELETE FROM session where session_key=?", sid)
c.Close()
return nil
}

// delete expired values in mysql session
func (mp *MysqlProvider) SessionGC() {
c := mp.connectInit()
c.Exec("DELETE from session where session_expiry < ?", time.Now().Unix()-mp.maxlifetime)
c.Close()
return
}

// count values in mysql session
func (mp *MysqlProvider) SessionAll() int {
c := mp.connectInit()
defer c.Close()
var total int
err := c.QueryRow("SELECT count(*) as num from session").Scan(&total)
if err != nil {
return 0
}
return total
}

func init() {
session.Register("mysql", mysqlpder)
}

+ 243
- 0
postgres/sess_postgresql.go View File

@@ -0,0 +1,243 @@
// Copyright 2013 Beego Authors
//
// Licensed under the Apache License, Version 2.0 (the "License"): you may
// not use this file except in compliance with the License. You may obtain
// a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
// License for the specific language governing permissions and limitations
// under the License.

package session

/*

beego session provider for postgresql
-------------------------------------

depends on github.com/lib/pq:

go install github.com/lib/pq


needs this table in your database:

CREATE TABLE session (
session_key char(64) NOT NULL,
session_data bytea,
session_expiry timestamp NOT NULL,
CONSTRAINT session_key PRIMARY KEY(session_key)
);


will be activated with these settings in app.conf:

SessionOn = true
SessionProvider = postgresql
SessionSavePath = "user=a password=b dbname=c sslmode=disable"
SessionName = session

*/

import (
"database/sql"
"net/http"
"sync"
"time"

_ "github.com/lib/pq"

"github.com/macaron-contrib/session"
)

var postgresqlpder = &PostgresqlProvider{}

// postgresql session store
type PostgresqlSessionStore struct {
c *sql.DB
sid string
lock sync.RWMutex
values map[interface{}]interface{}
}

// set value in postgresql session.
// it is temp value in map.
func (st *PostgresqlSessionStore) Set(key, value interface{}) error {
st.lock.Lock()
defer st.lock.Unlock()
st.values[key] = value
return nil
}

// get value from postgresql session
func (st *PostgresqlSessionStore) Get(key interface{}) interface{} {
st.lock.RLock()
defer st.lock.RUnlock()
if v, ok := st.values[key]; ok {
return v
} else {
return nil
}
}

// delete value in postgresql session
func (st *PostgresqlSessionStore) Delete(key interface{}) error {
st.lock.Lock()
defer st.lock.Unlock()
delete(st.values, key)
return nil
}

// clear all values in postgresql session
func (st *PostgresqlSessionStore) Flush() error {
st.lock.Lock()
defer st.lock.Unlock()
st.values = make(map[interface{}]interface{})
return nil
}

// get session id of this postgresql session store
func (st *PostgresqlSessionStore) SessionID() string {
return st.sid
}

// save postgresql session values to database.
// must call this method to save values to database.
func (st *PostgresqlSessionStore) SessionRelease(w http.ResponseWriter) {
defer st.c.Close()
b, err := session.EncodeGob(st.values)
if err != nil {
return
}
st.c.Exec("UPDATE session set session_data=$1, session_expiry=$2 where session_key=$3",
b, time.Now().Format(time.RFC3339), st.sid)

}

// postgresql session provider
type PostgresqlProvider struct {
maxlifetime int64
savePath string
}

// connect to postgresql
func (mp *PostgresqlProvider) connectInit() *sql.DB {
db, e := sql.Open("postgres", mp.savePath)
if e != nil {
return nil
}
return db
}

// init postgresql session.
// savepath is the connection string of postgresql.
func (mp *PostgresqlProvider) SessionInit(maxlifetime int64, savePath string) error {
mp.maxlifetime = maxlifetime
mp.savePath = savePath
return nil
}

// get postgresql session by sid
func (mp *PostgresqlProvider) SessionRead(sid string) (session.SessionStore, error) {
c := mp.connectInit()
row := c.QueryRow("select session_data from session where session_key=$1", sid)
var sessiondata []byte
err := row.Scan(&sessiondata)
if err == sql.ErrNoRows {
_, err = c.Exec("insert into session(session_key,session_data,session_expiry) values($1,$2,$3)",
sid, "", time.Now().Format(time.RFC3339))

if err != nil {
return nil, err
}
} else if err != nil {
return nil, err
}

var kv map[interface{}]interface{}
if len(sessiondata) == 0 {
kv = make(map[interface{}]interface{})
} else {
kv, err = session.DecodeGob(sessiondata)
if err != nil {
return nil, err
}
}
rs := &PostgresqlSessionStore{c: c, sid: sid, values: kv}
return rs, nil
}

// check postgresql session exist
func (mp *PostgresqlProvider) SessionExist(sid string) bool {
c := mp.connectInit()
defer c.Close()
row := c.QueryRow("select session_data from session where session_key=$1", sid)
var sessiondata []byte
err := row.Scan(&sessiondata)

if err == sql.ErrNoRows {
return false
} else {
return true
}
}

// generate new sid for postgresql session
func (mp *PostgresqlProvider) SessionRegenerate(oldsid, sid string) (session.SessionStore, error) {
c := mp.connectInit()
row := c.QueryRow("select session_data from session where session_key=$1", oldsid)
var sessiondata []byte
err := row.Scan(&sessiondata)
if err == sql.ErrNoRows {
c.Exec("insert into session(session_key,session_data,session_expiry) values($1,$2,$3)",
oldsid, "", time.Now().Format(time.RFC3339))
}
c.Exec("update session set session_key=$1 where session_key=$2", sid, oldsid)
var kv map[interface{}]interface{}
if len(sessiondata) == 0 {
kv = make(map[interface{}]interface{})
} else {
kv, err = session.DecodeGob(sessiondata)
if err != nil {
return nil, err
}
}
rs := &PostgresqlSessionStore{c: c, sid: sid, values: kv}
return rs, nil
}

// delete postgresql session by sid
func (mp *PostgresqlProvider) SessionDestroy(sid string) error {
c := mp.connectInit()
c.Exec("DELETE FROM session where session_key=$1", sid)
c.Close()
return nil
}

// delete expired values in postgresql session
func (mp *PostgresqlProvider) SessionGC() {
c := mp.connectInit()
c.Exec("DELETE from session where EXTRACT(EPOCH FROM (current_timestamp - session_expiry)) > $1", mp.maxlifetime)
c.Close()
return
}

// count values in postgresql session
func (mp *PostgresqlProvider) SessionAll() int {
c := mp.connectInit()
defer c.Close()
var total int
err := c.QueryRow("SELECT count(*) as num from session").Scan(&total)
if err != nil {
return 0
}
return total
}

func init() {
session.Register("postgresql", postgresqlpder)
}

+ 234
- 0
redis/sess_redis.go View File

@@ -0,0 +1,234 @@
// Copyright 2013 Beego Authors
//
// Licensed under the Apache License, Version 2.0 (the "License"): you may
// not use this file except in compliance with the License. You may obtain
// a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
// License for the specific language governing permissions and limitations
// under the License.

package session

import (
"net/http"
"strconv"
"strings"
"sync"

"github.com/beego/redigo/redis"

"github.com/macaron-contrib/session"
)

var redispder = &RedisProvider{}

// redis max pool size
var MAX_POOL_SIZE = 100

var redisPool chan redis.Conn

// redis session store
type RedisSessionStore struct {
p *redis.Pool
sid string
lock sync.RWMutex
values map[interface{}]interface{}
maxlifetime int64
}

// set value in redis session
func (rs *RedisSessionStore) Set(key, value interface{}) error {
rs.lock.Lock()
defer rs.lock.Unlock()
rs.values[key] = value
return nil
}

// get value in redis session
func (rs *RedisSessionStore) Get(key interface{}) interface{} {
rs.lock.RLock()
defer rs.lock.RUnlock()
if v, ok := rs.values[key]; ok {
return v
} else {
return nil
}
}

// delete value in redis session
func (rs *RedisSessionStore) Delete(key interface{}) error {
rs.lock.Lock()
defer rs.lock.Unlock()
delete(rs.values, key)
return nil
}

// clear all values in redis session
func (rs *RedisSessionStore) Flush() error {
rs.lock.Lock()
defer rs.lock.Unlock()
rs.values = make(map[interface{}]interface{})
return nil
}

// get redis session id
func (rs *RedisSessionStore) SessionID() string {
return rs.sid
}

// save session values to redis
func (rs *RedisSessionStore) SessionRelease(w http.ResponseWriter) {
c := rs.p.Get()
defer c.Close()

// if rs.values is empty, return directly
if len(rs.values) < 1 {
c.Do("DEL", rs.sid)
return
}

b, err := session.EncodeGob(rs.values)
if err != nil {
return
}

c.Do("SET", rs.sid, string(b), "EX", rs.maxlifetime)
}

// redis session provider
type RedisProvider struct {
maxlifetime int64
savePath string
poolsize int
password string
poollist *redis.Pool
}

// init redis session
// savepath like redis server addr,pool size,password
// e.g. 127.0.0.1:6379,100,astaxie
func (rp *RedisProvider) SessionInit(maxlifetime int64, savePath string) error {
rp.maxlifetime = maxlifetime
configs := strings.Split(savePath, ",")
if len(configs) > 0 {
rp.savePath = configs[0]
}
if len(configs) > 1 {
poolsize, err := strconv.Atoi(configs[1])
if err != nil || poolsize <= 0 {
rp.poolsize = MAX_POOL_SIZE
} else {
rp.poolsize = poolsize
}
} else {
rp.poolsize = MAX_POOL_SIZE
}
if len(configs) > 2 {
rp.password = configs[2]
}
rp.poollist = redis.NewPool(func() (redis.Conn, error) {
c, err := redis.Dial("tcp", rp.savePath)
if err != nil {
return nil, err
}
if rp.password != "" {
if _, err := c.Do("AUTH", rp.password); err != nil {
c.Close()
return nil, err
}
}
return c, err
}, rp.poolsize)

return rp.poollist.Get().Err()
}

// read redis session by sid
func (rp *RedisProvider) SessionRead(sid string) (session.SessionStore, error) {
c := rp.poollist.Get()
defer c.Close()

kvs, err := redis.String(c.Do("GET", sid))
var kv map[interface{}]interface{}
if len(kvs) == 0 {
kv = make(map[interface{}]interface{})
} else {
kv, err = session.DecodeGob([]byte(kvs))
if err != nil {
return nil, err
}
}

rs := &RedisSessionStore{p: rp.poollist, sid: sid, values: kv, maxlifetime: rp.maxlifetime}
return rs, nil
}

// check redis session exist by sid
func (rp *RedisProvider) SessionExist(sid string) bool {
c := rp.poollist.Get()
defer c.Close()

if existed, err := redis.Int(c.Do("EXISTS", sid)); err != nil || existed == 0 {
return false
} else {
return true
}
}

// generate new sid for redis session
func (rp *RedisProvider) SessionRegenerate(oldsid, sid string) (session.SessionStore, error) {
c := rp.poollist.Get()
defer c.Close()

if existed, _ := redis.Int(c.Do("EXISTS", oldsid)); existed == 0 {
// oldsid doesn't exists, set the new sid directly
// ignore error here, since if it return error
// the existed value will be 0
c.Do("SET", sid, "", "EX", rp.maxlifetime)
} else {
c.Do("RENAME", oldsid, sid)
c.Do("EXPIRE", sid, rp.maxlifetime)
}

kvs, err := redis.String(c.Do("GET", sid))
var kv map[interface{}]interface{}
if len(kvs) == 0 {
kv = make(map[interface{}]interface{})
} else {
kv, err = session.DecodeGob([]byte(kvs))
if err != nil {
return nil, err
}
}

rs := &RedisSessionStore{p: rp.poollist, sid: sid, values: kv, maxlifetime: rp.maxlifetime}
return rs, nil
}

// delete redis session by id
func (rp *RedisProvider) SessionDestroy(sid string) error {
c := rp.poollist.Get()
defer c.Close()

c.Do("DEL", sid)
return nil
}

// Impelment method, no used.
func (rp *RedisProvider) SessionGC() {
return
}

// @todo
func (rp *RedisProvider) SessionAll() int {
return 0
}

func init() {
session.Register("redis", redispder)
}

+ 185
- 0
sess_cookie.go View File

@@ -0,0 +1,185 @@
// Copyright 2013 Beego Authors
//
// Licensed under the Apache License, Version 2.0 (the "License"): you may
// not use this file except in compliance with the License. You may obtain
// a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
// License for the specific language governing permissions and limitations
// under the License.

package session

import (
"crypto/aes"
"crypto/cipher"
"encoding/json"
"net/http"
"net/url"
"sync"
)

var cookiepder = &CookieProvider{}

// Cookie SessionStore
type CookieSessionStore struct {
sid string
values map[interface{}]interface{} // session data
lock sync.RWMutex
}

// Set value to cookie session.
// the value are encoded as gob with hash block string.
func (st *CookieSessionStore) Set(key, value interface{}) error {
st.lock.Lock()
defer st.lock.Unlock()
st.values[key] = value
return nil
}

// Get value from cookie session
func (st *CookieSessionStore) Get(key interface{}) interface{} {
st.lock.RLock()
defer st.lock.RUnlock()
if v, ok := st.values[key]; ok {
return v
} else {
return nil
}
}

// Delete value in cookie session
func (st *CookieSessionStore) Delete(key interface{}) error {
st.lock.Lock()
defer st.lock.Unlock()
delete(st.values, key)
return nil
}

// Clean all values in cookie session
func (st *CookieSessionStore) Flush() error {
st.lock.Lock()
defer st.lock.Unlock()
st.values = make(map[interface{}]interface{})
return nil
}

// Return id of this cookie session
func (st *CookieSessionStore) SessionID() string {
return st.sid
}

// Write cookie session to http response cookie
func (st *CookieSessionStore) SessionRelease(w http.ResponseWriter) {
str, err := encodeCookie(cookiepder.block,
cookiepder.config.SecurityKey,
cookiepder.config.SecurityName,
st.values)
if err != nil {
return
}
cookie := &http.Cookie{Name: cookiepder.config.CookieName,
Value: url.QueryEscape(str),
Path: "/",
HttpOnly: true,
Secure: cookiepder.config.Secure,
MaxAge: cookiepder.config.Maxage}
http.SetCookie(w, cookie)
return
}

type cookieConfig struct {
SecurityKey string `json:"securityKey"`
BlockKey string `json:"blockKey"`
SecurityName string `json:"securityName"`
CookieName string `json:"cookieName"`
Secure bool `json:"secure"`
Maxage int `json:"maxage"`
}

// Cookie session provider
type CookieProvider struct {
maxlifetime int64
config *cookieConfig
block cipher.Block
}

// Init cookie session provider with max lifetime and config json.
// maxlifetime is ignored.
// json config:
// securityKey - hash string
// blockKey - gob encode hash string. it's saved as aes crypto.
// securityName - recognized name in encoded cookie string
// cookieName - cookie name
// maxage - cookie max life time.
func (pder *CookieProvider) SessionInit(maxlifetime int64, config string) error {
pder.config = &cookieConfig{}
err := json.Unmarshal([]byte(config), pder.config)
if err != nil {
return err
}
if pder.config.BlockKey == "" {
pder.config.BlockKey = string(generateRandomKey(16))
}
if pder.config.SecurityName == "" {
pder.config.SecurityName = string(generateRandomKey(20))
}
pder.block, err = aes.NewCipher([]byte(pder.config.BlockKey))
if err != nil {
return err
}
pder.maxlifetime = maxlifetime
return nil
}

// Get SessionStore in cooke.
// decode cooke string to map and put into SessionStore with sid.
func (pder *CookieProvider) SessionRead(sid string) (SessionStore, error) {
maps, _ := decodeCookie(pder.block,
pder.config.SecurityKey,
pder.config.SecurityName,
sid, pder.maxlifetime)
if maps == nil {
maps = make(map[interface{}]interface{})
}
rs := &CookieSessionStore{sid: sid, values: maps}
return rs, nil
}

// Cookie session is always existed
func (pder *CookieProvider) SessionExist(sid string) bool {
return true
}

// Implement method, no used.
func (pder *CookieProvider) SessionRegenerate(oldsid, sid string) (SessionStore, error) {
return nil, nil
}

// Implement method, no used.
func (pder *CookieProvider) SessionDestroy(sid string) error {
return nil
}

// Implement method, no used.
func (pder *CookieProvider) SessionGC() {
return
}

// Implement method, return 0.
func (pder *CookieProvider) SessionAll() int {
return 0
}

// Implement method, no used.
func (pder *CookieProvider) SessionUpdate(sid string) error {
return nil
}

func init() {
Register("cookie", cookiepder)
}

+ 52
- 0
sess_cookie_test.go View File

@@ -0,0 +1,52 @@
// Copyright 2013 Beego Authors
//
// Licensed under the Apache License, Version 2.0 (the "License"): you may
// not use this file except in compliance with the License. You may obtain
// a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
// License for the specific language governing permissions and limitations
// under the License.

package session

import (
"net/http"
"net/http/httptest"
"strings"
"testing"
)

func TestCookie(t *testing.T) {
config := `{"cookieName":"gosessionid","enableSetCookie":false,"gclifetime":3600,"ProviderConfig":"{\"cookieName\":\"gosessionid\",\"securityKey\":\"beegocookiehashkey\"}"}`
globalSessions, err := NewManager("cookie", config)
if err != nil {
t.Fatal("init cookie session err", err)
}
r, _ := http.NewRequest("GET", "/", nil)
w := httptest.NewRecorder()
sess := globalSessions.SessionStart(w, r)
err = sess.Set("username", "astaxie")
if err != nil {
t.Fatal("set error,", err)
}
if username := sess.Get("username"); username != "astaxie" {
t.Fatal("get username error")
}
sess.SessionRelease(w)
if cookiestr := w.Header().Get("Set-Cookie"); cookiestr == "" {
t.Fatal("setcookie error")
} else {
parts := strings.Split(strings.TrimSpace(cookiestr), ";")
for k, v := range parts {
nameval := strings.Split(v, "=")
if k == 0 && nameval[0] != "gosessionid" {
t.Fatal("error")
}
}
}
}

+ 280
- 0
sess_file.go View File

@@ -0,0 +1,280 @@
// Copyright 2013 Beego Authors
//
// Licensed under the Apache License, Version 2.0 (the "License"): you may
// not use this file except in compliance with the License. You may obtain
// a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
// License for the specific language governing permissions and limitations
// under the License.

package session

import (
"errors"
"fmt"
"io"
"io/ioutil"
"net/http"
"os"
"path"
"path/filepath"
"sync"
"time"
)

var (
filepder = &FileProvider{}
gcmaxlifetime int64
)

// File session store
type FileSessionStore struct {
f *os.File
sid string
lock sync.RWMutex
values map[interface{}]interface{}
}

// Set value to file session
func (fs *FileSessionStore) Set(key, value interface{}) error {
fs.lock.Lock()
defer fs.lock.Unlock()
fs.values[key] = value
return nil
}

// Get value from file session
func (fs *FileSessionStore) Get(key interface{}) interface{} {
fs.lock.RLock()
defer fs.lock.RUnlock()
if v, ok := fs.values[key]; ok {
return v
} else {
return nil
}
}

// Delete value in file session by given key
func (fs *FileSessionStore) Delete(key interface{}) error {
fs.lock.Lock()
defer fs.lock.Unlock()
delete(fs.values, key)
return nil
}

// Clean all values in file session
func (fs *FileSessionStore) Flush() error {
fs.lock.Lock()
defer fs.lock.Unlock()
fs.values = make(map[interface{}]interface{})
return nil
}

// Get file session store id
func (fs *FileSessionStore) SessionID() string {
return fs.sid
}

// Write file session to local file with Gob string
func (fs *FileSessionStore) SessionRelease(w http.ResponseWriter) {
defer fs.f.Close()
b, err := EncodeGob(fs.values)
if err != nil {
return
}
fs.f.Truncate(0)
fs.f.Seek(0, 0)
fs.f.Write(b)
}

// File session provider
type FileProvider struct {
lock sync.RWMutex
maxlifetime int64
savePath string
}

// Init file session provider.
// savePath sets the session files path.
func (fp *FileProvider) SessionInit(maxlifetime int64, savePath string) error {
fp.maxlifetime = maxlifetime
fp.savePath = savePath
return nil
}

// Read file session by sid.
// if file is not exist, create it.
// the file path is generated from sid string.
func (fp *FileProvider) SessionRead(sid string) (SessionStore, error) {
filepder.lock.Lock()
defer filepder.lock.Unlock()

err := os.MkdirAll(path.Join(fp.savePath, string(sid[0]), string(sid[1])), 0777)
if err != nil {
println(err.Error())
}
_, err = os.Stat(path.Join(fp.savePath, string(sid[0]), string(sid[1]), sid))
var f *os.File
if err == nil {
f, err = os.OpenFile(path.Join(fp.savePath, string(sid[0]), string(sid[1]), sid), os.O_RDWR, 0777)
} else if os.IsNotExist(err) {
f, err = os.Create(path.Join(fp.savePath, string(sid[0]), string(sid[1]), sid))
} else {
return nil, err
}
os.Chtimes(path.Join(fp.savePath, string(sid[0]), string(sid[1]), sid), time.Now(), time.Now())
var kv map[interface{}]interface{}
b, err := ioutil.ReadAll(f)
if err != nil {
return nil, err
}
if len(b) == 0 {
kv = make(map[interface{}]interface{})
} else {
kv, err = DecodeGob(b)
if err != nil {
return nil, err
}
}
f.Close()
f, err = os.OpenFile(path.Join(fp.savePath, string(sid[0]), string(sid[1]), sid), os.O_WRONLY|os.O_CREATE, 0777)
ss := &FileSessionStore{f: f, sid: sid, values: kv}
return ss, nil
}

// Check file session exist.
// it checkes the file named from sid exist or not.
func (fp *FileProvider) SessionExist(sid string) bool {
filepder.lock.Lock()
defer filepder.lock.Unlock()

_, err := os.Stat(path.Join(fp.savePath, string(sid[0]), string(sid[1]), sid))
if err == nil {
return true
} else {
return false
}
}

// Remove all files in this save path
func (fp *FileProvider) SessionDestroy(sid string) error {
filepder.lock.Lock()
defer filepder.lock.Unlock()
os.Remove(path.Join(fp.savePath, string(sid[0]), string(sid[1]), sid))
return nil
}

// Recycle files in save path
func (fp *FileProvider) SessionGC() {
filepder.lock.Lock()
defer filepder.lock.Unlock()

gcmaxlifetime = fp.maxlifetime
filepath.Walk(fp.savePath, gcpath)
}

// Get active file session number.
// it walks save path to count files.
func (fp *FileProvider) SessionAll() int {
a := &activeSession{}
err := filepath.Walk(fp.savePath, func(path string, f os.FileInfo, err error) error {
return a.visit(path, f, err)
})
if err != nil {
fmt.Printf("filepath.Walk() returned %v\n", err)
return 0
}
return a.total
}

// Generate new sid for file session.
// it delete old file and create new file named from new sid.
func (fp *FileProvider) SessionRegenerate(oldsid, sid string) (SessionStore, error) {
filepder.lock.Lock()
defer filepder.lock.Unlock()

err := os.MkdirAll(path.Join(fp.savePath, string(oldsid[0]), string(oldsid[1])), 0777)
if err != nil {
println(err.Error())
}
err = os.MkdirAll(path.Join(fp.savePath, string(sid[0]), string(sid[1])), 0777)
if err != nil {
println(err.Error())
}
_, err = os.Stat(path.Join(fp.savePath, string(sid[0]), string(sid[1]), sid))
var newf *os.File
if err == nil {
return nil, errors.New("newsid exist")
} else if os.IsNotExist(err) {
newf, err = os.Create(path.Join(fp.savePath, string(sid[0]), string(sid[1]), sid))
}

_, err = os.Stat(path.Join(fp.savePath, string(oldsid[0]), string(oldsid[1]), oldsid))
var f *os.File
if err == nil {
f, err = os.OpenFile(path.Join(fp.savePath, string(oldsid[0]), string(oldsid[1]), oldsid), os.O_RDWR, 0777)
io.Copy(newf, f)
} else if os.IsNotExist(err) {
newf, err = os.Create(path.Join(fp.savePath, string(sid[0]), string(sid[1]), sid))
} else {
return nil, err
}
f.Close()
os.Remove(path.Join(fp.savePath, string(oldsid[0]), string(oldsid[1])))
os.Chtimes(path.Join(fp.savePath, string(sid[0]), string(sid[1]), sid), time.Now(), time.Now())
var kv map[interface{}]interface{}
b, err := ioutil.ReadAll(newf)
if err != nil {
return nil, err
}
if len(b) == 0 {
kv = make(map[interface{}]interface{})
} else {
kv, err = DecodeGob(b)
if err != nil {
return nil, err
}
}

newf, err = os.OpenFile(path.Join(fp.savePath, string(sid[0]), string(sid[1]), sid), os.O_WRONLY|os.O_CREATE, 0777)
ss := &FileSessionStore{f: newf, sid: sid, values: kv}
return ss, nil
}

// remove file in save path if expired
func gcpath(path string, info os.FileInfo, err error) error {
if err != nil {
return err
}
if info.IsDir() {
return nil
}
if (info.ModTime().Unix() + gcmaxlifetime) < time.Now().Unix() {
os.Remove(path)
}
return nil
}

type activeSession struct {
total int
}

func (self *activeSession) visit(paths string, f os.FileInfo, err error) error {
if err != nil {
return err
}
if f.IsDir() {
return nil
}
self.total = self.total + 1
return nil
}

func init() {
Register("file", filepder)
}

+ 199
- 0
sess_mem.go View File

@@ -0,0 +1,199 @@
// Copyright 2013 Beego Authors
//
// Licensed under the Apache License, Version 2.0 (the "License"): you may
// not use this file except in compliance with the License. You may obtain
// a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
// License for the specific language governing permissions and limitations
// under the License.
package session
import (
"container/list"
"net/http"
"sync"
"time"
)
var mempder = &MemProvider{list: list.New(), sessions: make(map[string]*list.Element)}
// memory session store.
// it saved sessions in a map in memory.
type MemSessionStore struct {
sid string //session id
timeAccessed time.Time //last access time
value map[interface{}]interface{} //session store
lock sync.RWMutex
}
// set value to memory session
func (st *MemSessionStore) Set(key, value interface{}) error {
st.lock.Lock()
defer st.lock.Unlock()
st.value[key] = value
return nil
}
// get value from memory session by key
func (st *MemSessionStore) Get(key interface{}) interface{} {
st.lock.RLock()
defer st.lock.RUnlock()
if v, ok := st.value[key]; ok {
return v
} else {
return nil
}
}
// delete in memory session by key
func (st *MemSessionStore) Delete(key interface{}) error {
st.lock.Lock()
defer st.lock.Unlock()
delete(st.value, key)
return nil
}
// clear all values in memory session
func (st *MemSessionStore) Flush() error {
st.lock.Lock()
defer st.lock.Unlock()
st.value = make(map[interface{}]interface{})
return nil
}
// get this id of memory session store
func (st *MemSessionStore) SessionID() string {
return st.sid
}
// Implement method, no used.
func (st *MemSessionStore) SessionRelease(w http.ResponseWriter) {
}
type MemProvider struct {
lock sync.RWMutex // locker
sessions map[string]*list.Element // map in memory
list *list.List // for gc
maxlifetime int64
savePath string
}
// init memory session
func (pder *MemProvider) SessionInit(maxlifetime int64, savePath string) error {
pder.maxlifetime = maxlifetime
pder.savePath = savePath
return nil
}
// get memory session store by sid
func (pder *MemProvider) SessionRead(sid string) (SessionStore, error) {
pder.lock.RLock()
if element, ok := pder.sessions[sid]; ok {
go pder.SessionUpdate(sid)
pder.lock.RUnlock()
return element.Value.(*MemSessionStore), nil
} else {
pder.lock.RUnlock()
pder.lock.Lock()
newsess := &MemSessionStore{sid: sid, timeAccessed: time.Now(), value: make(map[interface{}]interface{})}
element := pder.list.PushBack(newsess)
pder.sessions[sid] = element
pder.lock.Unlock()
return newsess, nil
}
}
// check session store exist in memory session by sid
func (pder *MemProvider) SessionExist(sid string) bool {
pder.lock.RLock()
defer pder.lock.RUnlock()
if _, ok := pder.sessions[sid]; ok {
return true
} else {
return false
}
}
// generate new sid for session store in memory session
func (pder *MemProvider) SessionRegenerate(oldsid, sid string) (SessionStore, error) {
pder.lock.RLock()
if element, ok := pder.sessions[oldsid]; ok {
go pder.SessionUpdate(oldsid)
pder.lock.RUnlock()
pder.lock.Lock()
element.Value.(*MemSessionStore).sid = sid
pder.sessions[sid] = element
delete(pder.sessions, oldsid)
pder.lock.Unlock()
return element.Value.(*MemSessionStore), nil
} else {
pder.lock.RUnlock()
pder.lock.Lock()
newsess := &MemSessionStore{sid: sid, timeAccessed: time.Now(), value: make(map[interface{}]interface{})}
element := pder.list.PushBack(newsess)
pder.sessions[sid] = element
pder.lock.Unlock()
return newsess, nil
}
}
// delete session store in memory session by id
func (pder *MemProvider) SessionDestroy(sid string) error {
pder.lock.Lock()
defer pder.lock.Unlock()
if element, ok := pder.sessions[sid]; ok {
delete(pder.sessions, sid)
pder.list.Remove(element)
return nil
}
return nil
}
// clean expired session stores in memory session
func (pder *MemProvider) SessionGC() {
pder.lock.RLock()
for {
element := pder.list.Back()
if element == nil {
break
}
if (element.Value.(*MemSessionStore).timeAccessed.Unix() + pder.maxlifetime) < time.Now().Unix() {
pder.lock.RUnlock()
pder.lock.Lock()
pder.list.Remove(element)
delete(pder.sessions, element.Value.(*MemSessionStore).sid)
pder.lock.Unlock()
pder.lock.RLock()
} else {
break
}
}
pder.lock.RUnlock()
}