You've already forked nyx
mirror of
https://github.com/rls-moe/nyx
synced 2025-08-20 12:04:16 +00:00
MVP, no mod tools or anything but it works
This commit is contained in:
201
vendor/github.com/icza/session/LICENSE
generated
vendored
Normal file
201
vendor/github.com/icza/session/LICENSE
generated
vendored
Normal file
@@ -0,0 +1,201 @@
|
||||
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:
|
||||
|
||||
(a) You must give any other recipients of the Work or
|
||||
Derivative Works a copy of this License; and
|
||||
|
||||
(b) You must cause any modified files to carry prominent notices
|
||||
stating that You changed the files; and
|
||||
|
||||
(c) 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
|
||||
|
||||
(d) 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.
|
114
vendor/github.com/icza/session/README.md
generated
vendored
Normal file
114
vendor/github.com/icza/session/README.md
generated
vendored
Normal file
@@ -0,0 +1,114 @@
|
||||
# Session
|
||||
|
||||
[](https://travis-ci.org/icza/session)
|
||||
[](https://godoc.org/github.com/icza/session)
|
||||
[](https://goreportcard.com/report/github.com/icza/session)
|
||||
[](https://codecov.io/gh/icza/session)
|
||||
|
||||
The [Go](https://golang.org/) standard library includes a nice [http server](https://golang.org/pkg/net/http/), but unfortunately it lacks a very basic and important feature: _HTTP session management_.
|
||||
|
||||
This package provides an easy-to-use, extensible and secure session implementation and management. Package documentation can be found and godoc.org:
|
||||
|
||||
https://godoc.org/github.com/icza/session
|
||||
|
||||
This is "just" an HTTP session implementation and management, you can use it as-is, or with any existing Go web toolkits and frameworks.
|
||||
|
||||
## Overview
|
||||
|
||||
There are 3 key _players_ in the package:
|
||||
|
||||
- **`Session`** is the (HTTP) session interface. We can use it to store and retrieve constant and variable attributes from it.
|
||||
- **`Store`** is a session store interface which is responsible to store sessions and make them retrievable by their IDs at the server side.
|
||||
- **`Manager`** is a session manager interface which is responsible to acquire a `Session` from an (incoming) HTTP request, and to add a `Session` to an HTTP response to let the client know about the session. A `Manager` has a backing `Store` which is responsible to manage `Session` values at server side.
|
||||
|
||||
_Players_ of this package are represented by interfaces, and various implementations are provided for all these players.
|
||||
You are not bound by the provided implementations, feel free to provide your own implementations for any of the players.
|
||||
|
||||
## Usage
|
||||
|
||||
Usage can't be simpler than this. To get the current session associated with the [http.Request](https://golang.org/pkg/net/http/#Request):
|
||||
|
||||
sess := session.Get(r)
|
||||
if sess == nil {
|
||||
// No session (yet)
|
||||
} else {
|
||||
// We have a session, use it
|
||||
}
|
||||
|
||||
To create a new session (e.g. on a successful login) and add it to an [http.ResponseWriter](https://golang.org/pkg/net/http/#ResponseWriter) (to let the client know about the session):
|
||||
|
||||
sess := session.NewSession()
|
||||
session.Add(sess, w)
|
||||
|
||||
Let's see a more advanced session creation: let's provide a constant attribute (for the lifetime of the session) and an initial, variable attribute:
|
||||
|
||||
sess := session.NewSessionOptions(&session.SessOptions{
|
||||
CAttrs: map[string]interface{}{"UserName": userName},
|
||||
Attrs: map[string]interface{}{"Count": 1},
|
||||
})
|
||||
|
||||
And to access these attributes and change value of `"Count"`:
|
||||
|
||||
userName := sess.CAttr("UserName")
|
||||
count := sess.Attr("Count").(int) // Type assertion, you might wanna check if it succeeds
|
||||
sess.SetAttr("Count", count+1) // Increment count
|
||||
|
||||
(Of course variable attributes can be added later on too with `Session.SetAttr()`, not just at session creation.)
|
||||
|
||||
To remove a session (e.g. on logout):
|
||||
|
||||
session.Remove(sess, w)
|
||||
|
||||
Check out the [session demo application](https://github.com/icza/session/blob/master/session_demo/session_demo.go) which shows all these in action.
|
||||
|
||||
## Google App Engine support
|
||||
|
||||
The package provides support for Google App Engine (GAE) platform.
|
||||
|
||||
The documentation doesn't include it (due to the `+build appengine` build constraint), but here it is: [gae_memcache_store.go](https://github.com/icza/session/blob/master/gae_memcache_store.go)
|
||||
|
||||
The implementation stores sessions in the Memcache and also saves sessions in the Datastore as a backup
|
||||
in case data would be removed from the Memcache. This behaviour is optional, Datastore can be disabled completely.
|
||||
You can also choose whether saving to Datastore happens synchronously (in the same goroutine)
|
||||
or asynchronously (in another goroutine), resulting in faster response times.
|
||||
|
||||
We can use `NewMemcacheStore()` and `NewMemcacheStoreOptions()` functions to create a session Store implementation
|
||||
which stores sessions in GAE's Memcache. Important to note that since accessing the Memcache relies on
|
||||
Appengine Context which is bound to an `http.Request`, the returned Store can only be used for the lifetime of a request!
|
||||
Note that the Store will automatically "flush" sessions accessed from it when the Store is closed,
|
||||
so it is very important to close the Store at the end of your request; this is usually done by closing
|
||||
the session manager to which you passed the store (preferably with the defer statement).
|
||||
|
||||
So in each request handling we have to create a new session manager using a new Store, and we can use the session manager
|
||||
to do session-related tasks, something like this:
|
||||
|
||||
ctx := appengine.NewContext(r)
|
||||
sessmgr := session.NewCookieManager(session.NewMemcacheStore(ctx))
|
||||
defer sessmgr.Close() // This will ensure changes made to the session are auto-saved
|
||||
// in Memcache (and optionally in the Datastore).
|
||||
|
||||
sess := sessmgr.Get(r) // Get current session
|
||||
if sess != nil {
|
||||
// Session exists, do something with it.
|
||||
ctx.Infof("Count: %v", sess.Attr("Count"))
|
||||
} else {
|
||||
// No session yet, let's create one and add it:
|
||||
sess = session.NewSession()
|
||||
sess.SetAttr("Count", 1)
|
||||
sessmgr.Add(sess, w)
|
||||
}
|
||||
|
||||
Expired sessions are not automatically removed from the Datastore. To remove expired sessions, the package
|
||||
provides a `PurgeExpiredSessFromDSFunc()` function which returns an [http.HandlerFunc](https://golang.org/pkg/net/http/#HandlerFunc).
|
||||
It is recommended to register the returned handler function to a path which then can be defined
|
||||
as a cron job to be called periodically, e.g. in every 30 minutes or so (your choice).
|
||||
As cron handlers may run up to 10 minutes, the returned handler will stop at 8 minutes
|
||||
to complete safely even if there are more expired, undeleted sessions.
|
||||
It can be registered like this:
|
||||
|
||||
http.HandleFunc("/demo/purge", session.PurgeExpiredSessFromDSFunc(""))
|
||||
|
||||
Check out the GAE session demo application which shows how it can be used.
|
||||
[cron.yaml](https://github.com/icza/session/blob/master/gae_session_demo/cron.yaml) file of the demo shows how a cron job can be defined to purge expired sessions.
|
||||
|
||||
Check out the [GAE session demo application](https://github.com/icza/session/blob/master/gae_session_demo/gae_session_demo.go) which shows how to use this in action.
|
123
vendor/github.com/icza/session/cookie_manager.go
generated
vendored
Normal file
123
vendor/github.com/icza/session/cookie_manager.go
generated
vendored
Normal file
@@ -0,0 +1,123 @@
|
||||
/*
|
||||
|
||||
A secure, cookie based session Manager implementation.
|
||||
|
||||
*/
|
||||
|
||||
package session
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"time"
|
||||
)
|
||||
|
||||
// CookieManager is a secure, cookie based session Manager implementation.
|
||||
// Only the session ID is transmitted / stored at the clients, and it is managed using cookies.
|
||||
type CookieManager struct {
|
||||
store Store // Backing Store
|
||||
|
||||
sessIDCookieName string // Name of the cookie used for storing the session id
|
||||
cookieSecure bool // Tells if session ID cookies are to be sent only over HTTPS
|
||||
cookieMaxAgeSec int // Max age for session ID cookies in seconds
|
||||
cookiePath string // Cookie path to use
|
||||
}
|
||||
|
||||
// CookieMngrOptions defines options that may be passed when creating a new CookieManager.
|
||||
// All fields are optional; default value will be used for any field that has the zero value.
|
||||
type CookieMngrOptions struct {
|
||||
// Name of the cookie used for storing the session id; default value is "sessid"
|
||||
SessIDCookieName string
|
||||
|
||||
// Tells if session ID cookies are allowed to be sent over unsecure HTTP too (else only HTTPS);
|
||||
// default value is false (only HTTPS)
|
||||
AllowHTTP bool
|
||||
|
||||
// Max age for session ID cookies; default value is 30 days
|
||||
CookieMaxAge time.Duration
|
||||
|
||||
// Cookie path to use; default value is the root: "/"
|
||||
CookiePath string
|
||||
}
|
||||
|
||||
// Pointer to zero value of CookieMngrOptions to be reused for efficiency.
|
||||
var zeroCookieMngrOptions = new(CookieMngrOptions)
|
||||
|
||||
// NewCookieManager creates a new, cookie based session Manager with default options.
|
||||
// Default values of options are listed in the CookieMngrOptions type.
|
||||
func NewCookieManager(store Store) Manager {
|
||||
return NewCookieManagerOptions(store, zeroCookieMngrOptions)
|
||||
}
|
||||
|
||||
// NewCookieManagerOptions creates a new, cookie based session Manager with the specified options.
|
||||
func NewCookieManagerOptions(store Store, o *CookieMngrOptions) Manager {
|
||||
m := &CookieManager{
|
||||
store: store,
|
||||
cookieSecure: !o.AllowHTTP,
|
||||
sessIDCookieName: o.SessIDCookieName,
|
||||
cookiePath: o.CookiePath,
|
||||
}
|
||||
|
||||
if m.sessIDCookieName == "" {
|
||||
m.sessIDCookieName = "sessid"
|
||||
}
|
||||
if o.CookieMaxAge == 0 {
|
||||
m.cookieMaxAgeSec = 30 * 24 * 60 * 60 // 30 days max age
|
||||
} else {
|
||||
m.cookieMaxAgeSec = int(o.CookieMaxAge.Seconds())
|
||||
}
|
||||
if m.cookiePath == "" {
|
||||
m.cookiePath = "/"
|
||||
}
|
||||
|
||||
return m
|
||||
}
|
||||
|
||||
// Get is to implement Manager.Get().
|
||||
func (m *CookieManager) Get(r *http.Request) Session {
|
||||
c, err := r.Cookie(m.sessIDCookieName)
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
return m.store.Get(c.Value)
|
||||
}
|
||||
|
||||
// Add is to implement Manager.Add().
|
||||
func (m *CookieManager) Add(sess Session, w http.ResponseWriter) {
|
||||
// HttpOnly: do not allow non-HTTP access to it (like javascript) to prevent stealing it...
|
||||
// Secure: only send it over HTTPS
|
||||
// MaxAge: to specify the max age of the cookie in seconds, else it's a session cookie and gets deleted after the browser is closed.
|
||||
|
||||
c := http.Cookie{
|
||||
Name: m.sessIDCookieName,
|
||||
Value: sess.ID(),
|
||||
Path: m.cookiePath,
|
||||
HttpOnly: true,
|
||||
Secure: m.cookieSecure,
|
||||
MaxAge: m.cookieMaxAgeSec,
|
||||
}
|
||||
http.SetCookie(w, &c)
|
||||
|
||||
m.store.Add(sess)
|
||||
}
|
||||
|
||||
// Remove is to implement Manager.Remove().
|
||||
func (m *CookieManager) Remove(sess Session, w http.ResponseWriter) {
|
||||
// Set the cookie with empty value and 0 max age
|
||||
c := http.Cookie{
|
||||
Name: m.sessIDCookieName,
|
||||
Value: "",
|
||||
Path: m.cookiePath,
|
||||
HttpOnly: true,
|
||||
Secure: m.cookieSecure,
|
||||
MaxAge: -1, // MaxAge<0 means delete cookie now, equivalently 'Max-Age: 0'
|
||||
}
|
||||
http.SetCookie(w, &c)
|
||||
|
||||
m.store.Remove(sess)
|
||||
}
|
||||
|
||||
// Close is to implement Manager.Close().
|
||||
func (m *CookieManager) Close() {
|
||||
m.store.Close()
|
||||
}
|
117
vendor/github.com/icza/session/doc.go
generated
vendored
Normal file
117
vendor/github.com/icza/session/doc.go
generated
vendored
Normal file
@@ -0,0 +1,117 @@
|
||||
/*
|
||||
|
||||
Package session provides an easy-to-use, extensible and secure HTTP session implementation and management.
|
||||
|
||||
This is "just" an HTTP session implementation and management, you can use it as-is, or with any existing Go web toolkits and frameworks.
|
||||
Package documentation can be found and godoc.org:
|
||||
|
||||
https://godoc.org/github.com/icza/session
|
||||
|
||||
Overview
|
||||
|
||||
There are 3 key players in the package:
|
||||
|
||||
- Session is the (HTTP) session interface. We can use it to store and retrieve constant and variable attributes from it.
|
||||
|
||||
- Store is a session store interface which is responsible to store sessions and make them retrievable by their IDs at the server side.
|
||||
|
||||
- Manager is a session manager interface which is responsible to acquire a Session from an (incoming) HTTP request, and to add a Session to an HTTP response to let the client know about the session. A Manager has a backing Store which is responsible to manage Session values at server side.
|
||||
|
||||
Players of this package are represented by interfaces, and various implementations are provided for all these players.
|
||||
You are not bound by the provided implementations, feel free to provide your own implementations for any of the players.
|
||||
|
||||
Usage
|
||||
|
||||
Usage can't be simpler than this. To get the current session associated with the http.Request:
|
||||
|
||||
sess := session.Get(r)
|
||||
if sess == nil {
|
||||
// No session (yet)
|
||||
} else {
|
||||
// We have a session, use it
|
||||
}
|
||||
|
||||
To create a new session (e.g. on a successful login) and add it to an http.ResponseWriter (to let the client know about the session):
|
||||
|
||||
sess := session.NewSession()
|
||||
session.Add(sess, w)
|
||||
|
||||
Let's see a more advanced session creation: let's provide a constant attribute (for the lifetime of the session) and an initial, variable attribute:
|
||||
|
||||
sess := session.NewSessionOptions(&session.SessOptions{
|
||||
CAttrs: map[string]interface{}{"UserName": userName},
|
||||
Attrs: map[string]interface{}{"Count": 1},
|
||||
})
|
||||
|
||||
And to access these attributes and change value of "Count":
|
||||
|
||||
userName := sess.CAttr("UserName")
|
||||
count := sess.Attr("Count").(int) // Type assertion, you might wanna check if it succeeds
|
||||
sess.SetAttr("Count", count+1) // Increment count
|
||||
|
||||
(Of course variable attributes can be added later on too with Session.SetAttr(), not just at session creation.)
|
||||
|
||||
To remove a session (e.g. on logout):
|
||||
|
||||
session.Remove(sess, w)
|
||||
|
||||
Check out the session demo application which shows all these in action:
|
||||
|
||||
https://github.com/icza/session/blob/master/session_demo/session_demo.go
|
||||
|
||||
Google App Engine support
|
||||
|
||||
The package provides support for Google App Engine (GAE) platform.
|
||||
|
||||
The documentation doesn't include it (due to the '+build appengine' build constraint), but here it is:
|
||||
|
||||
https://github.com/icza/session/blob/master/gae_memcache_store.go
|
||||
|
||||
The implementation stores sessions in the Memcache and also saves sessions in the Datastore as a backup
|
||||
in case data would be removed from the Memcache. This behaviour is optional, Datastore can be disabled completely.
|
||||
You can also choose whether saving to Datastore happens synchronously (in the same goroutine)
|
||||
or asynchronously (in another goroutine), resulting in faster response times.
|
||||
|
||||
We can use NewMemcacheStore() and NewMemcacheStoreOptions() functions to create a session Store implementation
|
||||
which stores sessions in GAE's Memcache. Important to note that since accessing the Memcache relies on
|
||||
Appengine Context which is bound to an http.Request, the returned Store can only be used for the lifetime of a request!
|
||||
Note that the Store will automatically "flush" sessions accessed from it when the Store is closed,
|
||||
so it is very important to close the Store at the end of your request; this is usually done by closing
|
||||
the session manager to which you passed the store (preferably with the defer statement).
|
||||
|
||||
So in each request handling we have to create a new session manager using a new Store, and we can use the session manager
|
||||
to do session-related tasks, something like this:
|
||||
|
||||
ctx := appengine.NewContext(r)
|
||||
sessmgr := session.NewCookieManager(session.NewMemcacheStore(ctx))
|
||||
defer sessmgr.Close() // This will ensure changes made to the session are auto-saved
|
||||
// in Memcache (and optionally in the Datastore).
|
||||
|
||||
sess := sessmgr.Get(r) // Get current session
|
||||
if sess != nil {
|
||||
// Session exists, do something with it.
|
||||
ctx.Infof("Count: %v", sess.Attr("Count"))
|
||||
} else {
|
||||
// No session yet, let's create one and add it:
|
||||
sess = session.NewSession()
|
||||
sess.SetAttr("Count", 1)
|
||||
sessmgr.Add(sess, w)
|
||||
}
|
||||
|
||||
Expired sessions are not automatically removed from the Datastore. To remove expired sessions, the package
|
||||
provides a PurgeExpiredSessFromDSFunc() function which returns an http.HandlerFunc.
|
||||
It is recommended to register the returned handler function to a path which then can be defined
|
||||
as a cron job to be called periodically, e.g. in every 30 minutes or so (your choice).
|
||||
As cron handlers may run up to 10 minutes, the returned handler will stop at 8 minutes
|
||||
to complete safely even if there are more expired, undeleted sessions.
|
||||
It can be registered like this:
|
||||
|
||||
http.HandleFunc("/demo/purge", session.PurgeExpiredSessFromDSFunc(""))
|
||||
|
||||
Check out the GAE session demo application which shows how it can be used.
|
||||
cron.yaml file of the demo shows how a cron job can be defined to purge expired sessions.
|
||||
|
||||
https://github.com/icza/session/blob/master/gae_session_demo/gae_session_demo.go
|
||||
|
||||
*/
|
||||
package session
|
375
vendor/github.com/icza/session/gae_memcache_store.go
generated
vendored
Normal file
375
vendor/github.com/icza/session/gae_memcache_store.go
generated
vendored
Normal file
@@ -0,0 +1,375 @@
|
||||
// +build appengine
|
||||
|
||||
/*
|
||||
|
||||
A Google App Engine Memcache session store implementation.
|
||||
|
||||
The implementation stores sessions in the Memcache and also saves sessions to the Datastore as a backup
|
||||
in case data would be removed from the Memcache. This behaviour is optional, Datastore can be disabled completely.
|
||||
You can also choose whether saving to Datastore happens synchronously (in the same goroutine)
|
||||
or asynchronously (in another goroutine).
|
||||
|
||||
Limitations based on GAE Memcache:
|
||||
|
||||
- Since session ids are used in the Memcache keys, session ids can't be longer than 250 chars (bytes, but with Base64 charset it's the same).
|
||||
If you also specify a key prefix (in MemcacheStoreOptions), that also counts into it.
|
||||
|
||||
- The size of a Session cannot be larger than 1 MB (marshalled into a byte slice).
|
||||
|
||||
Note that the Store will automatically "flush" sessions accessed from it when the Store is closed,
|
||||
so it is very important to close the Store at the end of your request; this is usually done by closing
|
||||
the session manager to which you passed the store (preferably with the defer statement).
|
||||
|
||||
Check out the GAE session demo application which shows how to use it properly:
|
||||
|
||||
https://github.com/icza/session/blob/master/gae_session_demo/session_demo.go
|
||||
|
||||
*/
|
||||
|
||||
package session
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"appengine"
|
||||
"appengine/datastore"
|
||||
"appengine/memcache"
|
||||
)
|
||||
|
||||
// A Google App Engine Memcache session store implementation.
|
||||
type memcacheStore struct {
|
||||
ctx appengine.Context // Appengine context used when accessing the Memcache
|
||||
|
||||
keyPrefix string // Prefix to use in front of session ids to construct Memcache key
|
||||
retries int // Number of retries to perform in case of general Memcache failures
|
||||
|
||||
codec memcache.Codec // Codec used to marshal and unmarshal a Session to a byte slice
|
||||
|
||||
onlyMemcache bool // Tells if sessions are not to be saved in Datastore
|
||||
asyncDatastoreSave bool // Tells if saving in Datastore should happen asynchronously, in a new goroutine
|
||||
dsEntityName string // Name of the datastore entity to use to save sessions
|
||||
|
||||
// Map of sessions (mapped from ID) that were accessed using this store; usually it will only be 1.
|
||||
// It is also used as a cache, should the user call Get() with the same id multiple times.
|
||||
sessions map[string]Session
|
||||
|
||||
mux *sync.RWMutex // mutex to synchronize access to sessions
|
||||
}
|
||||
|
||||
// MemcacheStoreOptions defines options that may be passed when creating a new Memcache session store.
|
||||
// All fields are optional; default value will be used for any field that has the zero value.
|
||||
type MemcacheStoreOptions struct {
|
||||
// Prefix to use when storing sessions in the Memcache, cannot contain a null byte
|
||||
// and cannot be longer than 250 chars (bytes) when concatenated with the session id; default value is the empty string
|
||||
// The Memcache key will be this prefix and the session id concatenated.
|
||||
KeyPrefix string
|
||||
|
||||
// Number of retries to perform if Memcache operations fail due to general service error;
|
||||
// default value is 3
|
||||
Retries int
|
||||
|
||||
// Codec used to marshal and unmarshal a Session to a byte slice;
|
||||
// Default value is &memcache.Gob (which uses the gob package).
|
||||
Codec *memcache.Codec
|
||||
|
||||
// Tells if sessions are only to be stored in Memcache, and do not store them in Datastore as backup;
|
||||
// as Memcache has no guarantees, it may lose content from time to time, but if Datastore is
|
||||
// also used, the session will automatically be retrieved from the Datastore if not found in Memcache;
|
||||
// default value is false (which means to also save sessions in the Datastore)
|
||||
OnlyMemcache bool
|
||||
|
||||
// Tells if saving in Datastore should happen asynchronously (in a new goroutine, possibly after returning),
|
||||
// if false, session saving in Datastore will happen in the same goroutine, before returning from the request.
|
||||
// Asynchronous saving gives smaller latency (and is enough most of the time as Memcache is always checked first);
|
||||
// default value is false which means to save sessions in the Datastore in the same goroutine, synchronously
|
||||
// Not used if OnlyMemcache=true.
|
||||
// FIXME: See https://github.com/icza/session/issues/3
|
||||
AsyncDatastoreSave bool
|
||||
|
||||
// Name of the entity to use for saving sessions;
|
||||
// default value is "sess_"
|
||||
// Not used if OnlyMemcache=true.
|
||||
DSEntityName string
|
||||
}
|
||||
|
||||
// SessEntity models the session entity saved to Datastore.
|
||||
// The Key is the session id.
|
||||
type SessEntity struct {
|
||||
Expires time.Time `datastore:"exp"`
|
||||
Value []byte `datastore:"val"`
|
||||
}
|
||||
|
||||
// Pointer to zero value of MemcacheStoreOptions to be reused for efficiency.
|
||||
var zeroMemcacheStoreOptions = new(MemcacheStoreOptions)
|
||||
|
||||
// NewMemcacheStore returns a new, GAE Memcache session Store with default options.
|
||||
// Default values of options are listed in the MemcacheStoreOptions type.
|
||||
//
|
||||
// Important! Since accessing the Memcache relies on Appengine Context
|
||||
// which is bound to an http.Request, the returned Store can only be used for the lifetime of a request!
|
||||
func NewMemcacheStore(ctx appengine.Context) Store {
|
||||
return NewMemcacheStoreOptions(ctx, zeroMemcacheStoreOptions)
|
||||
}
|
||||
|
||||
const defaultDSEntityName = "sess_" // Default value of DSEntityName.
|
||||
|
||||
// NewMemcacheStoreOptions returns a new, GAE Memcache session Store with the specified options.
|
||||
//
|
||||
// Important! Since accessing the Memcache relies on Appengine Context
|
||||
// which is bound to an http.Request, the returned Store can only be used for the lifetime of a request!
|
||||
func NewMemcacheStoreOptions(ctx appengine.Context, o *MemcacheStoreOptions) Store {
|
||||
s := &memcacheStore{
|
||||
ctx: ctx,
|
||||
keyPrefix: o.KeyPrefix,
|
||||
retries: o.Retries,
|
||||
onlyMemcache: o.OnlyMemcache,
|
||||
asyncDatastoreSave: o.AsyncDatastoreSave,
|
||||
dsEntityName: o.DSEntityName,
|
||||
sessions: make(map[string]Session, 2),
|
||||
mux: &sync.RWMutex{},
|
||||
}
|
||||
if s.retries <= 0 {
|
||||
s.retries = 3
|
||||
}
|
||||
if o.Codec != nil {
|
||||
s.codec = *o.Codec
|
||||
} else {
|
||||
s.codec = memcache.Gob
|
||||
}
|
||||
if s.dsEntityName == "" {
|
||||
s.dsEntityName = defaultDSEntityName
|
||||
}
|
||||
return s
|
||||
}
|
||||
|
||||
// Get is to implement Store.Get().
|
||||
// Important! Since sessions are marshalled and stored in the Memcache,
|
||||
// the mutex of the Session (Session.RWMutex()) will be different for each
|
||||
// Session value (even though they might have the same session id)!
|
||||
func (s *memcacheStore) Get(id string) Session {
|
||||
s.mux.RLock()
|
||||
defer s.mux.RUnlock()
|
||||
|
||||
// First check our "cache"
|
||||
if sess := s.sessions[id]; sess != nil {
|
||||
return sess
|
||||
}
|
||||
|
||||
// Next check in Memcache
|
||||
var err error
|
||||
var sess *sessionImpl
|
||||
|
||||
for i := 0; i < s.retries; i++ {
|
||||
var sess_ sessionImpl
|
||||
_, err = s.codec.Get(s.ctx, s.keyPrefix+id, &sess_)
|
||||
if err == memcache.ErrCacheMiss {
|
||||
break // It's not in the Memcache (e.g. invalid sess id or was removed from Memcache by AppEngine)
|
||||
}
|
||||
if err == nil {
|
||||
sess = &sess_
|
||||
break
|
||||
}
|
||||
// Service error? Retry..
|
||||
}
|
||||
|
||||
if sess == nil {
|
||||
if err != nil && err != memcache.ErrCacheMiss {
|
||||
s.ctx.Errorf("Failed to get session from memcache, id: %s, error: %v", id, err)
|
||||
}
|
||||
|
||||
// Ok, we didn't get it from Memcace (either was not there or Memcache service is unavailable).
|
||||
// Now it's time to check in the Datastore.
|
||||
key := datastore.NewKey(s.ctx, s.dsEntityName, id, 0, nil)
|
||||
for i := 0; i < s.retries; i++ {
|
||||
e := SessEntity{}
|
||||
err = datastore.Get(s.ctx, key, &e)
|
||||
if err == datastore.ErrNoSuchEntity {
|
||||
return nil // It's not in the Datastore either
|
||||
}
|
||||
if err != nil {
|
||||
// Service error? Retry..
|
||||
continue
|
||||
}
|
||||
if e.Expires.Before(time.Now()) {
|
||||
// Session expired.
|
||||
datastore.Delete(s.ctx, key) // Omitting error check...
|
||||
return nil
|
||||
}
|
||||
var sess_ sessionImpl
|
||||
if err = s.codec.Unmarshal(e.Value, &sess_); err != nil {
|
||||
break // Invalid data in stored session entity...
|
||||
}
|
||||
sess = &sess_
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if sess == nil {
|
||||
s.ctx.Errorf("Failed to get session from datastore, id: %s, error: %v", id, err)
|
||||
return nil
|
||||
}
|
||||
|
||||
// Yes! We have it! "Actualize" it.
|
||||
sess.Access()
|
||||
// Mutex is not marshalled, so create a new one:
|
||||
sess.mux = &sync.RWMutex{}
|
||||
s.sessions[id] = sess
|
||||
return sess
|
||||
}
|
||||
|
||||
// Add is to implement Store.Add().
|
||||
func (s *memcacheStore) Add(sess Session) {
|
||||
s.mux.Lock()
|
||||
defer s.mux.Unlock()
|
||||
|
||||
if s.setMemcacheSession(sess) {
|
||||
s.ctx.Infof("Session added: %s", sess.ID())
|
||||
s.sessions[sess.ID()] = sess
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// setMemcacheSession sets the specified session in the Memcache.
|
||||
func (s *memcacheStore) setMemcacheSession(sess Session) (success bool) {
|
||||
item := &memcache.Item{
|
||||
Key: s.keyPrefix + sess.ID(),
|
||||
Object: sess,
|
||||
Expiration: sess.Timeout(),
|
||||
}
|
||||
|
||||
var err error
|
||||
for i := 0; i < s.retries; i++ {
|
||||
if err = s.codec.Set(s.ctx, item); err == nil {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
s.ctx.Errorf("Failed to add session to memcache, id: %s, error: %v", sess.ID(), err)
|
||||
return false
|
||||
}
|
||||
|
||||
// Remove is to implement Store.Remove().
|
||||
func (s *memcacheStore) Remove(sess Session) {
|
||||
s.mux.Lock()
|
||||
defer s.mux.Unlock()
|
||||
|
||||
var err error
|
||||
for i := 0; i < s.retries; i++ {
|
||||
if err = memcache.Delete(s.ctx, s.keyPrefix+sess.ID()); err == nil || err == memcache.ErrCacheMiss {
|
||||
s.ctx.Infof("Session removed: %s", sess.ID())
|
||||
delete(s.sessions, sess.ID())
|
||||
if !s.onlyMemcache {
|
||||
// Also from the Datastore:
|
||||
key := datastore.NewKey(s.ctx, s.dsEntityName, sess.ID(), 0, nil)
|
||||
datastore.Delete(s.ctx, key) // Omitting error check...
|
||||
}
|
||||
return
|
||||
}
|
||||
}
|
||||
s.ctx.Errorf("Failed to remove session from memcache, id: %s, error: %v", sess.ID(), err)
|
||||
}
|
||||
|
||||
// Close is to implement Store.Close().
|
||||
func (s *memcacheStore) Close() {
|
||||
// Flush out sessions that were accessed from this store. No need locking, we're closing...
|
||||
// We could use Cocec.SetMulti(), but sessions will contain at most 1 session like all the times.
|
||||
for _, sess := range s.sessions {
|
||||
s.setMemcacheSession(sess)
|
||||
}
|
||||
|
||||
if s.onlyMemcache {
|
||||
return // Don't save to Datastore
|
||||
}
|
||||
|
||||
if s.asyncDatastoreSave {
|
||||
go s.saveToDatastore()
|
||||
} else {
|
||||
s.saveToDatastore()
|
||||
}
|
||||
}
|
||||
|
||||
// saveToDatastore saves the sessions of the Store to the Datastore
|
||||
// in the caller's goroutine.
|
||||
func (s *memcacheStore) saveToDatastore() {
|
||||
// Save sessions that were accessed from this store. No need locking, we're closing...
|
||||
// We could use datastore.PutMulti(), but sessions will contain at most 1 session like all the times.
|
||||
for _, sess := range s.sessions {
|
||||
value, err := s.codec.Marshal(sess)
|
||||
if err != nil {
|
||||
s.ctx.Errorf("Failed to marshal session: %s, error: %v", sess.ID(), err)
|
||||
continue
|
||||
}
|
||||
e := SessEntity{
|
||||
Expires: sess.Accessed().Add(sess.Timeout()),
|
||||
Value: value,
|
||||
}
|
||||
key := datastore.NewKey(s.ctx, s.dsEntityName, sess.ID(), 0, nil)
|
||||
for i := 0; i < s.retries; i++ {
|
||||
if _, err = datastore.Put(s.ctx, key, &e); err == nil {
|
||||
break
|
||||
}
|
||||
}
|
||||
if err != nil {
|
||||
s.ctx.Errorf("Failed to save session to datastore: %s, error: %v", sess.ID(), err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// PurgeExpiredSessFromDSFunc returns a request handler function which deletes expired sessions
|
||||
// from the Datastore.
|
||||
// dsEntityName is the name of the entity used for saving sessions; pass an empty string
|
||||
// to use the default value (which is "sess_").
|
||||
//
|
||||
// It is recommended to register the returned handler function to a path which then can be defined
|
||||
// as a cron job to be called periodically, e.g. in every 30 minutes or so (your choice).
|
||||
// As cron handlers may run up to 10 minutes, the returned handler will stop at 8 minutes
|
||||
// to complete safely even if there are more expired, undeleted sessions.
|
||||
//
|
||||
// The response of the handler func is a JSON text telling if the handler was able to delete all expired sessions,
|
||||
// or that it was finished early due to the time. Examle of a respone where all expired sessions were deleted:
|
||||
//
|
||||
// {"completed":true}
|
||||
func PurgeExpiredSessFromDSFunc(dsEntityName string) http.HandlerFunc {
|
||||
if dsEntityName == "" {
|
||||
dsEntityName = defaultDSEntityName
|
||||
}
|
||||
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
c := appengine.NewContext(r)
|
||||
// Delete in batches of 100
|
||||
q := datastore.NewQuery(dsEntityName).Filter("exp<", time.Now()).KeysOnly().Limit(100)
|
||||
|
||||
deadline := time.Now().Add(time.Minute * 8)
|
||||
|
||||
for {
|
||||
var err error
|
||||
var keys []*datastore.Key
|
||||
|
||||
if keys, err = q.GetAll(c, nil); err != nil {
|
||||
// Datastore error.
|
||||
c.Errorf("Failed to query expired sessions: %v", err)
|
||||
http.Error(w, "Failed to query expired sessions!", http.StatusInternalServerError)
|
||||
}
|
||||
if len(keys) == 0 {
|
||||
// We're done, no more expired sessions
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
w.Write([]byte(`{"completed":true}`))
|
||||
return
|
||||
}
|
||||
|
||||
if err = datastore.DeleteMulti(c, keys); err != nil {
|
||||
c.Errorf("Error while deleting expired sessions: %v", err)
|
||||
}
|
||||
|
||||
if time.Now().After(deadline) {
|
||||
// Our time is up, return
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
w.Write([]byte(`{"completed":false}`))
|
||||
return
|
||||
}
|
||||
// We have time to continue
|
||||
}
|
||||
}
|
||||
}
|
39
vendor/github.com/icza/session/global.go
generated
vendored
Normal file
39
vendor/github.com/icza/session/global.go
generated
vendored
Normal file
@@ -0,0 +1,39 @@
|
||||
/*
|
||||
|
||||
A global session Manager and delegator functions - for easy to use.
|
||||
|
||||
*/
|
||||
|
||||
package session
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
)
|
||||
|
||||
// Global is the default session Manager to which the top-level functions such as Get, Add, Remove and Close
|
||||
// are wrappers of Manager.
|
||||
// You may replace this and keep using the top-level functions, but if you intend to do so,
|
||||
// you should close it first with Global.Close().
|
||||
var Global = NewCookieManager(NewInMemStore())
|
||||
|
||||
// Get delegates to Global.Get(); returns the session specified by the HTTP request.
|
||||
// nil is returned if the request does not contain a session, or the contained session is not know by this manager.
|
||||
func Get(r *http.Request) Session {
|
||||
return Global.Get(r)
|
||||
}
|
||||
|
||||
// Add delegates to Global.Add(); adds the session to the HTTP response.
|
||||
// This means to let the client know about the specified session by including the sesison id in the response somehow.
|
||||
func Add(sess Session, w http.ResponseWriter) {
|
||||
Global.Add(sess, w)
|
||||
}
|
||||
|
||||
// Remove delegates to Global.Remove(); removes the session from the HTTP response.
|
||||
func Remove(sess Session, w http.ResponseWriter) {
|
||||
Global.Remove(sess, w)
|
||||
}
|
||||
|
||||
// Close delegates to Global.Close(); closes the session manager, releasing any resources that were allocated.
|
||||
func Close() {
|
||||
Global.Close()
|
||||
}
|
145
vendor/github.com/icza/session/inmem_store.go
generated
vendored
Normal file
145
vendor/github.com/icza/session/inmem_store.go
generated
vendored
Normal file
@@ -0,0 +1,145 @@
|
||||
/*
|
||||
|
||||
An in-memory session store implementation.
|
||||
|
||||
*/
|
||||
|
||||
package session
|
||||
|
||||
import (
|
||||
"log"
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
|
||||
// In-memory session Store implementation.
|
||||
type inMemStore struct {
|
||||
sessions map[string]Session // Map of sessions (mapped from ID)
|
||||
mux *sync.RWMutex // mutex to synchronize access to sessions
|
||||
ticker *time.Ticker // Ticker for the session cleaner
|
||||
closeTicker chan struct{} // Channel to signal close for the session cleaner
|
||||
}
|
||||
|
||||
// InMemStoreOptions defines options that may be passed when creating a new in-memory Store.
|
||||
// All fields are optional; default value will be used for any field that has the zero value.
|
||||
type InMemStoreOptions struct {
|
||||
// Session cleaner check interval, default is 10 seconds.
|
||||
SessCleanerInterval time.Duration
|
||||
}
|
||||
|
||||
// Pointer to zero value of InMemStoreOptions to be reused for efficiency.
|
||||
var zeroInMemStoreOptions = new(InMemStoreOptions)
|
||||
|
||||
// NewInMemStore returns a new, in-memory session Store with the default options.
|
||||
// Default values of options are listed in the InMemStoreOptions type.
|
||||
// The returned Store has an automatic session cleaner which runs
|
||||
// in its own goroutine.
|
||||
func NewInMemStore() Store {
|
||||
return NewInMemStoreOptions(zeroInMemStoreOptions)
|
||||
}
|
||||
|
||||
// NewInMemStoreOptions returns a new, in-memory session Store with the specified options.
|
||||
// The returned Store has an automatic session cleaner which runs
|
||||
// in its own goroutine.
|
||||
func NewInMemStoreOptions(o *InMemStoreOptions) Store {
|
||||
s := &inMemStore{
|
||||
sessions: make(map[string]Session),
|
||||
mux: &sync.RWMutex{},
|
||||
closeTicker: make(chan struct{}),
|
||||
}
|
||||
|
||||
interval := o.SessCleanerInterval
|
||||
if interval == 0 {
|
||||
interval = 10 * time.Second
|
||||
}
|
||||
|
||||
go s.sessCleaner(interval)
|
||||
|
||||
return s
|
||||
}
|
||||
|
||||
// sessCleaner periodically checks whether sessions have timed out
|
||||
// in an endless loop. If a session has timed out, removes it.
|
||||
// This method is to be started as a new goroutine.
|
||||
func (s *inMemStore) sessCleaner(interval time.Duration) {
|
||||
ticker := time.NewTicker(interval)
|
||||
|
||||
for {
|
||||
select {
|
||||
case <-s.closeTicker:
|
||||
// We are being shut down...
|
||||
ticker.Stop()
|
||||
return
|
||||
case now := <-ticker.C:
|
||||
// Do a sweep.
|
||||
// Remove is very rare compared to the number of checks, so:
|
||||
// "Quick" check with read-lock to see if there's anything to remove:
|
||||
// Note: Session.Access() is called with s.mux, the same mutex we use
|
||||
// when looking for timed-out sessions, so we're good.
|
||||
needRemove := func() bool {
|
||||
s.mux.RLock() // Read lock is enough
|
||||
defer s.mux.RUnlock()
|
||||
|
||||
for _, sess := range s.sessions {
|
||||
if now.Sub(sess.Accessed()) > sess.Timeout() {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}()
|
||||
if !needRemove {
|
||||
continue
|
||||
}
|
||||
|
||||
// Remove required:
|
||||
func() {
|
||||
s.mux.Lock() // Read-write lock required
|
||||
defer s.mux.Unlock()
|
||||
|
||||
for _, sess := range s.sessions {
|
||||
if now.Sub(sess.Accessed()) > sess.Timeout() {
|
||||
log.Println("Session timed out:", sess.ID())
|
||||
delete(s.sessions, sess.ID())
|
||||
}
|
||||
}
|
||||
}()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Get is to implement Store.Get().
|
||||
func (s *inMemStore) Get(id string) Session {
|
||||
s.mux.RLock()
|
||||
defer s.mux.RUnlock()
|
||||
|
||||
sess := s.sessions[id]
|
||||
if sess == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
sess.Access()
|
||||
return sess
|
||||
}
|
||||
|
||||
// Add is to implement Store.Add().
|
||||
func (s *inMemStore) Add(sess Session) {
|
||||
s.mux.Lock()
|
||||
defer s.mux.Unlock()
|
||||
|
||||
log.Println("Session added:", sess.ID())
|
||||
s.sessions[sess.ID()] = sess
|
||||
}
|
||||
|
||||
// Remove is to implement Store.Remove().
|
||||
func (s *inMemStore) Remove(sess Session) {
|
||||
s.mux.Lock()
|
||||
defer s.mux.Unlock()
|
||||
|
||||
log.Println("Session removed:", sess.ID())
|
||||
delete(s.sessions, sess.ID())
|
||||
}
|
||||
|
||||
// Close is to implement Store.Close().
|
||||
func (s *inMemStore) Close() {
|
||||
close(s.closeTicker)
|
||||
}
|
31
vendor/github.com/icza/session/manager.go
generated
vendored
Normal file
31
vendor/github.com/icza/session/manager.go
generated
vendored
Normal file
@@ -0,0 +1,31 @@
|
||||
/*
|
||||
|
||||
Session Manager interface.
|
||||
|
||||
*/
|
||||
|
||||
package session
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
)
|
||||
|
||||
// Manager is a session manager interface.
|
||||
// A session manager is responsible to acquire a Session from an (incoming) HTTP request,
|
||||
// and to add a Session to an HTTP response to let the client know about the session.
|
||||
// A Manager has a backing Store which is responsible to manage Session values at server side.
|
||||
type Manager interface {
|
||||
// Get returns the session specified by the HTTP request.
|
||||
// nil is returned if the request does not contain a session, or the contained session is not know by this manager.
|
||||
Get(r *http.Request) Session
|
||||
|
||||
// Add adds the session to the HTTP response.
|
||||
// This means to let the client know about the specified session by including the sesison id in the response somehow.
|
||||
Add(sess Session, w http.ResponseWriter)
|
||||
|
||||
// Remove removes the session from the HTTP response.
|
||||
Remove(sess Session, w http.ResponseWriter)
|
||||
|
||||
// Close closes the session manager, releasing any resources that were allocated.
|
||||
Close()
|
||||
}
|
231
vendor/github.com/icza/session/session.go
generated
vendored
Normal file
231
vendor/github.com/icza/session/session.go
generated
vendored
Normal file
@@ -0,0 +1,231 @@
|
||||
/*
|
||||
|
||||
Session interface and its implementation.
|
||||
|
||||
*/
|
||||
|
||||
package session
|
||||
|
||||
import (
|
||||
"crypto/rand"
|
||||
"encoding/base64"
|
||||
"io"
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
|
||||
// Session is the (HTTP) session interface.
|
||||
// We can use it to store and retrieve constant and variable attributes from it.
|
||||
type Session interface {
|
||||
// ID returns the id of the session.
|
||||
ID() string
|
||||
|
||||
// New tells if the session is new.
|
||||
// Implementation is based on whether created and access times are equal.
|
||||
New() bool
|
||||
|
||||
// CAttr returns the value of an attribute provided at session creation.
|
||||
// These attributes cannot be changes during the lifetime of a session,
|
||||
// so they can be accessed safely without synchronization. Exampe is storing the
|
||||
// authenticated user.
|
||||
CAttr(name string) interface{}
|
||||
|
||||
// Attr returns the value of an attribute stored in the session.
|
||||
// Safe for concurrent use.
|
||||
Attr(name string) interface{}
|
||||
|
||||
// SetAttr sets the value of an attribute stored in the session.
|
||||
// Pass the nil value to delete the attribute.
|
||||
// Safe for concurrent use.
|
||||
SetAttr(name string, value interface{})
|
||||
|
||||
// Attrs returns a copy of all the attribute values stored in the session.
|
||||
// Safe for concurrent use.
|
||||
Attrs() map[string]interface{}
|
||||
|
||||
// Created returns the session creation time.
|
||||
Created() time.Time
|
||||
|
||||
// Accessed returns the time when the session was last accessed.
|
||||
Accessed() time.Time
|
||||
|
||||
// Timeout returns the session timeout.
|
||||
// A session may be removed automatically if it is not accessed for this duration.
|
||||
Timeout() time.Duration
|
||||
|
||||
// Mutex returns the RW mutex of the session.
|
||||
// It is used to synchronize access/modification of the state stored in the session.
|
||||
// It can be used if session-level synchronization is required.
|
||||
// Important! If Session values are marshalled / unmarshalled
|
||||
// (e.g. multi server instance environment such as Google AppEngine),
|
||||
// this mutex may be different for each Session value and thus
|
||||
// it can only be used to session-value level synchronization!
|
||||
Mutex() *sync.RWMutex
|
||||
|
||||
// Access registers an access to the session,
|
||||
// updates its last accessed time to the current time.
|
||||
// Users do not need to call this as the session store is responsible for that.
|
||||
Access()
|
||||
}
|
||||
|
||||
// Session implementation.
|
||||
// Fields are exported so a session may be marshalled / unmarshalled.
|
||||
type sessionImpl struct {
|
||||
IDF string // ID of the session
|
||||
CreatedF time.Time // Creation time
|
||||
AccessedF time.Time // Last accessed time
|
||||
CAttrsF map[string]interface{} // Constant attributes specified at session creation
|
||||
AttrsF map[string]interface{} // Attributes stored in the session
|
||||
TimeoutF time.Duration // Session timeout
|
||||
mux *sync.RWMutex // RW mutex to synchronize session state access
|
||||
}
|
||||
|
||||
// SessOptions defines options that may be passed when creating a new Session.
|
||||
// All fields are optional; default value will be used for any field that has the zero value.
|
||||
type SessOptions struct {
|
||||
// Constant attributes of the session. These be will available via the Session.CAttr() method, without synchronization.
|
||||
// Values from the map will be copied, and will be available via Session.CAttr().
|
||||
CAttrs map[string]interface{}
|
||||
|
||||
// Initial, non-constant attributes to be stored in the session.
|
||||
// Values from the map will be copied, and will be available via Session.Attr() and Session.Attrs,
|
||||
// and may be changed with Session.SetAttr().
|
||||
Attrs map[string]interface{}
|
||||
|
||||
// Session timeout, default is 30 minutes.
|
||||
Timeout time.Duration
|
||||
|
||||
// Byte-length of the information that builds up the session ids.
|
||||
// Using Base-64 encoding, id length will be this multiplied by 4/3 chars.
|
||||
// Default value is 18 (which means length of ID will be 24 chars).
|
||||
IDLength int
|
||||
}
|
||||
|
||||
// Pointer to zero value of SessOptions to be reused for efficiency.
|
||||
var zeroSessOptions = new(SessOptions)
|
||||
|
||||
// NewSession creates a new Session with the default options.
|
||||
// Default values of options are listed in the SessOptions type.
|
||||
func NewSession() Session {
|
||||
return NewSessionOptions(zeroSessOptions)
|
||||
}
|
||||
|
||||
// NewSessionOptions creates a new Session with the specified options.
|
||||
func NewSessionOptions(o *SessOptions) Session {
|
||||
now := time.Now()
|
||||
idLength := o.IDLength
|
||||
if idLength <= 0 {
|
||||
idLength = 18
|
||||
}
|
||||
timeout := o.Timeout
|
||||
if timeout == 0 {
|
||||
timeout = 30 * time.Minute
|
||||
}
|
||||
|
||||
sess := sessionImpl{
|
||||
IDF: genID(idLength),
|
||||
CreatedF: now,
|
||||
AccessedF: now,
|
||||
AttrsF: make(map[string]interface{}),
|
||||
TimeoutF: timeout,
|
||||
mux: &sync.RWMutex{},
|
||||
}
|
||||
|
||||
if len(o.CAttrs) > 0 {
|
||||
sess.CAttrsF = make(map[string]interface{}, len(o.CAttrs))
|
||||
for k, v := range o.CAttrs {
|
||||
sess.CAttrsF[k] = v
|
||||
}
|
||||
}
|
||||
|
||||
for k, v := range o.Attrs {
|
||||
sess.AttrsF[k] = v
|
||||
}
|
||||
|
||||
return &sess
|
||||
}
|
||||
|
||||
// genID generates a secure, random session id using the crypto/rand package.
|
||||
func genID(length int) string {
|
||||
r := make([]byte, length)
|
||||
io.ReadFull(rand.Reader, r)
|
||||
return base64.URLEncoding.EncodeToString(r)
|
||||
}
|
||||
|
||||
// ID is to implement Session.ID().
|
||||
func (s *sessionImpl) ID() string {
|
||||
return s.IDF
|
||||
}
|
||||
|
||||
// New is to implement Session.New().
|
||||
func (s *sessionImpl) New() bool {
|
||||
return s.CreatedF == s.AccessedF
|
||||
}
|
||||
|
||||
// CAttr is to implement Session.CAttr().
|
||||
func (s *sessionImpl) CAttr(name string) interface{} {
|
||||
return s.CAttrsF[name]
|
||||
}
|
||||
|
||||
// Attr is to implement Session.Attr().
|
||||
func (s *sessionImpl) Attr(name string) interface{} {
|
||||
s.mux.RLock()
|
||||
defer s.mux.RUnlock()
|
||||
|
||||
return s.AttrsF[name]
|
||||
}
|
||||
|
||||
// SetAttr is to implement Session.SetAttr().
|
||||
func (s *sessionImpl) SetAttr(name string, value interface{}) {
|
||||
s.mux.Lock()
|
||||
defer s.mux.Unlock()
|
||||
|
||||
if value == nil {
|
||||
delete(s.AttrsF, name)
|
||||
} else {
|
||||
s.AttrsF[name] = value
|
||||
}
|
||||
}
|
||||
|
||||
// Attrs is to implement Session.Attrs().
|
||||
func (s *sessionImpl) Attrs() map[string]interface{} {
|
||||
s.mux.RLock()
|
||||
defer s.mux.RUnlock()
|
||||
|
||||
m := make(map[string]interface{}, len(s.AttrsF))
|
||||
for k, v := range s.AttrsF {
|
||||
m[k] = v
|
||||
}
|
||||
return m
|
||||
}
|
||||
|
||||
// Created is to implement Session.Created().
|
||||
func (s *sessionImpl) Created() time.Time {
|
||||
return s.CreatedF
|
||||
}
|
||||
|
||||
// Accessed is to implement Session.Accessed().
|
||||
func (s *sessionImpl) Accessed() time.Time {
|
||||
s.mux.RLock()
|
||||
defer s.mux.RUnlock()
|
||||
|
||||
return s.AccessedF
|
||||
}
|
||||
|
||||
// Timeout is to implement Session.Timeout().
|
||||
func (s *sessionImpl) Timeout() time.Duration {
|
||||
return s.TimeoutF
|
||||
}
|
||||
|
||||
// Mutex is to implement Session.Mutex().
|
||||
func (s *sessionImpl) Mutex() *sync.RWMutex {
|
||||
return s.mux
|
||||
}
|
||||
|
||||
// Access is to implement Session.Access().
|
||||
func (s *sessionImpl) Access() {
|
||||
s.mux.Lock()
|
||||
defer s.mux.Unlock()
|
||||
|
||||
s.AccessedF = time.Now()
|
||||
}
|
25
vendor/github.com/icza/session/store.go
generated
vendored
Normal file
25
vendor/github.com/icza/session/store.go
generated
vendored
Normal file
@@ -0,0 +1,25 @@
|
||||
/*
|
||||
|
||||
Session Store interface.
|
||||
|
||||
*/
|
||||
|
||||
package session
|
||||
|
||||
// Store is a session store interface.
|
||||
// A session store is responsible to store sessions and make them retrievable by their IDs at the server side.
|
||||
type Store interface {
|
||||
// Get returns the session specified by its id.
|
||||
// The returned session will have an updated access time (set to the current time).
|
||||
// nil is returned if this store does not contain a session with the specified id.
|
||||
Get(id string) Session
|
||||
|
||||
// Add adds a new session to the store.
|
||||
Add(sess Session)
|
||||
|
||||
// Remove removes a session from the store.
|
||||
Remove(sess Session)
|
||||
|
||||
// Close closes the session store, releasing any resources that were allocated.
|
||||
Close()
|
||||
}
|
Reference in New Issue
Block a user