0
0
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:
Tim Schuster
2017-03-12 20:37:53 +01:00
parent 70b12c516a
commit 69b0d20825
186 changed files with 44200 additions and 0 deletions

201
vendor/github.com/icza/session/LICENSE generated vendored Normal file
View 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
View File

@@ -0,0 +1,114 @@
# Session
[![Build Status](https://travis-ci.org/icza/session.svg?branch=master)](https://travis-ci.org/icza/session)
[![GoDoc](https://godoc.org/github.com/icza/session?status.svg)](https://godoc.org/github.com/icza/session)
[![Go Report Card](https://goreportcard.com/badge/github.com/icza/session)](https://goreportcard.com/report/github.com/icza/session)
[![codecov](https://codecov.io/gh/icza/session/branch/master/graph/badge.svg)](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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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()
}