Major rewrite
* use dep for vendoring * lets encrypt * moved web to transfer.sh-web repo * single command install * added first tests
This commit is contained in:
7
vendor/github.com/PuerkitoBio/ghost/.gitignore
generated
vendored
Normal file
7
vendor/github.com/PuerkitoBio/ghost/.gitignore
generated
vendored
Normal file
@@ -0,0 +1,7 @@
|
||||
*.sublime-*
|
||||
.DS_Store
|
||||
#*#
|
||||
*~
|
||||
*.swp
|
||||
*.swo
|
||||
*.rdb
|
12
vendor/github.com/PuerkitoBio/ghost/LICENSE
generated
vendored
Normal file
12
vendor/github.com/PuerkitoBio/ghost/LICENSE
generated
vendored
Normal file
@@ -0,0 +1,12 @@
|
||||
Copyright (c) 2013, Martin Angers
|
||||
All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
|
||||
|
||||
* Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
|
||||
|
||||
* Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
|
||||
|
||||
* Neither the name of the author nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
83
vendor/github.com/PuerkitoBio/ghost/README.md
generated
vendored
Normal file
83
vendor/github.com/PuerkitoBio/ghost/README.md
generated
vendored
Normal file
@@ -0,0 +1,83 @@
|
||||
# Ghost
|
||||
|
||||
Ghost is a web development library loosely inspired by node's [Connect library][connect]. It provides a number of simple, single-responsibility HTTP handlers that can be combined to build a full-featured web server, and a generic template engine integration interface.
|
||||
|
||||
It stays close to the metal, not abstracting Go's standard library away. As a matter of fact, any stdlib handler can be used with Ghost's handlers, they simply are `net/http.Handler`'s.
|
||||
|
||||
## Installation and documentation
|
||||
|
||||
`go get github.com/PuerkitoBio/ghost`
|
||||
|
||||
[API reference][godoc]
|
||||
|
||||
*Status* : **Unmaintained**
|
||||
|
||||
## Example
|
||||
|
||||
See the /ghostest directory for a complete working example of a website built with Ghost. It shows all handlers and template support of Ghost.
|
||||
|
||||
## Handlers
|
||||
|
||||
Ghost offers the following handlers:
|
||||
|
||||
* BasicAuthHandler : basic authentication support.
|
||||
* ContextHandler : key-value map provider for the duration of the request.
|
||||
* FaviconHandler : simple and efficient favicon renderer.
|
||||
* GZIPHandler : gzip-compresser for the body of the response.
|
||||
* LogHandler : fully customizable request logger.
|
||||
* PanicHandler : panic-catching handler to control the error response.
|
||||
* SessionHandler : store-agnostic server-side session provider.
|
||||
* StaticHandler : convenience handler that wraps a call to `net/http.ServeFile`.
|
||||
|
||||
Two stores are provided for the session persistence, `MemoryStore`, an in-memory map that is not suited for production environment, and `RedisStore`, a more robust and scalable [redigo][]-based Redis store. Because of the generic `SessionStore` interface, custom stores can easily be created as needed.
|
||||
|
||||
The `handlers` package also offers the `ChainableHandler` interface, which supports combining HTTP handlers in a sequential fashion, and the `ChainHandlers()` function that creates a new handler from the sequential combination of any number of handlers.
|
||||
|
||||
As a convenience, all functions that take a `http.Handler` as argument also have a corresponding function with the `Func` suffix that take a `http.HandlerFunc` instead as argument. This saves the type-cast when a simple handler function is passed (for example, `SessionHandler()` and `SessionHandlerFunc()`).
|
||||
|
||||
### Handlers Design
|
||||
|
||||
The HTTP handlers such as Basic Auth and Context need to store some state information to provide their functionality. Instead of using variables and a mutex to control shared access, Ghost augments the `http.ResponseWriter` interface that is part of the Handler's `ServeHTTP()` function signature. Because this instance is unique for each request and is not shared, there is no locking involved to access the state information.
|
||||
|
||||
However, when combining such handlers, Ghost needs a way to move through the chain of augmented ResponseWriters. This is why these *augmented writers* need to implement the `WrapWriter` interface. A single method is required, `WrappedWriter() http.ResponseWriter`, which returns the wrapped ResponseWriter.
|
||||
|
||||
And to get back a specific augmented writer, the `GetResponseWriter()` function is provided. It takes a ResponseWriter and a predicate function as argument, and returns the requested specific writer using the *comma-ok* pattern. Example, for the session writer:
|
||||
|
||||
```Go
|
||||
func getSessionWriter(w http.ResponseWriter) (*sessResponseWriter, bool) {
|
||||
ss, ok := GetResponseWriter(w, func(tst http.ResponseWriter) bool {
|
||||
_, ok := tst.(*sessResponseWriter)
|
||||
return ok
|
||||
})
|
||||
if ok {
|
||||
return ss.(*sessResponseWriter), true
|
||||
}
|
||||
return nil, false
|
||||
}
|
||||
```
|
||||
|
||||
Ghost does not provide a muxer, there are already many great ones available, but I would recommend Go's native `http.ServeMux` or [pat][] because it has great features and plays well with Ghost's design. Gorilla's muxer is very popular, but since it depends on Gorilla's (mutex-based) context provider, this is redundant with Ghost's context.
|
||||
|
||||
## Templates
|
||||
|
||||
Ghost supports the following template engines:
|
||||
|
||||
* Go's native templates (needs work, at the moment does not work with nested templates)
|
||||
* [Amber][]
|
||||
|
||||
TODO : Go's mustache implementation.
|
||||
|
||||
### Templates Design
|
||||
|
||||
The template engines can be registered much in the same way as database drivers, just by importing for side effects (using `_ "import/path"`). The `init()` function of the template engine's package registers the template compiler with the correct file extension, and the engine can be used.
|
||||
|
||||
## License
|
||||
|
||||
The [BSD 3-Clause license][lic].
|
||||
|
||||
[connect]: https://github.com/senchalabs/connect
|
||||
[godoc]: http://godoc.org/github.com/PuerkitoBio/ghost
|
||||
[lic]: http://opensource.org/licenses/BSD-3-Clause
|
||||
[redigo]: https://github.com/garyburd/redigo
|
||||
[pat]: https://github.com/bmizerany/pat
|
||||
[amber]: https://github.com/eknkc/amber
|
12
vendor/github.com/PuerkitoBio/ghost/app.go
generated
vendored
Normal file
12
vendor/github.com/PuerkitoBio/ghost/app.go
generated
vendored
Normal file
@@ -0,0 +1,12 @@
|
||||
package ghost
|
||||
|
||||
import (
|
||||
"log"
|
||||
)
|
||||
|
||||
// Logging function, defaults to Go's native log.Printf function. The idea to use
|
||||
// this instead of a *log.Logger struct is that it can be set to any of log.{Printf,Fatalf, Panicf},
|
||||
// but also to more flexible userland loggers like SeeLog (https://github.com/cihub/seelog).
|
||||
// It could be set, for example, to SeeLog's Debugf function. Any function with the
|
||||
// signature func(fmt string, params ...interface{}).
|
||||
var LogFn = log.Printf
|
22
vendor/github.com/PuerkitoBio/ghost/ghostest/index.html
generated
vendored
Normal file
22
vendor/github.com/PuerkitoBio/ghost/ghostest/index.html
generated
vendored
Normal file
@@ -0,0 +1,22 @@
|
||||
<html>
|
||||
<head>
|
||||
<title>Ghost Test</title>
|
||||
<link type="text/css" rel="stylesheet" href="/public/styles.css">
|
||||
<link type="text/css" rel="stylesheet" href="/public/bootstrap-combined.min.css">
|
||||
</head>
|
||||
<body>
|
||||
<h1>Welcome to Ghost Test</h1>
|
||||
<img src="/public/logo.png" alt="peace" />
|
||||
<ol>
|
||||
<li><a href="/session">Session</a></li>
|
||||
<li><a href="/session/auth">Authenticated Session</a></li>
|
||||
<li><a href="/context">Chained Context</a></li>
|
||||
<li><a href="/panic">Panic</a></li>
|
||||
<li><a href="/public/styles.css">Styles.css</a></li>
|
||||
<li><a href="/public/jquery-2.0.0.min.js">JQuery</a></li>
|
||||
<li><a href="/public/logo.png">Logo</a></li>
|
||||
</ol>
|
||||
|
||||
<script src="/public/jquery-2.0.0.min.js"></script>
|
||||
</body>
|
||||
</html>
|
169
vendor/github.com/PuerkitoBio/ghost/ghostest/main.go
generated
vendored
Normal file
169
vendor/github.com/PuerkitoBio/ghost/ghostest/main.go
generated
vendored
Normal file
@@ -0,0 +1,169 @@
|
||||
// Ghostest is an interactive end-to-end Web site application to test
|
||||
// the ghost packages. It serves the following URLs, with the specified
|
||||
// features (handlers):
|
||||
//
|
||||
// / : panic;log;gzip;static; -> serve file index.html
|
||||
// /public/styles.css : panic;log;gzip;StripPrefix;FileServer; -> serve directory public/
|
||||
// /public/script.js : panic;log;gzip;StripPrefix;FileServer; -> serve directory public/
|
||||
// /public/logo.pn : panic;log;gzip;StripPrefix;FileServer; -> serve directory public/
|
||||
// /session : panic;log;gzip;session;context;Custom; -> serve dynamic Go template
|
||||
// /session/auth : panic;log;gzip;session;context;basicAuth;Custom; -> serve dynamic template
|
||||
// /panic : panic;log;gzip;Custom; -> panics
|
||||
// /context : panic;log;gzip;context;Custom1;Custom2; -> serve dynamic Amber template
|
||||
package main
|
||||
|
||||
import (
|
||||
"log"
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
"github.com/PuerkitoBio/ghost/handlers"
|
||||
"github.com/PuerkitoBio/ghost/templates"
|
||||
_ "github.com/PuerkitoBio/ghost/templates/amber"
|
||||
_ "github.com/PuerkitoBio/ghost/templates/gotpl"
|
||||
"github.com/bmizerany/pat"
|
||||
)
|
||||
|
||||
const (
|
||||
sessionPageTitle = "Session Page"
|
||||
sessionPageAuthTitle = "Authenticated Session Page"
|
||||
sessionPageKey = "txt"
|
||||
contextPageKey = "time"
|
||||
sessionExpiration = 10 // Session expires after 10 seconds
|
||||
)
|
||||
|
||||
var (
|
||||
// Create the common session store and secret
|
||||
memStore = handlers.NewMemoryStore(1)
|
||||
secret = "testimony of the ancients"
|
||||
)
|
||||
|
||||
// The struct used to pass data to the session template.
|
||||
type sessionPageInfo struct {
|
||||
SessionID string
|
||||
Title string
|
||||
Text string
|
||||
}
|
||||
|
||||
// Authenticate the Basic Auth credentials.
|
||||
func authenticate(u, p string) (interface{}, bool) {
|
||||
if u == "user" && p == "pwd" {
|
||||
return u + p, true
|
||||
}
|
||||
return nil, false
|
||||
}
|
||||
|
||||
// Handle the session page requests.
|
||||
func sessionPageRenderer(w handlers.GhostWriter, r *http.Request) {
|
||||
var (
|
||||
txt interface{}
|
||||
data sessionPageInfo
|
||||
title string
|
||||
)
|
||||
|
||||
ssn := w.Session()
|
||||
if r.Method == "GET" {
|
||||
txt = ssn.Data[sessionPageKey]
|
||||
} else {
|
||||
txt = r.FormValue(sessionPageKey)
|
||||
ssn.Data[sessionPageKey] = txt
|
||||
}
|
||||
if r.URL.Path == "/session/auth" {
|
||||
title = sessionPageAuthTitle
|
||||
} else {
|
||||
title = sessionPageTitle
|
||||
}
|
||||
if txt != nil {
|
||||
data = sessionPageInfo{ssn.ID(), title, txt.(string)}
|
||||
} else {
|
||||
data = sessionPageInfo{ssn.ID(), title, "[nil]"}
|
||||
}
|
||||
err := templates.Render("templates/session.tmpl", w, data)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
|
||||
// Prepare the context value for the chained handlers context page.
|
||||
func setContext(w handlers.GhostWriter, r *http.Request) {
|
||||
w.Context()[contextPageKey] = time.Now().String()
|
||||
}
|
||||
|
||||
// Retrieve the context value and render the chained handlers context page.
|
||||
func renderContextPage(w handlers.GhostWriter, r *http.Request) {
|
||||
err := templates.Render("templates/amber/context.amber",
|
||||
w, &struct{ Val string }{w.Context()[contextPageKey].(string)})
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
|
||||
// Prepare the web server and kick it off.
|
||||
func main() {
|
||||
// Blank the default logger's prefixes
|
||||
log.SetFlags(0)
|
||||
|
||||
// Compile the dynamic templates (native Go templates and Amber
|
||||
// templates are both registered via the for-side-effects-only imports)
|
||||
err := templates.CompileDir("./templates/")
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
// Set the simple routes for static files
|
||||
mux := pat.New()
|
||||
mux.Get("/", handlers.StaticFileHandler("./index.html"))
|
||||
mux.Get("/public/", http.StripPrefix("/public/", http.FileServer(http.Dir("./public/"))))
|
||||
|
||||
// Set the more complex routes for session handling and dynamic page (same
|
||||
// handler is used for both GET and POST).
|
||||
ssnOpts := handlers.NewSessionOptions(memStore, secret)
|
||||
ssnOpts.CookieTemplate.MaxAge = sessionExpiration
|
||||
hSsn := handlers.SessionHandler(
|
||||
handlers.ContextHandlerFunc(
|
||||
handlers.GhostHandlerFunc(sessionPageRenderer),
|
||||
1),
|
||||
ssnOpts)
|
||||
mux.Get("/session", hSsn)
|
||||
mux.Post("/session", hSsn)
|
||||
|
||||
hAuthSsn := handlers.BasicAuthHandler(hSsn, authenticate, "")
|
||||
mux.Get("/session/auth", hAuthSsn)
|
||||
mux.Post("/session/auth", hAuthSsn)
|
||||
|
||||
// Set the handler for the chained context route
|
||||
mux.Get("/context", handlers.ContextHandler(handlers.ChainHandlerFuncs(
|
||||
handlers.GhostHandlerFunc(setContext),
|
||||
handlers.GhostHandlerFunc(renderContextPage)),
|
||||
1))
|
||||
|
||||
// Set the panic route, which simply panics
|
||||
mux.Get("/panic", http.HandlerFunc(
|
||||
func(w http.ResponseWriter, r *http.Request) {
|
||||
panic("explicit panic")
|
||||
}))
|
||||
|
||||
// Combine the top level handlers, that wrap around the muxer.
|
||||
// Panic is the outermost, so that any panic is caught and responded to with a code 500.
|
||||
// Log is next, so that every request is logged along with the URL, status code and response time.
|
||||
// GZIP is then applied, so that content is compressed.
|
||||
// Finally, the muxer finds the specific handler that applies to the route.
|
||||
h := handlers.FaviconHandler(
|
||||
handlers.PanicHandler(
|
||||
handlers.LogHandler(
|
||||
handlers.GZIPHandler(
|
||||
mux,
|
||||
nil),
|
||||
handlers.NewLogOptions(nil, handlers.Ltiny)),
|
||||
nil),
|
||||
"./public/favicon.ico",
|
||||
48*time.Hour)
|
||||
|
||||
// Assign the combined handler to the server.
|
||||
http.Handle("/", h)
|
||||
|
||||
// Start it up.
|
||||
if err := http.ListenAndServe(":9000", nil); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
873
vendor/github.com/PuerkitoBio/ghost/ghostest/public/bootstrap-combined.min.css
generated
vendored
Normal file
873
vendor/github.com/PuerkitoBio/ghost/ghostest/public/bootstrap-combined.min.css
generated
vendored
Normal file
File diff suppressed because one or more lines are too long
BIN
vendor/github.com/PuerkitoBio/ghost/ghostest/public/favicon.ico
generated
vendored
Normal file
BIN
vendor/github.com/PuerkitoBio/ghost/ghostest/public/favicon.ico
generated
vendored
Normal file
Binary file not shown.
After Width: | Height: | Size: 1.4 KiB |
6
vendor/github.com/PuerkitoBio/ghost/ghostest/public/jquery-2.0.0.min.js
generated
vendored
Normal file
6
vendor/github.com/PuerkitoBio/ghost/ghostest/public/jquery-2.0.0.min.js
generated
vendored
Normal file
File diff suppressed because one or more lines are too long
BIN
vendor/github.com/PuerkitoBio/ghost/ghostest/public/logo.png
generated
vendored
Normal file
BIN
vendor/github.com/PuerkitoBio/ghost/ghostest/public/logo.png
generated
vendored
Normal file
Binary file not shown.
After Width: | Height: | Size: 20 KiB |
3
vendor/github.com/PuerkitoBio/ghost/ghostest/public/styles.css
generated
vendored
Normal file
3
vendor/github.com/PuerkitoBio/ghost/ghostest/public/styles.css
generated
vendored
Normal file
@@ -0,0 +1,3 @@
|
||||
body {
|
||||
background-color: silver;
|
||||
}
|
8
vendor/github.com/PuerkitoBio/ghost/ghostest/templates/amber/context.amber
generated
vendored
Normal file
8
vendor/github.com/PuerkitoBio/ghost/ghostest/templates/amber/context.amber
generated
vendored
Normal file
@@ -0,0 +1,8 @@
|
||||
!!! 5
|
||||
html
|
||||
head
|
||||
title Chained Context
|
||||
link[type="text/css"][rel="stylesheet"][href="/public/bootstrap-combined.min.css"]
|
||||
body
|
||||
h1 Chained Context
|
||||
h2 Value found: #{Val}
|
28
vendor/github.com/PuerkitoBio/ghost/ghostest/templates/session.tmpl
generated
vendored
Normal file
28
vendor/github.com/PuerkitoBio/ghost/ghostest/templates/session.tmpl
generated
vendored
Normal file
@@ -0,0 +1,28 @@
|
||||
<html>
|
||||
<head>
|
||||
<title>{{ .Title }}</title>
|
||||
<link type="text/css" rel="stylesheet" href="/public/styles.css">
|
||||
<link type="text/css" rel="stylesheet" href="/public/bootstrap-combined.min.css">
|
||||
</head>
|
||||
<body>
|
||||
<h1>Session: {{ .SessionID }}</h1>
|
||||
<ol>
|
||||
<li><a href="/">Home</a></li>
|
||||
<li><a href="/session">Session</a></li>
|
||||
<li><a href="/session/auth">Authenticated Session</a></li>
|
||||
<li><a href="/context">Chained Context</a></li>
|
||||
<li><a href="/panic">Panic</a></li>
|
||||
<li><a href="/public/styles.css">Styles.css</a></li>
|
||||
<li><a href="/public/jquery-2.0.0.min.js">JQuery</a></li>
|
||||
<li><a href="/public/logo.png">Logo</a></li>
|
||||
</ol>
|
||||
<h2>Current Value: {{ .Text }}</h2>
|
||||
<form method="POST">
|
||||
<input type="text" name="txt" placeholder="some value to save to session"></input>
|
||||
<button type="submit">Submit</button>
|
||||
</form>
|
||||
|
||||
<script src="/public/jquery-2.0.0.min.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
123
vendor/github.com/PuerkitoBio/ghost/handlers/basicauth.go
generated
vendored
Normal file
123
vendor/github.com/PuerkitoBio/ghost/handlers/basicauth.go
generated
vendored
Normal file
@@ -0,0 +1,123 @@
|
||||
package handlers
|
||||
|
||||
// Inspired by node.js' Connect library implementation of the basicAuth middleware.
|
||||
// https://github.com/senchalabs/connect
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/base64"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// Internal writer that keeps track of the currently authenticated user.
|
||||
type userResponseWriter struct {
|
||||
http.ResponseWriter
|
||||
user interface{}
|
||||
userName string
|
||||
}
|
||||
|
||||
// Implement the WrapWriter interface.
|
||||
func (this *userResponseWriter) WrappedWriter() http.ResponseWriter {
|
||||
return this.ResponseWriter
|
||||
}
|
||||
|
||||
// Writes an unauthorized response to the client, specifying the expected authentication
|
||||
// information.
|
||||
func Unauthorized(w http.ResponseWriter, realm string) {
|
||||
w.Header().Set("Www-Authenticate", fmt.Sprintf(`Basic realm="%s"`, realm))
|
||||
w.WriteHeader(http.StatusUnauthorized)
|
||||
w.Write([]byte("Unauthorized"))
|
||||
}
|
||||
|
||||
// Writes a bad request response to the client, with an optional message.
|
||||
func BadRequest(w http.ResponseWriter, msg string) {
|
||||
w.WriteHeader(http.StatusBadRequest)
|
||||
if msg == "" {
|
||||
msg = "Bad Request"
|
||||
}
|
||||
w.Write([]byte(msg))
|
||||
}
|
||||
|
||||
// BasicAuthHandlerFunc is the same as BasicAuthHandler, it is just a convenience
|
||||
// signature that accepts a func(http.ResponseWriter, *http.Request) instead of
|
||||
// a http.Handler interface. It saves the boilerplate http.HandlerFunc() cast.
|
||||
func BasicAuthHandlerFunc(h http.HandlerFunc,
|
||||
authFn func(string, string) (interface{}, bool), realm string) http.HandlerFunc {
|
||||
return BasicAuthHandler(h, authFn, realm)
|
||||
}
|
||||
|
||||
// Returns a Basic Authentication handler, protecting the wrapped handler from
|
||||
// being accessed if the authentication function is not successful.
|
||||
func BasicAuthHandler(h http.Handler,
|
||||
authFn func(string, string) (interface{}, bool), realm string) http.HandlerFunc {
|
||||
|
||||
if realm == "" {
|
||||
realm = "Authorization Required"
|
||||
}
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
// Self-awareness
|
||||
if _, ok := GetUser(w); ok {
|
||||
h.ServeHTTP(w, r)
|
||||
return
|
||||
}
|
||||
authInfo := r.Header.Get("Authorization")
|
||||
if authInfo == "" {
|
||||
// No authorization info, return 401
|
||||
Unauthorized(w, realm)
|
||||
return
|
||||
}
|
||||
parts := strings.Split(authInfo, " ")
|
||||
if len(parts) != 2 {
|
||||
BadRequest(w, "Bad authorization header")
|
||||
return
|
||||
}
|
||||
scheme := parts[0]
|
||||
creds, err := base64.StdEncoding.DecodeString(parts[1])
|
||||
if err != nil {
|
||||
BadRequest(w, "Bad credentials encoding")
|
||||
return
|
||||
}
|
||||
index := bytes.Index(creds, []byte(":"))
|
||||
if scheme != "Basic" || index < 0 {
|
||||
BadRequest(w, "Bad authorization header")
|
||||
return
|
||||
}
|
||||
user, pwd := string(creds[:index]), string(creds[index+1:])
|
||||
udata, ok := authFn(user, pwd)
|
||||
if ok {
|
||||
// Save user data and continue
|
||||
uw := &userResponseWriter{w, udata, user}
|
||||
h.ServeHTTP(uw, r)
|
||||
} else {
|
||||
Unauthorized(w, realm)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Return the currently authenticated user. This is the same data that was returned
|
||||
// by the authentication function passed to BasicAuthHandler.
|
||||
func GetUser(w http.ResponseWriter) (interface{}, bool) {
|
||||
usr, ok := GetResponseWriter(w, func(tst http.ResponseWriter) bool {
|
||||
_, ok := tst.(*userResponseWriter)
|
||||
return ok
|
||||
})
|
||||
if ok {
|
||||
return usr.(*userResponseWriter).user, true
|
||||
}
|
||||
return nil, false
|
||||
}
|
||||
|
||||
// Return the currently authenticated user name. This is the user name that was
|
||||
// authenticated for the current request.
|
||||
func GetUserName(w http.ResponseWriter) (string, bool) {
|
||||
usr, ok := GetResponseWriter(w, func(tst http.ResponseWriter) bool {
|
||||
_, ok := tst.(*userResponseWriter)
|
||||
return ok
|
||||
})
|
||||
if ok {
|
||||
return usr.(*userResponseWriter).userName, true
|
||||
}
|
||||
return "", false
|
||||
}
|
62
vendor/github.com/PuerkitoBio/ghost/handlers/basicauth_test.go
generated
vendored
Normal file
62
vendor/github.com/PuerkitoBio/ghost/handlers/basicauth_test.go
generated
vendored
Normal file
@@ -0,0 +1,62 @@
|
||||
package handlers
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestUnauth(t *testing.T) {
|
||||
h := BasicAuthHandler(StaticFileHandler("./testdata/script.js"), func(u, pwd string) (interface{}, bool) {
|
||||
if u == "me" && pwd == "you" {
|
||||
return u, true
|
||||
}
|
||||
return nil, false
|
||||
}, "foo")
|
||||
s := httptest.NewServer(h)
|
||||
defer s.Close()
|
||||
|
||||
res, err := http.Get(s.URL)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
assertStatus(http.StatusUnauthorized, res.StatusCode, t)
|
||||
assertHeader("Www-Authenticate", `Basic realm="foo"`, res, t)
|
||||
}
|
||||
|
||||
func TestGzippedAuth(t *testing.T) {
|
||||
h := GZIPHandler(BasicAuthHandler(http.HandlerFunc(
|
||||
func(w http.ResponseWriter, r *http.Request) {
|
||||
usr, ok := GetUser(w)
|
||||
if assertTrue(ok, "expected authenticated user, got false", t) {
|
||||
assertTrue(usr.(string) == "meyou", fmt.Sprintf("expected user data to be 'meyou', got '%s'", usr), t)
|
||||
}
|
||||
usr, ok = GetUserName(w)
|
||||
if assertTrue(ok, "expected authenticated user name, got false", t) {
|
||||
assertTrue(usr == "me", fmt.Sprintf("expected user name to be 'me', got '%s'", usr), t)
|
||||
}
|
||||
w.Header().Set("Content-Type", "text/plain")
|
||||
w.Write([]byte(usr.(string)))
|
||||
}), func(u, pwd string) (interface{}, bool) {
|
||||
if u == "me" && pwd == "you" {
|
||||
return u + pwd, true
|
||||
}
|
||||
return nil, false
|
||||
}, ""), nil)
|
||||
|
||||
s := httptest.NewServer(h)
|
||||
defer s.Close()
|
||||
|
||||
req, err := http.NewRequest("GET", "http://me:you@"+s.URL[7:], nil)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
req.Header.Set("Accept-Encoding", "gzip")
|
||||
res, err := http.DefaultClient.Do(req)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
assertStatus(http.StatusOK, res.StatusCode, t)
|
||||
assertGzippedBody([]byte("me"), res, t)
|
||||
}
|
63
vendor/github.com/PuerkitoBio/ghost/handlers/chain.go
generated
vendored
Normal file
63
vendor/github.com/PuerkitoBio/ghost/handlers/chain.go
generated
vendored
Normal file
@@ -0,0 +1,63 @@
|
||||
package handlers
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
)
|
||||
|
||||
// ChainableHandler is a valid Handler interface, and adds the possibility to
|
||||
// chain other handlers.
|
||||
type ChainableHandler interface {
|
||||
http.Handler
|
||||
Chain(http.Handler) ChainableHandler
|
||||
ChainFunc(http.HandlerFunc) ChainableHandler
|
||||
}
|
||||
|
||||
// Default implementation of a simple ChainableHandler
|
||||
type chainHandler struct {
|
||||
http.Handler
|
||||
}
|
||||
|
||||
func (this *chainHandler) ChainFunc(h http.HandlerFunc) ChainableHandler {
|
||||
return this.Chain(h)
|
||||
}
|
||||
|
||||
// Implementation of the ChainableHandler interface, calls the chained handler
|
||||
// after the current one (sequential).
|
||||
func (this *chainHandler) Chain(h http.Handler) ChainableHandler {
|
||||
return &chainHandler{
|
||||
http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
// Add the chained handler after the call to this handler
|
||||
this.ServeHTTP(w, r)
|
||||
h.ServeHTTP(w, r)
|
||||
}),
|
||||
}
|
||||
}
|
||||
|
||||
// Convert a standard http handler to a chainable handler interface.
|
||||
func NewChainableHandler(h http.Handler) ChainableHandler {
|
||||
return &chainHandler{
|
||||
h,
|
||||
}
|
||||
}
|
||||
|
||||
// Helper function to chain multiple handler functions in a single call.
|
||||
func ChainHandlerFuncs(h ...http.HandlerFunc) ChainableHandler {
|
||||
return &chainHandler{
|
||||
http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
for _, v := range h {
|
||||
v(w, r)
|
||||
}
|
||||
}),
|
||||
}
|
||||
}
|
||||
|
||||
// Helper function to chain multiple handlers in a single call.
|
||||
func ChainHandlers(h ...http.Handler) ChainableHandler {
|
||||
return &chainHandler{
|
||||
http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
for _, v := range h {
|
||||
v.ServeHTTP(w, r)
|
||||
}
|
||||
}),
|
||||
}
|
||||
}
|
73
vendor/github.com/PuerkitoBio/ghost/handlers/chain_test.go
generated
vendored
Normal file
73
vendor/github.com/PuerkitoBio/ghost/handlers/chain_test.go
generated
vendored
Normal file
@@ -0,0 +1,73 @@
|
||||
package handlers
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"net/http"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestChaining(t *testing.T) {
|
||||
var buf bytes.Buffer
|
||||
|
||||
a := func(w http.ResponseWriter, r *http.Request) {
|
||||
buf.WriteRune('a')
|
||||
}
|
||||
b := func(w http.ResponseWriter, r *http.Request) {
|
||||
buf.WriteRune('b')
|
||||
}
|
||||
c := func(w http.ResponseWriter, r *http.Request) {
|
||||
buf.WriteRune('c')
|
||||
}
|
||||
f := NewChainableHandler(http.HandlerFunc(a)).Chain(http.HandlerFunc(b)).Chain(http.HandlerFunc(c))
|
||||
f.ServeHTTP(nil, nil)
|
||||
|
||||
if buf.String() != "abc" {
|
||||
t.Errorf("expected 'abc', got %s", buf.String())
|
||||
}
|
||||
}
|
||||
|
||||
func TestChainingWithHelperFunc(t *testing.T) {
|
||||
var buf bytes.Buffer
|
||||
|
||||
a := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
buf.WriteRune('a')
|
||||
})
|
||||
b := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
buf.WriteRune('b')
|
||||
})
|
||||
c := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
buf.WriteRune('c')
|
||||
})
|
||||
d := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
buf.WriteRune('d')
|
||||
})
|
||||
f := ChainHandlers(a, b, c, d)
|
||||
f.ServeHTTP(nil, nil)
|
||||
|
||||
if buf.String() != "abcd" {
|
||||
t.Errorf("expected 'abcd', got %s", buf.String())
|
||||
}
|
||||
}
|
||||
|
||||
func TestChainingMixed(t *testing.T) {
|
||||
var buf bytes.Buffer
|
||||
|
||||
a := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
buf.WriteRune('a')
|
||||
})
|
||||
b := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
buf.WriteRune('b')
|
||||
})
|
||||
c := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
buf.WriteRune('c')
|
||||
})
|
||||
d := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
buf.WriteRune('d')
|
||||
})
|
||||
f := NewChainableHandler(a).Chain(ChainHandlers(b, c)).Chain(d)
|
||||
f.ServeHTTP(nil, nil)
|
||||
|
||||
if buf.String() != "abcd" {
|
||||
t.Errorf("expected 'abcd', got %s", buf.String())
|
||||
}
|
||||
}
|
55
vendor/github.com/PuerkitoBio/ghost/handlers/context.go
generated
vendored
Normal file
55
vendor/github.com/PuerkitoBio/ghost/handlers/context.go
generated
vendored
Normal file
@@ -0,0 +1,55 @@
|
||||
package handlers
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
)
|
||||
|
||||
// Structure that holds the context map and exposes the ResponseWriter interface.
|
||||
type contextResponseWriter struct {
|
||||
http.ResponseWriter
|
||||
m map[interface{}]interface{}
|
||||
}
|
||||
|
||||
// Implement the WrapWriter interface.
|
||||
func (this *contextResponseWriter) WrappedWriter() http.ResponseWriter {
|
||||
return this.ResponseWriter
|
||||
}
|
||||
|
||||
// ContextHandlerFunc is the same as ContextHandler, it is just a convenience
|
||||
// signature that accepts a func(http.ResponseWriter, *http.Request) instead of
|
||||
// a http.Handler interface. It saves the boilerplate http.HandlerFunc() cast.
|
||||
func ContextHandlerFunc(h http.HandlerFunc, cap int) http.HandlerFunc {
|
||||
return ContextHandler(h, cap)
|
||||
}
|
||||
|
||||
// ContextHandler gives a context storage that lives only for the duration of
|
||||
// the request, with no locking involved.
|
||||
func ContextHandler(h http.Handler, cap int) http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
if _, ok := GetContext(w); ok {
|
||||
// Self-awareness, context handler is already set up
|
||||
h.ServeHTTP(w, r)
|
||||
return
|
||||
}
|
||||
|
||||
// Create the context-providing ResponseWriter replacement.
|
||||
ctxw := &contextResponseWriter{
|
||||
w,
|
||||
make(map[interface{}]interface{}, cap),
|
||||
}
|
||||
// Call the wrapped handler with the context-aware writer
|
||||
h.ServeHTTP(ctxw, r)
|
||||
}
|
||||
}
|
||||
|
||||
// Helper function to retrieve the context map from the ResponseWriter interface.
|
||||
func GetContext(w http.ResponseWriter) (map[interface{}]interface{}, bool) {
|
||||
ctxw, ok := GetResponseWriter(w, func(tst http.ResponseWriter) bool {
|
||||
_, ok := tst.(*contextResponseWriter)
|
||||
return ok
|
||||
})
|
||||
if ok {
|
||||
return ctxw.(*contextResponseWriter).m, true
|
||||
}
|
||||
return nil, false
|
||||
}
|
83
vendor/github.com/PuerkitoBio/ghost/handlers/context_test.go
generated
vendored
Normal file
83
vendor/github.com/PuerkitoBio/ghost/handlers/context_test.go
generated
vendored
Normal file
@@ -0,0 +1,83 @@
|
||||
package handlers
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestContext(t *testing.T) {
|
||||
key := "key"
|
||||
val := 10
|
||||
body := "this is the output"
|
||||
|
||||
h2 := wrappedHandler(t, key, val, body)
|
||||
// Create the context handler with a wrapped handler
|
||||
h := ContextHandler(http.HandlerFunc(
|
||||
func(w http.ResponseWriter, r *http.Request) {
|
||||
ctx, _ := GetContext(w)
|
||||
assertTrue(ctx != nil, "expected context to be non-nil", t)
|
||||
assertTrue(len(ctx) == 0, fmt.Sprintf("expected context to be empty, got %d", len(ctx)), t)
|
||||
ctx[key] = val
|
||||
h2.ServeHTTP(w, r)
|
||||
}), 2)
|
||||
s := httptest.NewServer(h)
|
||||
defer s.Close()
|
||||
|
||||
// First call
|
||||
res, err := http.DefaultClient.Get(s.URL)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
res.Body.Close()
|
||||
// Second call, context should be cleaned at start
|
||||
res, err = http.DefaultClient.Get(s.URL)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
assertStatus(http.StatusOK, res.StatusCode, t)
|
||||
assertBody([]byte(body), res, t)
|
||||
}
|
||||
|
||||
func TestWrappedContext(t *testing.T) {
|
||||
key := "key"
|
||||
val := 10
|
||||
body := "this is the output"
|
||||
|
||||
h2 := wrappedHandler(t, key, val, body)
|
||||
h := ContextHandler(LogHandler(http.HandlerFunc(
|
||||
func(w http.ResponseWriter, r *http.Request) {
|
||||
ctx, _ := GetContext(w)
|
||||
if !assertTrue(ctx != nil, "expected context to be non-nil", t) {
|
||||
panic("ctx is nil")
|
||||
}
|
||||
assertTrue(len(ctx) == 0, fmt.Sprintf("expected context to be empty, got %d", len(ctx)), t)
|
||||
ctx[key] = val
|
||||
h2.ServeHTTP(w, r)
|
||||
}), NewLogOptions(nil, "%s", "url")), 2)
|
||||
s := httptest.NewServer(h)
|
||||
defer s.Close()
|
||||
|
||||
res, err := http.DefaultClient.Get(s.URL)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
assertStatus(http.StatusOK, res.StatusCode, t)
|
||||
assertBody([]byte(body), res, t)
|
||||
}
|
||||
|
||||
func wrappedHandler(t *testing.T, k, v interface{}, body string) http.Handler {
|
||||
return http.HandlerFunc(
|
||||
func(w http.ResponseWriter, r *http.Request) {
|
||||
ctx, _ := GetContext(w)
|
||||
ac := ctx[k]
|
||||
assertTrue(ac == v, fmt.Sprintf("expected value to be %v, got %v", v, ac), t)
|
||||
|
||||
// Actually write something
|
||||
_, err := w.Write([]byte(body))
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
})
|
||||
}
|
29
vendor/github.com/PuerkitoBio/ghost/handlers/doc.go
generated
vendored
Normal file
29
vendor/github.com/PuerkitoBio/ghost/handlers/doc.go
generated
vendored
Normal file
@@ -0,0 +1,29 @@
|
||||
// Package handlers define reusable handler components that focus on offering
|
||||
// a single well-defined feature. Note that any http.Handler implementation
|
||||
// can be used with Ghost's chainable or wrappable handlers design.
|
||||
//
|
||||
// Go's standard library provides a number of such useful handlers in net/http:
|
||||
//
|
||||
// - FileServer(http.FileSystem)
|
||||
// - NotFoundHandler()
|
||||
// - RedirectHandler(string, int)
|
||||
// - StripPrefix(string, http.Handler)
|
||||
// - TimeoutHandler(http.Handler, time.Duration, string)
|
||||
//
|
||||
// This package adds the following list of handlers:
|
||||
//
|
||||
// - BasicAuthHandler(http.Handler, func(string, string) (interface{}, bool), string)
|
||||
// a Basic Authentication handler.
|
||||
// - ContextHandler(http.Handler, int) : a volatile storage map valid only
|
||||
// for the duration of the request, with no locking required.
|
||||
// - FaviconHandler(http.Handler, string, time.Duration) : an efficient favicon
|
||||
// handler.
|
||||
// - GZIPHandler(http.Handler) : compress the content of the body if the client
|
||||
// accepts gzip compression.
|
||||
// - LogHandler(http.Handler, *LogOptions) : customizable request logger.
|
||||
// - PanicHandler(http.Handler) : handle panics gracefully so that the client
|
||||
// receives a response (status code 500).
|
||||
// - SessionHandler(http.Handler, *SessionOptions) : a cookie-based, store-agnostic
|
||||
// persistent session handler.
|
||||
// - StaticFileHandler(string) : serve the contents of a specific file.
|
||||
package handlers
|
71
vendor/github.com/PuerkitoBio/ghost/handlers/favicon.go
generated
vendored
Normal file
71
vendor/github.com/PuerkitoBio/ghost/handlers/favicon.go
generated
vendored
Normal file
@@ -0,0 +1,71 @@
|
||||
package handlers
|
||||
|
||||
import (
|
||||
"crypto/md5"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
"github.com/PuerkitoBio/ghost"
|
||||
)
|
||||
|
||||
// FaviconHandlerFunc is the same as FaviconHandler, it is just a convenience
|
||||
// signature that accepts a func(http.ResponseWriter, *http.Request) instead of
|
||||
// a http.Handler interface. It saves the boilerplate http.HandlerFunc() cast.
|
||||
func FaviconHandlerFunc(h http.HandlerFunc, path string, maxAge time.Duration) http.HandlerFunc {
|
||||
return FaviconHandler(h, path, maxAge)
|
||||
}
|
||||
|
||||
// Efficient favicon handler, mostly a port of node's Connect library implementation
|
||||
// of the favicon middleware.
|
||||
// https://github.com/senchalabs/connect
|
||||
func FaviconHandler(h http.Handler, path string, maxAge time.Duration) http.HandlerFunc {
|
||||
var buf []byte
|
||||
var hash string
|
||||
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
var err error
|
||||
if r.URL.Path == "/favicon.ico" {
|
||||
if buf == nil {
|
||||
// Read from file and cache
|
||||
ghost.LogFn("ghost.favicon : serving from %s", path)
|
||||
buf, err = ioutil.ReadFile(path)
|
||||
if err != nil {
|
||||
ghost.LogFn("ghost.favicon : error reading file : %s", err)
|
||||
http.NotFound(w, r)
|
||||
return
|
||||
}
|
||||
hash = hashContent(buf)
|
||||
}
|
||||
writeHeaders(w.Header(), buf, maxAge, hash)
|
||||
writeBody(w, r, buf)
|
||||
} else {
|
||||
h.ServeHTTP(w, r)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Write the content of the favicon, or respond with a 404 not found
|
||||
// in case of error (hardly a critical error).
|
||||
func writeBody(w http.ResponseWriter, r *http.Request, buf []byte) {
|
||||
_, err := w.Write(buf)
|
||||
if err != nil {
|
||||
ghost.LogFn("ghost.favicon : error writing response : %s", err)
|
||||
http.NotFound(w, r)
|
||||
}
|
||||
}
|
||||
|
||||
// Correctly set the http headers.
|
||||
func writeHeaders(hdr http.Header, buf []byte, maxAge time.Duration, hash string) {
|
||||
hdr.Set("Content-Type", "image/x-icon")
|
||||
hdr.Set("Content-Length", strconv.Itoa(len(buf)))
|
||||
hdr.Set("Etag", hash)
|
||||
hdr.Set("Cache-Control", "public, max-age="+strconv.Itoa(int(maxAge.Seconds())))
|
||||
}
|
||||
|
||||
// Get the MD5 hash of the content.
|
||||
func hashContent(buf []byte) string {
|
||||
h := md5.New()
|
||||
return string(h.Sum(buf))
|
||||
}
|
72
vendor/github.com/PuerkitoBio/ghost/handlers/favicon_test.go
generated
vendored
Normal file
72
vendor/github.com/PuerkitoBio/ghost/handlers/favicon_test.go
generated
vendored
Normal file
@@ -0,0 +1,72 @@
|
||||
package handlers
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"os"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
func TestFavicon(t *testing.T) {
|
||||
s := httptest.NewServer(FaviconHandlerFunc(
|
||||
func(w http.ResponseWriter, r *http.Request) {
|
||||
w.Write([]byte("ok"))
|
||||
}, "./testdata/favicon.ico", time.Second))
|
||||
defer s.Close()
|
||||
|
||||
res, err := http.Get(s.URL + "/favicon.ico")
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
defer res.Body.Close()
|
||||
assertStatus(http.StatusOK, res.StatusCode, t)
|
||||
assertHeader("Content-Type", "image/x-icon", res, t)
|
||||
assertHeader("Cache-Control", "public, max-age=1", res, t)
|
||||
assertHeader("Content-Length", "1406", res, t)
|
||||
}
|
||||
|
||||
func TestFaviconInvalidPath(t *testing.T) {
|
||||
s := httptest.NewServer(FaviconHandlerFunc(
|
||||
func(w http.ResponseWriter, r *http.Request) {
|
||||
w.Write([]byte("ok"))
|
||||
}, "./testdata/xfavicon.ico", time.Second))
|
||||
defer s.Close()
|
||||
|
||||
res, err := http.Get(s.URL + "/favicon.ico")
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
defer res.Body.Close()
|
||||
assertStatus(http.StatusNotFound, res.StatusCode, t)
|
||||
}
|
||||
|
||||
func TestFaviconFromCache(t *testing.T) {
|
||||
s := httptest.NewServer(FaviconHandlerFunc(
|
||||
func(w http.ResponseWriter, r *http.Request) {
|
||||
w.Write([]byte("ok"))
|
||||
}, "./testdata/favicon.ico", time.Second))
|
||||
defer s.Close()
|
||||
|
||||
res, err := http.Get(s.URL + "/favicon.ico")
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
defer res.Body.Close()
|
||||
|
||||
// Rename the file temporarily
|
||||
err = os.Rename("./testdata/favicon.ico", "./testdata/xfavicon.ico")
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
defer os.Rename("./testdata/xfavicon.ico", "./testdata/favicon.ico")
|
||||
res, err = http.Get(s.URL + "/favicon.ico")
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
defer res.Body.Close()
|
||||
assertStatus(http.StatusOK, res.StatusCode, t)
|
||||
assertHeader("Content-Type", "image/x-icon", res, t)
|
||||
assertHeader("Cache-Control", "public, max-age=1", res, t)
|
||||
assertHeader("Content-Length", "1406", res, t)
|
||||
}
|
75
vendor/github.com/PuerkitoBio/ghost/handlers/ghost.go
generated
vendored
Normal file
75
vendor/github.com/PuerkitoBio/ghost/handlers/ghost.go
generated
vendored
Normal file
@@ -0,0 +1,75 @@
|
||||
package handlers
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
)
|
||||
|
||||
// Interface giving easy access to the most common augmented features.
|
||||
type GhostWriter interface {
|
||||
http.ResponseWriter
|
||||
UserName() string
|
||||
User() interface{}
|
||||
Context() map[interface{}]interface{}
|
||||
Session() *Session
|
||||
}
|
||||
|
||||
// Internal implementation of the GhostWriter interface.
|
||||
type ghostWriter struct {
|
||||
http.ResponseWriter
|
||||
userName string
|
||||
user interface{}
|
||||
ctx map[interface{}]interface{}
|
||||
ssn *Session
|
||||
}
|
||||
|
||||
func (this *ghostWriter) UserName() string {
|
||||
return this.userName
|
||||
}
|
||||
|
||||
func (this *ghostWriter) User() interface{} {
|
||||
return this.user
|
||||
}
|
||||
|
||||
func (this *ghostWriter) Context() map[interface{}]interface{} {
|
||||
return this.ctx
|
||||
}
|
||||
|
||||
func (this *ghostWriter) Session() *Session {
|
||||
return this.ssn
|
||||
}
|
||||
|
||||
// Convenience handler that wraps a custom function with direct access to the
|
||||
// authenticated user, context and session on the writer.
|
||||
func GhostHandlerFunc(h func(w GhostWriter, r *http.Request)) http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
if gw, ok := getGhostWriter(w); ok {
|
||||
// Self-awareness
|
||||
h(gw, r)
|
||||
return
|
||||
}
|
||||
uid, _ := GetUserName(w)
|
||||
usr, _ := GetUser(w)
|
||||
ctx, _ := GetContext(w)
|
||||
ssn, _ := GetSession(w)
|
||||
gw := &ghostWriter{
|
||||
w,
|
||||
uid,
|
||||
usr,
|
||||
ctx,
|
||||
ssn,
|
||||
}
|
||||
h(gw, r)
|
||||
}
|
||||
}
|
||||
|
||||
// Check the writer chain to find a ghostWriter.
|
||||
func getGhostWriter(w http.ResponseWriter) (*ghostWriter, bool) {
|
||||
gw, ok := GetResponseWriter(w, func(tst http.ResponseWriter) bool {
|
||||
_, ok := tst.(*ghostWriter)
|
||||
return ok
|
||||
})
|
||||
if ok {
|
||||
return gw.(*ghostWriter), true
|
||||
}
|
||||
return nil, false
|
||||
}
|
168
vendor/github.com/PuerkitoBio/ghost/handlers/gzip.go
generated
vendored
Normal file
168
vendor/github.com/PuerkitoBio/ghost/handlers/gzip.go
generated
vendored
Normal file
@@ -0,0 +1,168 @@
|
||||
package handlers
|
||||
|
||||
import (
|
||||
"compress/gzip"
|
||||
"io"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
// Thanks to Andrew Gerrand for inspiration:
|
||||
// https://groups.google.com/d/msg/golang-nuts/eVnTcMwNVjM/4vYU8id9Q2UJ
|
||||
//
|
||||
// Also, node's Connect library implementation of the compress middleware:
|
||||
// https://github.com/senchalabs/connect/blob/master/lib/middleware/compress.js
|
||||
//
|
||||
// And StackOverflow's explanation of Vary: Accept-Encoding header:
|
||||
// http://stackoverflow.com/questions/7848796/what-does-varyaccept-encoding-mean
|
||||
|
||||
// Internal gzipped writer that satisfies both the (body) writer in gzipped format,
|
||||
// and maintains the rest of the ResponseWriter interface for header manipulation.
|
||||
type gzipResponseWriter struct {
|
||||
io.Writer
|
||||
http.ResponseWriter
|
||||
r *http.Request // Keep a hold of the Request, for the filter function
|
||||
filtered bool // Has the request been run through the filter function?
|
||||
dogzip bool // Should we do GZIP compression for this request?
|
||||
filterFn func(http.ResponseWriter, *http.Request) bool
|
||||
}
|
||||
|
||||
// Make sure the filter function is applied.
|
||||
func (w *gzipResponseWriter) applyFilter() {
|
||||
if !w.filtered {
|
||||
if w.dogzip = w.filterFn(w, w.r); w.dogzip {
|
||||
setGzipHeaders(w.Header())
|
||||
}
|
||||
w.filtered = true
|
||||
}
|
||||
}
|
||||
|
||||
// Unambiguous Write() implementation (otherwise both ResponseWriter and Writer
|
||||
// want to claim this method).
|
||||
func (w *gzipResponseWriter) Write(b []byte) (int, error) {
|
||||
w.applyFilter()
|
||||
if w.dogzip {
|
||||
// Write compressed
|
||||
return w.Writer.Write(b)
|
||||
}
|
||||
// Write uncompressed
|
||||
return w.ResponseWriter.Write(b)
|
||||
}
|
||||
|
||||
// Intercept the WriteHeader call to correctly set the GZIP headers.
|
||||
func (w *gzipResponseWriter) WriteHeader(code int) {
|
||||
w.applyFilter()
|
||||
w.ResponseWriter.WriteHeader(code)
|
||||
}
|
||||
|
||||
// Implement WrapWriter interface
|
||||
func (w *gzipResponseWriter) WrappedWriter() http.ResponseWriter {
|
||||
return w.ResponseWriter
|
||||
}
|
||||
|
||||
var (
|
||||
defaultFilterTypes = [...]string{
|
||||
"text",
|
||||
"javascript",
|
||||
"json",
|
||||
}
|
||||
)
|
||||
|
||||
// Default filter to check if the response should be GZIPped.
|
||||
// By default, all text (html, css, xml, ...), javascript and json
|
||||
// content types are candidates for GZIP.
|
||||
func defaultFilter(w http.ResponseWriter, r *http.Request) bool {
|
||||
hdr := w.Header()
|
||||
for _, tp := range defaultFilterTypes {
|
||||
ok := HeaderMatch(hdr, "Content-Type", HmContains, tp)
|
||||
if ok {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// GZIPHandlerFunc is the same as GZIPHandler, it is just a convenience
|
||||
// signature that accepts a func(http.ResponseWriter, *http.Request) instead of
|
||||
// a http.Handler interface. It saves the boilerplate http.HandlerFunc() cast.
|
||||
func GZIPHandlerFunc(h http.HandlerFunc, filterFn func(http.ResponseWriter, *http.Request) bool) http.HandlerFunc {
|
||||
return GZIPHandler(h, filterFn)
|
||||
}
|
||||
|
||||
// Gzip compression HTTP handler. If the client supports it, it compresses the response
|
||||
// written by the wrapped handler. The filter function is called when the response is about
|
||||
// to be written to determine if compression should be applied. If this argument is nil,
|
||||
// the default filter will GZIP only content types containing /json|text|javascript/.
|
||||
func GZIPHandler(h http.Handler, filterFn func(http.ResponseWriter, *http.Request) bool) http.HandlerFunc {
|
||||
if filterFn == nil {
|
||||
filterFn = defaultFilter
|
||||
}
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
if _, ok := getGzipWriter(w); ok {
|
||||
// Self-awareness, gzip handler is already set up
|
||||
h.ServeHTTP(w, r)
|
||||
return
|
||||
}
|
||||
hdr := w.Header()
|
||||
setVaryHeader(hdr)
|
||||
|
||||
// Do nothing on a HEAD request
|
||||
if r.Method == "HEAD" {
|
||||
h.ServeHTTP(w, r)
|
||||
return
|
||||
}
|
||||
if !acceptsGzip(r.Header) {
|
||||
// No gzip support from the client, return uncompressed
|
||||
h.ServeHTTP(w, r)
|
||||
return
|
||||
}
|
||||
|
||||
// Prepare a gzip response container
|
||||
gz := gzip.NewWriter(w)
|
||||
gzw := &gzipResponseWriter{
|
||||
Writer: gz,
|
||||
ResponseWriter: w,
|
||||
r: r,
|
||||
filterFn: filterFn,
|
||||
}
|
||||
h.ServeHTTP(gzw, r)
|
||||
// Iff the handler completed successfully (no panic) and GZIP was indeed used, close the gzip writer,
|
||||
// which seems to generate a Write to the underlying writer.
|
||||
if gzw.dogzip {
|
||||
gz.Close()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Add the vary by "accept-encoding" header if it is not already set.
|
||||
func setVaryHeader(hdr http.Header) {
|
||||
if !HeaderMatch(hdr, "Vary", HmContains, "accept-encoding") {
|
||||
hdr.Add("Vary", "Accept-Encoding")
|
||||
}
|
||||
}
|
||||
|
||||
// Checks if the client accepts GZIP-encoded responses.
|
||||
func acceptsGzip(hdr http.Header) bool {
|
||||
ok := HeaderMatch(hdr, "Accept-Encoding", HmContains, "gzip")
|
||||
if !ok {
|
||||
ok = HeaderMatch(hdr, "Accept-Encoding", HmEquals, "*")
|
||||
}
|
||||
return ok
|
||||
}
|
||||
|
||||
func setGzipHeaders(hdr http.Header) {
|
||||
// The content-type will be explicitly set somewhere down the path of handlers
|
||||
hdr.Set("Content-Encoding", "gzip")
|
||||
hdr.Del("Content-Length")
|
||||
}
|
||||
|
||||
// Helper function to retrieve the gzip writer.
|
||||
func getGzipWriter(w http.ResponseWriter) (*gzipResponseWriter, bool) {
|
||||
gz, ok := GetResponseWriter(w, func(tst http.ResponseWriter) bool {
|
||||
_, ok := tst.(*gzipResponseWriter)
|
||||
return ok
|
||||
})
|
||||
if ok {
|
||||
return gz.(*gzipResponseWriter), true
|
||||
}
|
||||
return nil, false
|
||||
}
|
178
vendor/github.com/PuerkitoBio/ghost/handlers/gzip_test.go
generated
vendored
Normal file
178
vendor/github.com/PuerkitoBio/ghost/handlers/gzip_test.go
generated
vendored
Normal file
@@ -0,0 +1,178 @@
|
||||
package handlers
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestGzipped(t *testing.T) {
|
||||
body := "This is the body"
|
||||
headers := []string{"gzip", "*", "gzip, deflate, sdch"}
|
||||
|
||||
h := GZIPHandler(http.HandlerFunc(
|
||||
func(w http.ResponseWriter, r *http.Request) {
|
||||
w.Header().Set("Content-Type", "text/plain")
|
||||
_, err := w.Write([]byte(body))
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}), nil)
|
||||
s := httptest.NewServer(h)
|
||||
defer s.Close()
|
||||
|
||||
for _, hdr := range headers {
|
||||
t.Logf("running with Accept-Encoding header %s", hdr)
|
||||
req, err := http.NewRequest("GET", s.URL, nil)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
req.Header.Set("Accept-Encoding", hdr)
|
||||
res, err := http.DefaultClient.Do(req)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
assertStatus(http.StatusOK, res.StatusCode, t)
|
||||
assertHeader("Content-Encoding", "gzip", res, t)
|
||||
assertGzippedBody([]byte(body), res, t)
|
||||
}
|
||||
}
|
||||
|
||||
func TestNoGzip(t *testing.T) {
|
||||
body := "This is the body"
|
||||
|
||||
h := GZIPHandler(http.HandlerFunc(
|
||||
func(w http.ResponseWriter, r *http.Request) {
|
||||
w.Header().Set("Content-Type", "text/plain")
|
||||
_, err := w.Write([]byte(body))
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}), nil)
|
||||
s := httptest.NewServer(h)
|
||||
defer s.Close()
|
||||
|
||||
req, err := http.NewRequest("GET", s.URL, nil)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
res, err := http.DefaultClient.Do(req)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
assertStatus(http.StatusOK, res.StatusCode, t)
|
||||
assertHeader("Content-Encoding", "", res, t)
|
||||
assertBody([]byte(body), res, t)
|
||||
}
|
||||
|
||||
func TestGzipOuterPanic(t *testing.T) {
|
||||
msg := "ko"
|
||||
|
||||
h := PanicHandler(
|
||||
GZIPHandler(http.HandlerFunc(
|
||||
func(w http.ResponseWriter, r *http.Request) {
|
||||
panic(msg)
|
||||
}), nil), nil)
|
||||
s := httptest.NewServer(h)
|
||||
defer s.Close()
|
||||
|
||||
req, err := http.NewRequest("GET", s.URL, nil)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
res, err := http.DefaultClient.Do(req)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
assertStatus(http.StatusInternalServerError, res.StatusCode, t)
|
||||
assertHeader("Content-Encoding", "", res, t)
|
||||
assertBody([]byte(msg+"\n"), res, t)
|
||||
}
|
||||
|
||||
func TestNoGzipOnFilter(t *testing.T) {
|
||||
body := "This is the body"
|
||||
|
||||
h := GZIPHandler(http.HandlerFunc(
|
||||
func(w http.ResponseWriter, r *http.Request) {
|
||||
w.Header().Set("Content-Type", "x/x")
|
||||
_, err := w.Write([]byte(body))
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}), nil)
|
||||
s := httptest.NewServer(h)
|
||||
defer s.Close()
|
||||
|
||||
req, err := http.NewRequest("GET", s.URL, nil)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
req.Header.Set("Accept-Encoding", "gzip")
|
||||
res, err := http.DefaultClient.Do(req)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
assertStatus(http.StatusOK, res.StatusCode, t)
|
||||
assertHeader("Content-Encoding", "", res, t)
|
||||
assertBody([]byte(body), res, t)
|
||||
}
|
||||
|
||||
func TestNoGzipOnCustomFilter(t *testing.T) {
|
||||
body := "This is the body"
|
||||
|
||||
h := GZIPHandler(http.HandlerFunc(
|
||||
func(w http.ResponseWriter, r *http.Request) {
|
||||
w.Header().Set("Content-Type", "text/plain")
|
||||
_, err := w.Write([]byte(body))
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}), func(w http.ResponseWriter, r *http.Request) bool {
|
||||
return false
|
||||
})
|
||||
s := httptest.NewServer(h)
|
||||
defer s.Close()
|
||||
|
||||
req, err := http.NewRequest("GET", s.URL, nil)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
req.Header.Set("Accept-Encoding", "gzip")
|
||||
res, err := http.DefaultClient.Do(req)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
assertStatus(http.StatusOK, res.StatusCode, t)
|
||||
assertHeader("Content-Encoding", "", res, t)
|
||||
assertBody([]byte(body), res, t)
|
||||
}
|
||||
|
||||
func TestGzipOnCustomFilter(t *testing.T) {
|
||||
body := "This is the body"
|
||||
|
||||
h := GZIPHandler(http.HandlerFunc(
|
||||
func(w http.ResponseWriter, r *http.Request) {
|
||||
w.Header().Set("Content-Type", "x/x")
|
||||
_, err := w.Write([]byte(body))
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}), func(w http.ResponseWriter, r *http.Request) bool {
|
||||
return true
|
||||
})
|
||||
s := httptest.NewServer(h)
|
||||
defer s.Close()
|
||||
|
||||
req, err := http.NewRequest("GET", s.URL, nil)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
req.Header.Set("Accept-Encoding", "gzip")
|
||||
res, err := http.DefaultClient.Do(req)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
assertStatus(http.StatusOK, res.StatusCode, t)
|
||||
assertHeader("Content-Encoding", "gzip", res, t)
|
||||
assertGzippedBody([]byte(body), res, t)
|
||||
}
|
50
vendor/github.com/PuerkitoBio/ghost/handlers/header.go
generated
vendored
Normal file
50
vendor/github.com/PuerkitoBio/ghost/handlers/header.go
generated
vendored
Normal file
@@ -0,0 +1,50 @@
|
||||
package handlers
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// Kind of match to apply to the header check.
|
||||
type HeaderMatchType int
|
||||
|
||||
const (
|
||||
HmEquals HeaderMatchType = iota
|
||||
HmStartsWith
|
||||
HmEndsWith
|
||||
HmContains
|
||||
)
|
||||
|
||||
// Check if the specified header matches the test string, applying the header match type
|
||||
// specified.
|
||||
func HeaderMatch(hdr http.Header, nm string, matchType HeaderMatchType, test string) bool {
|
||||
// First get the header value
|
||||
val := hdr[http.CanonicalHeaderKey(nm)]
|
||||
if len(val) == 0 {
|
||||
return false
|
||||
}
|
||||
// Prepare the match test
|
||||
test = strings.ToLower(test)
|
||||
for _, v := range val {
|
||||
v = strings.Trim(strings.ToLower(v), " \n\t")
|
||||
switch matchType {
|
||||
case HmEquals:
|
||||
if v == test {
|
||||
return true
|
||||
}
|
||||
case HmStartsWith:
|
||||
if strings.HasPrefix(v, test) {
|
||||
return true
|
||||
}
|
||||
case HmEndsWith:
|
||||
if strings.HasSuffix(v, test) {
|
||||
return true
|
||||
}
|
||||
case HmContains:
|
||||
if strings.Contains(v, test) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
231
vendor/github.com/PuerkitoBio/ghost/handlers/log.go
generated
vendored
Normal file
231
vendor/github.com/PuerkitoBio/ghost/handlers/log.go
generated
vendored
Normal file
@@ -0,0 +1,231 @@
|
||||
package handlers
|
||||
|
||||
// Inspired by node's Connect library implementation of the logging middleware
|
||||
// https://github.com/senchalabs/connect
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
"regexp"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/PuerkitoBio/ghost"
|
||||
)
|
||||
|
||||
const (
|
||||
// Predefined logging formats that can be passed as format string.
|
||||
Ldefault = "_default_"
|
||||
Lshort = "_short_"
|
||||
Ltiny = "_tiny_"
|
||||
)
|
||||
|
||||
var (
|
||||
// Token parser for request and response headers
|
||||
rxHeaders = regexp.MustCompile(`^(req|res)\[([^\]]+)\]$`)
|
||||
|
||||
// Lookup table for predefined formats
|
||||
predefFormats = map[string]struct {
|
||||
fmt string
|
||||
toks []string
|
||||
}{
|
||||
Ldefault: {
|
||||
`%s - - [%s] "%s %s HTTP/%s" %d %s "%s" "%s"`,
|
||||
[]string{"remote-addr", "date", "method", "url", "http-version", "status", "res[Content-Length]", "referrer", "user-agent"},
|
||||
},
|
||||
Lshort: {
|
||||
`%s - %s %s HTTP/%s %d %s - %.3f s`,
|
||||
[]string{"remote-addr", "method", "url", "http-version", "status", "res[Content-Length]", "response-time"},
|
||||
},
|
||||
Ltiny: {
|
||||
`%s %s %d %s - %.3f s`,
|
||||
[]string{"method", "url", "status", "res[Content-Length]", "response-time"},
|
||||
},
|
||||
}
|
||||
)
|
||||
|
||||
// Augmented ResponseWriter implementation that captures the status code for the logger.
|
||||
type statusResponseWriter struct {
|
||||
http.ResponseWriter
|
||||
code int
|
||||
oriURL string
|
||||
}
|
||||
|
||||
// Intercept the WriteHeader call to save the status code.
|
||||
func (this *statusResponseWriter) WriteHeader(code int) {
|
||||
this.code = code
|
||||
this.ResponseWriter.WriteHeader(code)
|
||||
}
|
||||
|
||||
// Intercept the Write call to save the default status code.
|
||||
func (this *statusResponseWriter) Write(data []byte) (int, error) {
|
||||
if this.code == 0 {
|
||||
this.code = http.StatusOK
|
||||
}
|
||||
return this.ResponseWriter.Write(data)
|
||||
}
|
||||
|
||||
// Implement the WrapWriter interface.
|
||||
func (this *statusResponseWriter) WrappedWriter() http.ResponseWriter {
|
||||
return this.ResponseWriter
|
||||
}
|
||||
|
||||
// LogHandler options
|
||||
type LogOptions struct {
|
||||
LogFn func(string, ...interface{}) // Defaults to ghost.LogFn if nil
|
||||
Format string
|
||||
Tokens []string
|
||||
CustomTokens map[string]func(http.ResponseWriter, *http.Request) string
|
||||
Immediate bool
|
||||
DateFormat string
|
||||
}
|
||||
|
||||
// Create a new LogOptions struct. The DateFormat defaults to time.RFC3339.
|
||||
func NewLogOptions(l func(string, ...interface{}), ft string, tok ...string) *LogOptions {
|
||||
return &LogOptions{
|
||||
LogFn: l,
|
||||
Format: ft,
|
||||
Tokens: tok,
|
||||
CustomTokens: make(map[string]func(http.ResponseWriter, *http.Request) string),
|
||||
DateFormat: time.RFC3339,
|
||||
}
|
||||
}
|
||||
|
||||
// LogHandlerFunc is the same as LogHandler, it is just a convenience
|
||||
// signature that accepts a func(http.ResponseWriter, *http.Request) instead of
|
||||
// a http.Handler interface. It saves the boilerplate http.HandlerFunc() cast.
|
||||
func LogHandlerFunc(h http.HandlerFunc, opts *LogOptions) http.HandlerFunc {
|
||||
return LogHandler(h, opts)
|
||||
}
|
||||
|
||||
// Create a log handler for every request it receives.
|
||||
func LogHandler(h http.Handler, opts *LogOptions) http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
if _, ok := getStatusWriter(w); ok {
|
||||
// Self-awareness, logging handler already set up
|
||||
h.ServeHTTP(w, r)
|
||||
return
|
||||
}
|
||||
|
||||
// Save the response start time
|
||||
st := time.Now()
|
||||
// Call the wrapped handler, with the augmented ResponseWriter to handle the status code
|
||||
stw := &statusResponseWriter{w, 0, ""}
|
||||
|
||||
// Log immediately if requested, otherwise on exit
|
||||
if opts.Immediate {
|
||||
logRequest(stw, r, st, opts)
|
||||
} else {
|
||||
// Store original URL, may get modified by handlers (i.e. StripPrefix)
|
||||
stw.oriURL = r.URL.String()
|
||||
defer logRequest(stw, r, st, opts)
|
||||
}
|
||||
h.ServeHTTP(stw, r)
|
||||
}
|
||||
}
|
||||
|
||||
func getIpAddress(r *http.Request) string {
|
||||
hdr := r.Header
|
||||
hdrRealIp := hdr.Get("X-Real-Ip")
|
||||
hdrForwardedFor := hdr.Get("X-Forwarded-For")
|
||||
if hdrRealIp == "" && hdrForwardedFor == "" {
|
||||
return r.RemoteAddr
|
||||
}
|
||||
if hdrForwardedFor != "" {
|
||||
// X-Forwarded-For is potentially a list of addresses separated with ","
|
||||
part := strings.Split(hdrForwardedFor, ",")[0]
|
||||
return strings.TrimSpace(part) + ":0"
|
||||
}
|
||||
return hdrRealIp
|
||||
}
|
||||
|
||||
// Check if the specified token is a predefined one, and if so return its current value.
|
||||
func getPredefinedTokenValue(t string, w *statusResponseWriter, r *http.Request,
|
||||
st time.Time, opts *LogOptions) (interface{}, bool) {
|
||||
|
||||
switch t {
|
||||
case "http-version":
|
||||
return fmt.Sprintf("%d.%d", r.ProtoMajor, r.ProtoMinor), true
|
||||
case "response-time":
|
||||
return time.Now().Sub(st).Seconds(), true
|
||||
case "remote-addr":
|
||||
return getIpAddress(r), true
|
||||
case "date":
|
||||
return time.Now().Format(opts.DateFormat), true
|
||||
case "method":
|
||||
return r.Method, true
|
||||
case "url":
|
||||
if w.oriURL != "" {
|
||||
return w.oriURL, true
|
||||
}
|
||||
return r.URL.String(), true
|
||||
case "referrer", "referer":
|
||||
return r.Referer(), true
|
||||
case "user-agent":
|
||||
return r.UserAgent(), true
|
||||
case "status":
|
||||
return w.code, true
|
||||
}
|
||||
|
||||
// Handle special cases for header
|
||||
mtch := rxHeaders.FindStringSubmatch(t)
|
||||
if len(mtch) > 2 {
|
||||
if mtch[1] == "req" {
|
||||
return r.Header.Get(mtch[2]), true
|
||||
} else {
|
||||
// This only works for headers explicitly set via the Header() map of
|
||||
// the writer, not those added by the http package under the covers.
|
||||
return w.Header().Get(mtch[2]), true
|
||||
}
|
||||
}
|
||||
return nil, false
|
||||
}
|
||||
|
||||
// Do the actual logging.
|
||||
func logRequest(w *statusResponseWriter, r *http.Request, st time.Time, opts *LogOptions) {
|
||||
var (
|
||||
fn func(string, ...interface{})
|
||||
ok bool
|
||||
format string
|
||||
toks []string
|
||||
)
|
||||
|
||||
// If no specific log function, use the default one from the ghost package
|
||||
if opts.LogFn == nil {
|
||||
fn = ghost.LogFn
|
||||
} else {
|
||||
fn = opts.LogFn
|
||||
}
|
||||
|
||||
// If this is a predefined format, use it instead
|
||||
if v, ok := predefFormats[opts.Format]; ok {
|
||||
format = v.fmt
|
||||
toks = v.toks
|
||||
} else {
|
||||
format = opts.Format
|
||||
toks = opts.Tokens
|
||||
}
|
||||
args := make([]interface{}, len(toks))
|
||||
for i, t := range toks {
|
||||
if args[i], ok = getPredefinedTokenValue(t, w, r, st, opts); !ok {
|
||||
if f, ok := opts.CustomTokens[t]; ok && f != nil {
|
||||
args[i] = f(w, r)
|
||||
} else {
|
||||
args[i] = "?"
|
||||
}
|
||||
}
|
||||
}
|
||||
fn(format, args...)
|
||||
}
|
||||
|
||||
// Helper function to retrieve the status writer.
|
||||
func getStatusWriter(w http.ResponseWriter) (*statusResponseWriter, bool) {
|
||||
st, ok := GetResponseWriter(w, func(tst http.ResponseWriter) bool {
|
||||
_, ok := tst.(*statusResponseWriter)
|
||||
return ok
|
||||
})
|
||||
if ok {
|
||||
return st.(*statusResponseWriter), true
|
||||
}
|
||||
return nil, false
|
||||
}
|
217
vendor/github.com/PuerkitoBio/ghost/handlers/log_test.go
generated
vendored
Normal file
217
vendor/github.com/PuerkitoBio/ghost/handlers/log_test.go
generated
vendored
Normal file
@@ -0,0 +1,217 @@
|
||||
package handlers
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"log"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"regexp"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
type testCase struct {
|
||||
tok string
|
||||
fmt string
|
||||
rx *regexp.Regexp
|
||||
}
|
||||
|
||||
func TestLog(t *testing.T) {
|
||||
log.SetFlags(0)
|
||||
now := time.Now()
|
||||
|
||||
formats := []testCase{
|
||||
testCase{"remote-addr",
|
||||
"%s",
|
||||
regexp.MustCompile(`^127\.0\.0\.1:\d+\n$`),
|
||||
},
|
||||
testCase{"date",
|
||||
"%s",
|
||||
regexp.MustCompile(`^` + fmt.Sprintf("%04d-%02d-%02d", now.Year(), now.Month(), now.Day()) + `\n$`),
|
||||
},
|
||||
testCase{"method",
|
||||
"%s",
|
||||
regexp.MustCompile(`^GET\n$`),
|
||||
},
|
||||
testCase{"url",
|
||||
"%s",
|
||||
regexp.MustCompile(`^/\n$`),
|
||||
},
|
||||
testCase{"http-version",
|
||||
"%s",
|
||||
regexp.MustCompile(`^1\.1\n$`),
|
||||
},
|
||||
testCase{"status",
|
||||
"%d",
|
||||
regexp.MustCompile(`^200\n$`),
|
||||
},
|
||||
testCase{"referer",
|
||||
"%s",
|
||||
regexp.MustCompile(`^http://www\.test\.com\n$`),
|
||||
},
|
||||
testCase{"referrer",
|
||||
"%s",
|
||||
regexp.MustCompile(`^http://www\.test\.com\n$`),
|
||||
},
|
||||
testCase{"user-agent",
|
||||
"%s",
|
||||
regexp.MustCompile(`^Go \d+\.\d+ package http\n$`),
|
||||
},
|
||||
testCase{"bidon",
|
||||
"%s",
|
||||
regexp.MustCompile(`^\?\n$`),
|
||||
},
|
||||
testCase{"response-time",
|
||||
"%.3f",
|
||||
regexp.MustCompile(`^0\.1\d\d\n$`),
|
||||
},
|
||||
testCase{"req[Accept-Encoding]",
|
||||
"%s",
|
||||
regexp.MustCompile(`^gzip\n$`),
|
||||
},
|
||||
testCase{"res[blah]",
|
||||
"%s",
|
||||
regexp.MustCompile(`^$`),
|
||||
},
|
||||
testCase{"tiny",
|
||||
Ltiny,
|
||||
regexp.MustCompile(`^GET / 200 - 0\.1\d\d s\n$`),
|
||||
},
|
||||
testCase{"short",
|
||||
Lshort,
|
||||
regexp.MustCompile(`^127\.0\.0\.1:\d+ - GET / HTTP/1\.1 200 - 0\.1\d\d s\n$`),
|
||||
},
|
||||
testCase{"default",
|
||||
Ldefault,
|
||||
regexp.MustCompile(`^127\.0\.0\.1:\d+ - - \[\d{4}-\d{2}-\d{2}\] "GET / HTTP/1\.1" 200 "http://www\.test\.com" "Go \d+\.\d+ package http"\n$`),
|
||||
},
|
||||
testCase{"res[Content-Type]",
|
||||
"%s",
|
||||
regexp.MustCompile(`^text/plain\n$`),
|
||||
},
|
||||
}
|
||||
for _, tc := range formats {
|
||||
testLogCase(tc, t)
|
||||
}
|
||||
}
|
||||
|
||||
func testLogCase(tc testCase, t *testing.T) {
|
||||
buf := bytes.NewBuffer(nil)
|
||||
log.SetOutput(buf)
|
||||
opts := NewLogOptions(log.Printf, tc.fmt, tc.tok)
|
||||
opts.DateFormat = "2006-01-02"
|
||||
h := LogHandler(http.HandlerFunc(
|
||||
func(w http.ResponseWriter, r *http.Request) {
|
||||
time.Sleep(100 * time.Millisecond)
|
||||
w.Header().Set("Content-Type", "text/plain")
|
||||
w.WriteHeader(200)
|
||||
w.Write([]byte("body"))
|
||||
}), opts)
|
||||
|
||||
s := httptest.NewServer(h)
|
||||
defer s.Close()
|
||||
t.Logf("running %s...", tc.tok)
|
||||
req, err := http.NewRequest("GET", s.URL, nil)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
req.Header.Set("Referer", "http://www.test.com")
|
||||
req.Header.Set("Accept-Encoding", "gzip")
|
||||
res, err := http.DefaultClient.Do(req)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
assertStatus(http.StatusOK, res.StatusCode, t)
|
||||
ac := buf.String()
|
||||
assertTrue(tc.rx.MatchString(ac), fmt.Sprintf("expected log to match '%s', got '%s'", tc.rx.String(), ac), t)
|
||||
}
|
||||
|
||||
func TestForwardedFor(t *testing.T) {
|
||||
rx := regexp.MustCompile(`^1\.1\.1\.1:0 - - \[\d{4}-\d{2}-\d{2}\] "GET / HTTP/1\.1" 200 "http://www\.test\.com" "Go \d+\.\d+ package http"\n$`)
|
||||
|
||||
buf := bytes.NewBuffer(nil)
|
||||
log.SetOutput(buf)
|
||||
opts := NewLogOptions(log.Printf, Ldefault)
|
||||
opts.DateFormat = "2006-01-02"
|
||||
|
||||
h := LogHandler(http.HandlerFunc(
|
||||
func(w http.ResponseWriter, r *http.Request) {
|
||||
time.Sleep(100 * time.Millisecond)
|
||||
w.Header().Set("Content-Type", "text/plain")
|
||||
w.WriteHeader(200)
|
||||
w.Write([]byte("body"))
|
||||
}), opts)
|
||||
|
||||
s := httptest.NewServer(h)
|
||||
defer s.Close()
|
||||
t.Logf("running ForwardedFor...")
|
||||
req, err := http.NewRequest("GET", s.URL, nil)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
req.Header.Set("Referer", "http://www.test.com")
|
||||
req.Header.Set("X-Forwarded-For", "1.1.1.1")
|
||||
req.Header.Set("Accept-Encoding", "gzip")
|
||||
res, err := http.DefaultClient.Do(req)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
assertStatus(http.StatusOK, res.StatusCode, t)
|
||||
ac := buf.String()
|
||||
assertTrue(rx.MatchString(ac), fmt.Sprintf("expected log to match '%s', got '%s'", rx.String(), ac), t)
|
||||
}
|
||||
|
||||
func TestImmediate(t *testing.T) {
|
||||
buf := bytes.NewBuffer(nil)
|
||||
log.SetFlags(0)
|
||||
log.SetOutput(buf)
|
||||
opts := NewLogOptions(nil, Ltiny)
|
||||
opts.Immediate = true
|
||||
h := LogHandler(http.HandlerFunc(
|
||||
func(w http.ResponseWriter, r *http.Request) {
|
||||
time.Sleep(100 * time.Millisecond)
|
||||
w.WriteHeader(200)
|
||||
w.Write([]byte("body"))
|
||||
}), opts)
|
||||
s := httptest.NewServer(h)
|
||||
defer s.Close()
|
||||
|
||||
res, err := http.Get(s.URL)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
assertStatus(http.StatusOK, res.StatusCode, t)
|
||||
ac := buf.String()
|
||||
// Since it is Immediate logging, status is still 0 and response time is less than 100ms
|
||||
rx := regexp.MustCompile(`GET / 0 - 0\.0\d\d s\n`)
|
||||
assertTrue(rx.MatchString(ac), fmt.Sprintf("expected log to match '%s', got '%s'", rx.String(), ac), t)
|
||||
}
|
||||
|
||||
func TestCustom(t *testing.T) {
|
||||
buf := bytes.NewBuffer(nil)
|
||||
log.SetFlags(0)
|
||||
log.SetOutput(buf)
|
||||
opts := NewLogOptions(nil, "%s %s", "method", "custom")
|
||||
opts.CustomTokens["custom"] = func(w http.ResponseWriter, r *http.Request) string {
|
||||
return "toto"
|
||||
}
|
||||
|
||||
h := LogHandler(http.HandlerFunc(
|
||||
func(w http.ResponseWriter, r *http.Request) {
|
||||
time.Sleep(100 * time.Millisecond)
|
||||
w.WriteHeader(200)
|
||||
w.Write([]byte("body"))
|
||||
}), opts)
|
||||
s := httptest.NewServer(h)
|
||||
defer s.Close()
|
||||
|
||||
res, err := http.Get(s.URL)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
assertStatus(http.StatusOK, res.StatusCode, t)
|
||||
ac := buf.String()
|
||||
rx := regexp.MustCompile(`GET toto`)
|
||||
assertTrue(rx.MatchString(ac), fmt.Sprintf("expected log to match '%s', got '%s'", rx.String(), ac), t)
|
||||
}
|
57
vendor/github.com/PuerkitoBio/ghost/handlers/panic.go
generated
vendored
Normal file
57
vendor/github.com/PuerkitoBio/ghost/handlers/panic.go
generated
vendored
Normal file
@@ -0,0 +1,57 @@
|
||||
package handlers
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
// Augmented response writer to hold the panic data (can be anything, not necessarily an error
|
||||
// interface).
|
||||
type errResponseWriter struct {
|
||||
http.ResponseWriter
|
||||
perr interface{}
|
||||
}
|
||||
|
||||
// Implement the WrapWriter interface.
|
||||
func (this *errResponseWriter) WrappedWriter() http.ResponseWriter {
|
||||
return this.ResponseWriter
|
||||
}
|
||||
|
||||
// PanicHandlerFunc is the same as PanicHandler, it is just a convenience
|
||||
// signature that accepts a func(http.ResponseWriter, *http.Request) instead of
|
||||
// a http.Handler interface. It saves the boilerplate http.HandlerFunc() cast.
|
||||
func PanicHandlerFunc(h http.HandlerFunc, errH http.HandlerFunc) http.HandlerFunc {
|
||||
return PanicHandler(h, errH)
|
||||
}
|
||||
|
||||
// Calls the wrapped handler and on panic calls the specified error handler. If the error handler is nil,
|
||||
// responds with a 500 error message.
|
||||
func PanicHandler(h http.Handler, errH http.Handler) http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
defer func() {
|
||||
if err := recover(); err != nil {
|
||||
if errH != nil {
|
||||
ew := &errResponseWriter{w, err}
|
||||
errH.ServeHTTP(ew, r)
|
||||
} else {
|
||||
http.Error(w, fmt.Sprintf("%s", err), http.StatusInternalServerError)
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
// Call the protected handler
|
||||
h.ServeHTTP(w, r)
|
||||
}
|
||||
}
|
||||
|
||||
// Helper function to retrieve the panic error, if any.
|
||||
func GetPanicError(w http.ResponseWriter) (interface{}, bool) {
|
||||
er, ok := GetResponseWriter(w, func(tst http.ResponseWriter) bool {
|
||||
_, ok := tst.(*errResponseWriter)
|
||||
return ok
|
||||
})
|
||||
if ok {
|
||||
return er.(*errResponseWriter).perr, true
|
||||
}
|
||||
return nil, false
|
||||
}
|
62
vendor/github.com/PuerkitoBio/ghost/handlers/panic_test.go
generated
vendored
Normal file
62
vendor/github.com/PuerkitoBio/ghost/handlers/panic_test.go
generated
vendored
Normal file
@@ -0,0 +1,62 @@
|
||||
package handlers
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestPanic(t *testing.T) {
|
||||
h := PanicHandler(http.HandlerFunc(
|
||||
func(w http.ResponseWriter, r *http.Request) {
|
||||
panic("test")
|
||||
}), nil)
|
||||
s := httptest.NewServer(h)
|
||||
defer s.Close()
|
||||
|
||||
res, err := http.Get(s.URL)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
assertStatus(http.StatusInternalServerError, res.StatusCode, t)
|
||||
}
|
||||
|
||||
func TestNoPanic(t *testing.T) {
|
||||
h := PanicHandler(http.HandlerFunc(
|
||||
func(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
}), nil)
|
||||
s := httptest.NewServer(h)
|
||||
defer s.Close()
|
||||
|
||||
res, err := http.Get(s.URL)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
assertStatus(http.StatusOK, res.StatusCode, t)
|
||||
}
|
||||
|
||||
func TestPanicCustom(t *testing.T) {
|
||||
h := PanicHandler(http.HandlerFunc(
|
||||
func(w http.ResponseWriter, r *http.Request) {
|
||||
panic("ok")
|
||||
}),
|
||||
http.HandlerFunc(
|
||||
func(w http.ResponseWriter, r *http.Request) {
|
||||
err, ok := GetPanicError(w)
|
||||
if !ok {
|
||||
panic("no panic error found")
|
||||
}
|
||||
w.WriteHeader(501)
|
||||
w.Write([]byte(err.(string)))
|
||||
}))
|
||||
s := httptest.NewServer(h)
|
||||
defer s.Close()
|
||||
|
||||
res, err := http.Get(s.URL)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
assertStatus(501, res.StatusCode, t)
|
||||
assertBody([]byte("ok"), res, t)
|
||||
}
|
135
vendor/github.com/PuerkitoBio/ghost/handlers/redisstore.go
generated
vendored
Normal file
135
vendor/github.com/PuerkitoBio/ghost/handlers/redisstore.go
generated
vendored
Normal file
@@ -0,0 +1,135 @@
|
||||
package handlers
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"time"
|
||||
|
||||
"github.com/garyburd/redigo/redis"
|
||||
)
|
||||
|
||||
var (
|
||||
ErrNoKeyPrefix = errors.New("cannot get session keys without a key prefix")
|
||||
)
|
||||
|
||||
type RedisStoreOptions struct {
|
||||
Network string
|
||||
Address string
|
||||
ConnectTimeout time.Duration
|
||||
ReadTimeout time.Duration
|
||||
WriteTimeout time.Duration
|
||||
Database int // Redis database to use for session keys
|
||||
KeyPrefix string // If set, keys will be KeyPrefix:SessionID (semicolon added)
|
||||
BrowserSessServerTTL time.Duration // Defaults to 2 days
|
||||
}
|
||||
|
||||
type RedisStore struct {
|
||||
opts *RedisStoreOptions
|
||||
conn redis.Conn
|
||||
}
|
||||
|
||||
// Create a redis session store with the specified options.
|
||||
func NewRedisStore(opts *RedisStoreOptions) *RedisStore {
|
||||
var err error
|
||||
rs := &RedisStore{opts, nil}
|
||||
rs.conn, err = redis.DialTimeout(opts.Network, opts.Address, opts.ConnectTimeout,
|
||||
opts.ReadTimeout, opts.WriteTimeout)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return rs
|
||||
}
|
||||
|
||||
// Get the session from the store.
|
||||
func (this *RedisStore) Get(id string) (*Session, error) {
|
||||
key := id
|
||||
if this.opts.KeyPrefix != "" {
|
||||
key = this.opts.KeyPrefix + ":" + id
|
||||
}
|
||||
b, err := redis.Bytes(this.conn.Do("GET", key))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var sess Session
|
||||
err = json.Unmarshal(b, &sess)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &sess, nil
|
||||
}
|
||||
|
||||
// Save the session into the store.
|
||||
func (this *RedisStore) Set(sess *Session) error {
|
||||
b, err := json.Marshal(sess)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
key := sess.ID()
|
||||
if this.opts.KeyPrefix != "" {
|
||||
key = this.opts.KeyPrefix + ":" + sess.ID()
|
||||
}
|
||||
ttl := sess.MaxAge()
|
||||
if ttl == 0 {
|
||||
// Browser session, set to specified TTL
|
||||
ttl = this.opts.BrowserSessServerTTL
|
||||
if ttl == 0 {
|
||||
ttl = 2 * 24 * time.Hour // Default to 2 days
|
||||
}
|
||||
}
|
||||
_, err = this.conn.Do("SETEX", key, int(ttl.Seconds()), b)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Delete the session from the store.
|
||||
func (this *RedisStore) Delete(id string) error {
|
||||
key := id
|
||||
if this.opts.KeyPrefix != "" {
|
||||
key = this.opts.KeyPrefix + ":" + id
|
||||
}
|
||||
_, err := this.conn.Do("DEL", key)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Clear all sessions from the store. Requires the use of a key
|
||||
// prefix in the store options, otherwise the method refuses to delete all keys.
|
||||
func (this *RedisStore) Clear() error {
|
||||
vals, err := this.getSessionKeys()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if len(vals) > 0 {
|
||||
this.conn.Send("MULTI")
|
||||
for _, v := range vals {
|
||||
this.conn.Send("DEL", v)
|
||||
}
|
||||
_, err = this.conn.Do("EXEC")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Get the number of session keys in the store. Requires the use of a
|
||||
// key prefix in the store options, otherwise returns -1 (cannot tell
|
||||
// session keys from other keys).
|
||||
func (this *RedisStore) Len() int {
|
||||
vals, err := this.getSessionKeys()
|
||||
if err != nil {
|
||||
return -1
|
||||
}
|
||||
return len(vals)
|
||||
}
|
||||
|
||||
func (this *RedisStore) getSessionKeys() ([]interface{}, error) {
|
||||
if this.opts.KeyPrefix != "" {
|
||||
return redis.Values(this.conn.Do("KEYS", this.opts.KeyPrefix+":*"))
|
||||
}
|
||||
return nil, ErrNoKeyPrefix
|
||||
}
|
30
vendor/github.com/PuerkitoBio/ghost/handlers/reswriter.go
generated
vendored
Normal file
30
vendor/github.com/PuerkitoBio/ghost/handlers/reswriter.go
generated
vendored
Normal file
@@ -0,0 +1,30 @@
|
||||
package handlers
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
)
|
||||
|
||||
// This interface can be implemented by an augmented ResponseWriter, so that
|
||||
// it doesn't hide other augmented writers in the chain.
|
||||
type WrapWriter interface {
|
||||
http.ResponseWriter
|
||||
WrappedWriter() http.ResponseWriter
|
||||
}
|
||||
|
||||
// Helper function to retrieve a specific ResponseWriter.
|
||||
func GetResponseWriter(w http.ResponseWriter,
|
||||
predicate func(http.ResponseWriter) bool) (http.ResponseWriter, bool) {
|
||||
|
||||
for {
|
||||
// Check if this writer is the one we're looking for
|
||||
if w != nil && predicate(w) {
|
||||
return w, true
|
||||
}
|
||||
// If it is a WrapWriter, move back the chain of wrapped writers
|
||||
ww, ok := w.(WrapWriter)
|
||||
if !ok {
|
||||
return nil, false
|
||||
}
|
||||
w = ww.WrappedWriter()
|
||||
}
|
||||
}
|
52
vendor/github.com/PuerkitoBio/ghost/handlers/reswriter_test.go
generated
vendored
Normal file
52
vendor/github.com/PuerkitoBio/ghost/handlers/reswriter_test.go
generated
vendored
Normal file
@@ -0,0 +1,52 @@
|
||||
package handlers
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
"testing"
|
||||
)
|
||||
|
||||
type baseWriter struct{}
|
||||
|
||||
func (b *baseWriter) Write(data []byte) (int, error) { return 0, nil }
|
||||
func (b *baseWriter) WriteHeader(code int) {}
|
||||
func (b *baseWriter) Header() http.Header { return nil }
|
||||
|
||||
func TestNilWriter(t *testing.T) {
|
||||
rw, ok := GetResponseWriter(nil, func(w http.ResponseWriter) bool {
|
||||
return true
|
||||
})
|
||||
assertTrue(rw == nil, "expected nil, got non-nil", t)
|
||||
assertTrue(!ok, "expected false, got true", t)
|
||||
}
|
||||
|
||||
func TestBaseWriter(t *testing.T) {
|
||||
bw := &baseWriter{}
|
||||
rw, ok := GetResponseWriter(bw, func(w http.ResponseWriter) bool {
|
||||
return true
|
||||
})
|
||||
assertTrue(rw == bw, fmt.Sprintf("expected %#v, got %#v", bw, rw), t)
|
||||
assertTrue(ok, "expected true, got false", t)
|
||||
}
|
||||
|
||||
func TestWrappedWriter(t *testing.T) {
|
||||
bw := &baseWriter{}
|
||||
ctx := &contextResponseWriter{bw, nil}
|
||||
rw, ok := GetResponseWriter(ctx, func(w http.ResponseWriter) bool {
|
||||
_, ok := w.(*baseWriter)
|
||||
return ok
|
||||
})
|
||||
assertTrue(rw == bw, fmt.Sprintf("expected %#v, got %#v", bw, rw), t)
|
||||
assertTrue(ok, "expected true, got false", t)
|
||||
}
|
||||
|
||||
func TestWrappedNotFoundWriter(t *testing.T) {
|
||||
bw := &baseWriter{}
|
||||
ctx := &contextResponseWriter{bw, nil}
|
||||
rw, ok := GetResponseWriter(ctx, func(w http.ResponseWriter) bool {
|
||||
_, ok := w.(*statusResponseWriter)
|
||||
return ok
|
||||
})
|
||||
assertTrue(rw == nil, fmt.Sprintf("expected nil, got %#v", rw), t)
|
||||
assertTrue(!ok, "expected false, got true", t)
|
||||
}
|
321
vendor/github.com/PuerkitoBio/ghost/handlers/session.go
generated
vendored
Normal file
321
vendor/github.com/PuerkitoBio/ghost/handlers/session.go
generated
vendored
Normal file
@@ -0,0 +1,321 @@
|
||||
package handlers
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"hash/crc32"
|
||||
"net/http"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/PuerkitoBio/ghost"
|
||||
"github.com/gorilla/securecookie"
|
||||
"github.com/nu7hatch/gouuid"
|
||||
)
|
||||
|
||||
const defaultCookieName = "ghost.sid"
|
||||
|
||||
var (
|
||||
ErrSessionSecretMissing = errors.New("session secret is missing")
|
||||
ErrNoSessionID = errors.New("session ID could not be generated")
|
||||
)
|
||||
|
||||
// The Session holds the data map that persists for the duration of the session.
|
||||
// The information stored in this map should be marshalable for the target Session store
|
||||
// format (i.e. json, sql, gob, etc. depending on how the store persists the data).
|
||||
type Session struct {
|
||||
isNew bool // keep private, not saved to JSON, will be false once read from the store
|
||||
internalSession
|
||||
}
|
||||
|
||||
// Use a separate private struct to hold the private fields of the Session,
|
||||
// although those fields are exposed (public). This is a trick to simplify
|
||||
// JSON encoding.
|
||||
type internalSession struct {
|
||||
Data map[string]interface{} // JSON cannot marshal a map[interface{}]interface{}
|
||||
ID string
|
||||
Created time.Time
|
||||
MaxAge time.Duration
|
||||
}
|
||||
|
||||
// Create a new Session instance. It panics in the unlikely event that a new random ID cannot be generated.
|
||||
func newSession(maxAge int) *Session {
|
||||
uid, err := uuid.NewV4()
|
||||
if err != nil {
|
||||
panic(ErrNoSessionID)
|
||||
}
|
||||
return &Session{
|
||||
true, // is new
|
||||
internalSession{
|
||||
make(map[string]interface{}),
|
||||
uid.String(),
|
||||
time.Now(),
|
||||
time.Duration(maxAge) * time.Second,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// Gets the ID of the session.
|
||||
func (ø *Session) ID() string {
|
||||
return ø.internalSession.ID
|
||||
}
|
||||
|
||||
// Get the max age duration
|
||||
func (ø *Session) MaxAge() time.Duration {
|
||||
return ø.internalSession.MaxAge
|
||||
}
|
||||
|
||||
// Get the creation time of the session.
|
||||
func (ø *Session) Created() time.Time {
|
||||
return ø.internalSession.Created
|
||||
}
|
||||
|
||||
// Is this a new Session (created by the current request)
|
||||
func (ø *Session) IsNew() bool {
|
||||
return ø.isNew
|
||||
}
|
||||
|
||||
// TODO : Resets the max age property of the session to its original value (sliding expiration).
|
||||
func (ø *Session) resetMaxAge() {
|
||||
}
|
||||
|
||||
// Marshal the session to JSON.
|
||||
func (ø *Session) MarshalJSON() ([]byte, error) {
|
||||
return json.Marshal(ø.internalSession)
|
||||
}
|
||||
|
||||
// Unmarshal the JSON into the internal session struct.
|
||||
func (ø *Session) UnmarshalJSON(b []byte) error {
|
||||
return json.Unmarshal(b, &ø.internalSession)
|
||||
}
|
||||
|
||||
// Options object for the session handler. It specified the Session store to use for
|
||||
// persistence, the template for the session cookie (name, path, maxage, etc.),
|
||||
// whether or not the proxy should be trusted to determine if the connection is secure,
|
||||
// and the required secret to sign the session cookie.
|
||||
type SessionOptions struct {
|
||||
Store SessionStore
|
||||
CookieTemplate http.Cookie
|
||||
TrustProxy bool
|
||||
Secret string
|
||||
}
|
||||
|
||||
// Create a new SessionOptions struct, using default cookie and proxy values.
|
||||
func NewSessionOptions(store SessionStore, secret string) *SessionOptions {
|
||||
return &SessionOptions{
|
||||
Store: store,
|
||||
Secret: secret,
|
||||
}
|
||||
}
|
||||
|
||||
// The augmented ResponseWriter struct for the session handler. It holds the current
|
||||
// Session object and Session store, as well as flags and function to send the actual
|
||||
// session cookie at the end of the request.
|
||||
type sessResponseWriter struct {
|
||||
http.ResponseWriter
|
||||
sess *Session
|
||||
sessStore SessionStore
|
||||
sessSent bool
|
||||
sendCookieFn func()
|
||||
}
|
||||
|
||||
// Implement the WrapWriter interface.
|
||||
func (ø *sessResponseWriter) WrappedWriter() http.ResponseWriter {
|
||||
return ø.ResponseWriter
|
||||
}
|
||||
|
||||
// Intercept the Write() method to add the Set-Cookie header before it's too late.
|
||||
func (ø *sessResponseWriter) Write(data []byte) (int, error) {
|
||||
if !ø.sessSent {
|
||||
ø.sendCookieFn()
|
||||
ø.sessSent = true
|
||||
}
|
||||
return ø.ResponseWriter.Write(data)
|
||||
}
|
||||
|
||||
// Intercept the WriteHeader() method to add the Set-Cookie header before it's too late.
|
||||
func (ø *sessResponseWriter) WriteHeader(code int) {
|
||||
if !ø.sessSent {
|
||||
ø.sendCookieFn()
|
||||
ø.sessSent = true
|
||||
}
|
||||
ø.ResponseWriter.WriteHeader(code)
|
||||
}
|
||||
|
||||
// SessionHandlerFunc is the same as SessionHandler, it is just a convenience
|
||||
// signature that accepts a func(http.ResponseWriter, *http.Request) instead of
|
||||
// a http.Handler interface. It saves the boilerplate http.HandlerFunc() cast.
|
||||
func SessionHandlerFunc(h http.HandlerFunc, opts *SessionOptions) http.HandlerFunc {
|
||||
return SessionHandler(h, opts)
|
||||
}
|
||||
|
||||
// Create a Session handler to offer the Session behaviour to the specified handler.
|
||||
func SessionHandler(h http.Handler, opts *SessionOptions) http.HandlerFunc {
|
||||
// Make sure the required cookie fields are set
|
||||
if opts.CookieTemplate.Name == "" {
|
||||
opts.CookieTemplate.Name = defaultCookieName
|
||||
}
|
||||
if opts.CookieTemplate.Path == "" {
|
||||
opts.CookieTemplate.Path = "/"
|
||||
}
|
||||
// Secret is required
|
||||
if opts.Secret == "" {
|
||||
panic(ErrSessionSecretMissing)
|
||||
}
|
||||
|
||||
// Return the actual handler
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
if _, ok := getSessionWriter(w); ok {
|
||||
// Self-awareness
|
||||
h.ServeHTTP(w, r)
|
||||
return
|
||||
}
|
||||
|
||||
if strings.Index(r.URL.Path, opts.CookieTemplate.Path) != 0 {
|
||||
// Session does not apply to this path
|
||||
h.ServeHTTP(w, r)
|
||||
return
|
||||
}
|
||||
|
||||
// Create a new Session or retrieve the existing session based on the
|
||||
// session cookie received.
|
||||
var sess *Session
|
||||
var ckSessId string
|
||||
exCk, err := r.Cookie(opts.CookieTemplate.Name)
|
||||
if err != nil {
|
||||
sess = newSession(opts.CookieTemplate.MaxAge)
|
||||
ghost.LogFn("ghost.session : error getting session cookie : %s", err)
|
||||
} else {
|
||||
ckSessId, err = parseSignedCookie(exCk, opts.Secret)
|
||||
if err != nil {
|
||||
sess = newSession(opts.CookieTemplate.MaxAge)
|
||||
ghost.LogFn("ghost.session : error parsing signed cookie : %s", err)
|
||||
} else if ckSessId == "" {
|
||||
sess = newSession(opts.CookieTemplate.MaxAge)
|
||||
ghost.LogFn("ghost.session : no existing session ID")
|
||||
} else {
|
||||
// Get the session
|
||||
sess, err = opts.Store.Get(ckSessId)
|
||||
if err != nil {
|
||||
sess = newSession(opts.CookieTemplate.MaxAge)
|
||||
ghost.LogFn("ghost.session : error getting session from store : %s", err)
|
||||
} else if sess == nil {
|
||||
sess = newSession(opts.CookieTemplate.MaxAge)
|
||||
ghost.LogFn("ghost.session : nil session")
|
||||
}
|
||||
}
|
||||
}
|
||||
// Save the original hash of the session, used to compare if the contents
|
||||
// have changed during the handling of the request, so that it has to be
|
||||
// saved to the stored.
|
||||
oriHash := hash(sess)
|
||||
|
||||
// Create the augmented ResponseWriter.
|
||||
srw := &sessResponseWriter{w, sess, opts.Store, false, func() {
|
||||
// This function is called when the header is about to be written, so that
|
||||
// the session cookie is correctly set.
|
||||
|
||||
// Check if the connection is secure
|
||||
proto := strings.Trim(strings.ToLower(r.Header.Get("X-Forwarded-Proto")), " ")
|
||||
tls := r.TLS != nil || (strings.HasPrefix(proto, "https") && opts.TrustProxy)
|
||||
if opts.CookieTemplate.Secure && !tls {
|
||||
ghost.LogFn("ghost.session : secure cookie on a non-secure connection, cookie not sent")
|
||||
return
|
||||
}
|
||||
if !sess.IsNew() {
|
||||
// If this is not a new session, no need to send back the cookie
|
||||
// TODO : Handle expires?
|
||||
return
|
||||
}
|
||||
|
||||
// Send the session cookie
|
||||
ck := opts.CookieTemplate
|
||||
ck.Value = sess.ID()
|
||||
err := signCookie(&ck, opts.Secret)
|
||||
if err != nil {
|
||||
ghost.LogFn("ghost.session : error signing cookie : %s", err)
|
||||
return
|
||||
}
|
||||
http.SetCookie(w, &ck)
|
||||
}}
|
||||
|
||||
// Call wrapped handler
|
||||
h.ServeHTTP(srw, r)
|
||||
|
||||
// TODO : Expiration management? srw.sess.resetMaxAge()
|
||||
// Do not save if content is the same, unless session is new (to avoid
|
||||
// creating a new session and sending a cookie on each successive request).
|
||||
if newHash := hash(sess); !sess.IsNew() && oriHash == newHash && newHash != 0 {
|
||||
// No changes to the session, no need to save
|
||||
ghost.LogFn("ghost.session : no changes to save to store")
|
||||
return
|
||||
}
|
||||
err = opts.Store.Set(sess)
|
||||
if err != nil {
|
||||
ghost.LogFn("ghost.session : error saving session to store : %s", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Helper function to retrieve the session for the current request.
|
||||
func GetSession(w http.ResponseWriter) (*Session, bool) {
|
||||
ss, ok := getSessionWriter(w)
|
||||
if ok {
|
||||
return ss.sess, true
|
||||
}
|
||||
return nil, false
|
||||
}
|
||||
|
||||
// Helper function to retrieve the session store
|
||||
func GetSessionStore(w http.ResponseWriter) (SessionStore, bool) {
|
||||
ss, ok := getSessionWriter(w)
|
||||
if ok {
|
||||
return ss.sessStore, true
|
||||
}
|
||||
return nil, false
|
||||
}
|
||||
|
||||
// Internal helper function to retrieve the session writer object.
|
||||
func getSessionWriter(w http.ResponseWriter) (*sessResponseWriter, bool) {
|
||||
ss, ok := GetResponseWriter(w, func(tst http.ResponseWriter) bool {
|
||||
_, ok := tst.(*sessResponseWriter)
|
||||
return ok
|
||||
})
|
||||
if ok {
|
||||
return ss.(*sessResponseWriter), true
|
||||
}
|
||||
return nil, false
|
||||
}
|
||||
|
||||
// Parse a signed cookie and return the cookie value
|
||||
func parseSignedCookie(ck *http.Cookie, secret string) (string, error) {
|
||||
var val string
|
||||
|
||||
sck := securecookie.New([]byte(secret), nil)
|
||||
err := sck.Decode(ck.Name, ck.Value, &val)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return val, nil
|
||||
}
|
||||
|
||||
// Sign the specified cookie's value
|
||||
func signCookie(ck *http.Cookie, secret string) error {
|
||||
sck := securecookie.New([]byte(secret), nil)
|
||||
enc, err := sck.Encode(ck.Name, ck.Value)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
ck.Value = enc
|
||||
return nil
|
||||
}
|
||||
|
||||
// Compute a CRC32 hash of the session's JSON-encoded contents.
|
||||
func hash(s *Session) uint32 {
|
||||
data, err := json.Marshal(s)
|
||||
if err != nil {
|
||||
ghost.LogFn("ghost.session : error hash : %s", err)
|
||||
return 0 // 0 is always treated as "modified" session content
|
||||
}
|
||||
return crc32.ChecksumIEEE(data)
|
||||
}
|
258
vendor/github.com/PuerkitoBio/ghost/handlers/session_test.go
generated
vendored
Normal file
258
vendor/github.com/PuerkitoBio/ghost/handlers/session_test.go
generated
vendored
Normal file
@@ -0,0 +1,258 @@
|
||||
package handlers
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"net/http/cookiejar"
|
||||
"net/http/httptest"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
var (
|
||||
store SessionStore
|
||||
secret = "butchered at birth"
|
||||
)
|
||||
|
||||
func TestSession(t *testing.T) {
|
||||
stores := map[string]SessionStore{
|
||||
"memory": NewMemoryStore(1),
|
||||
"redis": NewRedisStore(&RedisStoreOptions{
|
||||
Network: "tcp",
|
||||
Address: ":6379",
|
||||
Database: 1,
|
||||
KeyPrefix: "sess",
|
||||
}),
|
||||
}
|
||||
for k, v := range stores {
|
||||
t.Logf("testing session with %s store\n", k)
|
||||
store = v
|
||||
t.Log("SessionExists")
|
||||
testSessionExists(t)
|
||||
t.Log("SessionPersists")
|
||||
testSessionPersists(t)
|
||||
t.Log("SessionExpires")
|
||||
testSessionExpires(t)
|
||||
t.Log("SessionBeforeExpires")
|
||||
testSessionBeforeExpires(t)
|
||||
t.Log("PanicIfNoSecret")
|
||||
testPanicIfNoSecret(t)
|
||||
t.Log("InvalidPath")
|
||||
testInvalidPath(t)
|
||||
t.Log("ValidSubPath")
|
||||
testValidSubPath(t)
|
||||
t.Log("SecureOverHttp")
|
||||
testSecureOverHttp(t)
|
||||
}
|
||||
}
|
||||
|
||||
func setupTest(f func(w http.ResponseWriter, r *http.Request), ckPath string, secure bool, maxAge int) *httptest.Server {
|
||||
opts := NewSessionOptions(store, secret)
|
||||
if ckPath != "" {
|
||||
opts.CookieTemplate.Path = ckPath
|
||||
}
|
||||
opts.CookieTemplate.Secure = secure
|
||||
opts.CookieTemplate.MaxAge = maxAge
|
||||
h := SessionHandler(http.HandlerFunc(f), opts)
|
||||
return httptest.NewServer(h)
|
||||
}
|
||||
|
||||
func doRequest(u string, newJar bool) *http.Response {
|
||||
var err error
|
||||
if newJar {
|
||||
http.DefaultClient.Jar, err = cookiejar.New(new(cookiejar.Options))
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
res, err := http.Get(u)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return res
|
||||
}
|
||||
|
||||
func testSessionExists(t *testing.T) {
|
||||
s := setupTest(func(w http.ResponseWriter, r *http.Request) {
|
||||
ssn, ok := GetSession(w)
|
||||
if assertTrue(ok, "expected session to be non-nil, got nil", t) {
|
||||
ssn.Data["foo"] = "bar"
|
||||
assertTrue(ssn.Data["foo"] == "bar", fmt.Sprintf("expected ssn[foo] to be 'bar', got %v", ssn.Data["foo"]), t)
|
||||
}
|
||||
w.Write([]byte("ok"))
|
||||
}, "", false, 0)
|
||||
defer s.Close()
|
||||
|
||||
res := doRequest(s.URL, true)
|
||||
assertStatus(http.StatusOK, res.StatusCode, t)
|
||||
assertBody([]byte("ok"), res, t)
|
||||
assertTrue(len(res.Cookies()) == 1, fmt.Sprintf("expected response to have 1 cookie, got %d", len(res.Cookies())), t)
|
||||
}
|
||||
|
||||
func testSessionPersists(t *testing.T) {
|
||||
cnt := 0
|
||||
s := setupTest(func(w http.ResponseWriter, r *http.Request) {
|
||||
ssn, ok := GetSession(w)
|
||||
if !ok {
|
||||
panic("session not found!")
|
||||
}
|
||||
if cnt == 0 {
|
||||
ssn.Data["foo"] = "bar"
|
||||
w.Write([]byte("ok"))
|
||||
cnt++
|
||||
} else {
|
||||
w.Write([]byte(ssn.Data["foo"].(string)))
|
||||
}
|
||||
}, "", false, 0)
|
||||
defer s.Close()
|
||||
|
||||
// 1st call, set the session value
|
||||
res := doRequest(s.URL, true)
|
||||
assertStatus(http.StatusOK, res.StatusCode, t)
|
||||
assertBody([]byte("ok"), res, t)
|
||||
|
||||
// 2nd call, get the session value
|
||||
res = doRequest(s.URL, false)
|
||||
assertStatus(http.StatusOK, res.StatusCode, t)
|
||||
assertBody([]byte("bar"), res, t)
|
||||
assertTrue(len(res.Cookies()) == 0, fmt.Sprintf("expected 2nd response to have 0 cookie, got %d", len(res.Cookies())), t)
|
||||
}
|
||||
|
||||
func testSessionExpires(t *testing.T) {
|
||||
cnt := 0
|
||||
s := setupTest(func(w http.ResponseWriter, r *http.Request) {
|
||||
ssn, ok := GetSession(w)
|
||||
if !ok {
|
||||
panic("session not found!")
|
||||
}
|
||||
if cnt == 0 {
|
||||
w.Write([]byte(ssn.ID()))
|
||||
cnt++
|
||||
} else {
|
||||
w.Write([]byte(ssn.ID()))
|
||||
}
|
||||
}, "", false, 1) // Expire in 1 second
|
||||
defer s.Close()
|
||||
|
||||
// 1st call, set the session value
|
||||
res := doRequest(s.URL, true)
|
||||
assertStatus(http.StatusOK, res.StatusCode, t)
|
||||
id1, err := ioutil.ReadAll(res.Body)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
res.Body.Close()
|
||||
time.Sleep(1001 * time.Millisecond)
|
||||
|
||||
// 2nd call, get the session value
|
||||
res = doRequest(s.URL, false)
|
||||
assertStatus(http.StatusOK, res.StatusCode, t)
|
||||
id2, err := ioutil.ReadAll(res.Body)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
res.Body.Close()
|
||||
sid1, sid2 := string(id1), string(id2)
|
||||
assertTrue(len(res.Cookies()) == 1, fmt.Sprintf("expected 2nd response to have 1 cookie, got %d", len(res.Cookies())), t)
|
||||
assertTrue(sid1 != sid2, "expected session IDs to be different, got same", t)
|
||||
}
|
||||
|
||||
func testSessionBeforeExpires(t *testing.T) {
|
||||
s := setupTest(func(w http.ResponseWriter, r *http.Request) {
|
||||
ssn, ok := GetSession(w)
|
||||
if !ok {
|
||||
panic("session not found!")
|
||||
}
|
||||
w.Write([]byte(ssn.ID()))
|
||||
}, "", false, 1) // Expire in 1 second
|
||||
defer s.Close()
|
||||
|
||||
// 1st call, set the session value
|
||||
res := doRequest(s.URL, true)
|
||||
assertStatus(http.StatusOK, res.StatusCode, t)
|
||||
id1, err := ioutil.ReadAll(res.Body)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
res.Body.Close()
|
||||
time.Sleep(500 * time.Millisecond)
|
||||
|
||||
// 2nd call, get the session value
|
||||
res = doRequest(s.URL, false)
|
||||
assertStatus(http.StatusOK, res.StatusCode, t)
|
||||
id2, err := ioutil.ReadAll(res.Body)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
res.Body.Close()
|
||||
sid1, sid2 := string(id1), string(id2)
|
||||
assertTrue(len(res.Cookies()) == 0, fmt.Sprintf("expected 2nd response to have no cookie, got %d", len(res.Cookies())), t)
|
||||
assertTrue(sid1 == sid2, "expected session IDs to be the same, got different", t)
|
||||
}
|
||||
|
||||
func testPanicIfNoSecret(t *testing.T) {
|
||||
defer assertPanic(t)
|
||||
SessionHandler(http.NotFoundHandler(), NewSessionOptions(nil, ""))
|
||||
}
|
||||
|
||||
func testInvalidPath(t *testing.T) {
|
||||
s := setupTest(func(w http.ResponseWriter, r *http.Request) {
|
||||
_, ok := GetSession(w)
|
||||
assertTrue(!ok, "expected session to be nil, got non-nil", t)
|
||||
w.Write([]byte("ok"))
|
||||
}, "/foo", false, 0)
|
||||
defer s.Close()
|
||||
|
||||
res := doRequest(s.URL, true)
|
||||
assertStatus(http.StatusOK, res.StatusCode, t)
|
||||
assertBody([]byte("ok"), res, t)
|
||||
assertTrue(len(res.Cookies()) == 0, fmt.Sprintf("expected response to have no cookie, got %d", len(res.Cookies())), t)
|
||||
}
|
||||
|
||||
func testValidSubPath(t *testing.T) {
|
||||
s := setupTest(func(w http.ResponseWriter, r *http.Request) {
|
||||
_, ok := GetSession(w)
|
||||
assertTrue(ok, "expected session to be non-nil, got nil", t)
|
||||
w.Write([]byte("ok"))
|
||||
}, "/foo", false, 0)
|
||||
defer s.Close()
|
||||
|
||||
res := doRequest(s.URL+"/foo/bar", true)
|
||||
assertStatus(http.StatusOK, res.StatusCode, t)
|
||||
assertBody([]byte("ok"), res, t)
|
||||
assertTrue(len(res.Cookies()) == 1, fmt.Sprintf("expected response to have 1 cookie, got %d", len(res.Cookies())), t)
|
||||
}
|
||||
|
||||
func testSecureOverHttp(t *testing.T) {
|
||||
s := setupTest(func(w http.ResponseWriter, r *http.Request) {
|
||||
_, ok := GetSession(w)
|
||||
assertTrue(ok, "expected session to be non-nil, got nil", t)
|
||||
w.Write([]byte("ok"))
|
||||
}, "", true, 0)
|
||||
defer s.Close()
|
||||
|
||||
res := doRequest(s.URL, true)
|
||||
assertStatus(http.StatusOK, res.StatusCode, t)
|
||||
assertBody([]byte("ok"), res, t)
|
||||
assertTrue(len(res.Cookies()) == 0, fmt.Sprintf("expected response to have no cookie, got %d", len(res.Cookies())), t)
|
||||
}
|
||||
|
||||
// TODO : commented, certificate problem
|
||||
func xtestSecureOverHttps(t *testing.T) {
|
||||
opts := NewSessionOptions(store, secret)
|
||||
opts.CookieTemplate.Secure = true
|
||||
h := SessionHandler(http.HandlerFunc(
|
||||
func(w http.ResponseWriter, r *http.Request) {
|
||||
_, ok := GetSession(w)
|
||||
assertTrue(ok, "expected session to be non-nil, got nil", t)
|
||||
w.Write([]byte("ok"))
|
||||
}), opts)
|
||||
s := httptest.NewTLSServer(h)
|
||||
defer s.Close()
|
||||
|
||||
res := doRequest(s.URL, true)
|
||||
assertStatus(http.StatusOK, res.StatusCode, t)
|
||||
assertBody([]byte("ok"), res, t)
|
||||
assertTrue(len(res.Cookies()) == 1, fmt.Sprintf("expected response to have 1 cookie, got %d", len(res.Cookies())), t)
|
||||
}
|
90
vendor/github.com/PuerkitoBio/ghost/handlers/sstore.go
generated
vendored
Normal file
90
vendor/github.com/PuerkitoBio/ghost/handlers/sstore.go
generated
vendored
Normal file
@@ -0,0 +1,90 @@
|
||||
package handlers
|
||||
|
||||
import (
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
|
||||
// SessionStore interface, must be implemented by any store to be used
|
||||
// for session storage.
|
||||
type SessionStore interface {
|
||||
Get(id string) (*Session, error) // Get the session from the store
|
||||
Set(sess *Session) error // Save the session in the store
|
||||
Delete(id string) error // Delete the session from the store
|
||||
Clear() error // Delete all sessions from the store
|
||||
Len() int // Get the number of sessions in the store
|
||||
}
|
||||
|
||||
// In-memory implementation of a session store. Not recommended for production
|
||||
// use.
|
||||
type MemoryStore struct {
|
||||
l sync.RWMutex
|
||||
m map[string]*Session
|
||||
capc int
|
||||
}
|
||||
|
||||
// Create a new memory store.
|
||||
func NewMemoryStore(capc int) *MemoryStore {
|
||||
m := &MemoryStore{}
|
||||
m.capc = capc
|
||||
m.newMap()
|
||||
return m
|
||||
}
|
||||
|
||||
// Get the number of sessions saved in the store.
|
||||
func (this *MemoryStore) Len() int {
|
||||
return len(this.m)
|
||||
}
|
||||
|
||||
// Get the requested session from the store.
|
||||
func (this *MemoryStore) Get(id string) (*Session, error) {
|
||||
this.l.RLock()
|
||||
defer this.l.RUnlock()
|
||||
return this.m[id], nil
|
||||
}
|
||||
|
||||
// Save the session to the store.
|
||||
func (this *MemoryStore) Set(sess *Session) error {
|
||||
this.l.Lock()
|
||||
defer this.l.Unlock()
|
||||
this.m[sess.ID()] = sess
|
||||
if sess.IsNew() {
|
||||
// Since the memory store doesn't marshal to a string without the isNew, if it is left
|
||||
// to true, it will stay true forever.
|
||||
sess.isNew = false
|
||||
// Expire in the given time. If the maxAge is 0 (which means browser-session lifetime),
|
||||
// expire in a reasonable delay, 2 days. The weird case of a negative maxAge will
|
||||
// cause the immediate Delete call.
|
||||
wait := sess.MaxAge()
|
||||
if wait == 0 {
|
||||
wait = 2 * 24 * time.Hour
|
||||
}
|
||||
go func() {
|
||||
// Clear the session after the specified delay
|
||||
<-time.After(wait)
|
||||
this.Delete(sess.ID())
|
||||
}()
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Delete the specified session ID from the store.
|
||||
func (this *MemoryStore) Delete(id string) error {
|
||||
this.l.Lock()
|
||||
defer this.l.Unlock()
|
||||
delete(this.m, id)
|
||||
return nil
|
||||
}
|
||||
|
||||
// Clear all sessions from the store.
|
||||
func (this *MemoryStore) Clear() error {
|
||||
this.l.Lock()
|
||||
defer this.l.Unlock()
|
||||
this.newMap()
|
||||
return nil
|
||||
}
|
||||
|
||||
// Re-create the internal map, dropping all existing sessions.
|
||||
func (this *MemoryStore) newMap() {
|
||||
this.m = make(map[string]*Session, this.capc)
|
||||
}
|
13
vendor/github.com/PuerkitoBio/ghost/handlers/static.go
generated
vendored
Normal file
13
vendor/github.com/PuerkitoBio/ghost/handlers/static.go
generated
vendored
Normal file
@@ -0,0 +1,13 @@
|
||||
package handlers
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
)
|
||||
|
||||
// StaticFileHandler, unlike net/http.FileServer, serves the contents of a specific
|
||||
// file when it is called.
|
||||
func StaticFileHandler(path string) http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
http.ServeFile(w, r, path)
|
||||
}
|
||||
}
|
46
vendor/github.com/PuerkitoBio/ghost/handlers/static_test.go
generated
vendored
Normal file
46
vendor/github.com/PuerkitoBio/ghost/handlers/static_test.go
generated
vendored
Normal file
@@ -0,0 +1,46 @@
|
||||
package handlers
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestServeFile(t *testing.T) {
|
||||
h := StaticFileHandler("./testdata/styles.css")
|
||||
s := httptest.NewServer(h)
|
||||
defer s.Close()
|
||||
|
||||
res, err := http.Get(s.URL)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
assertStatus(http.StatusOK, res.StatusCode, t)
|
||||
assertHeader("Content-Type", "text/css; charset=utf-8", res, t)
|
||||
assertHeader("Content-Encoding", "", res, t)
|
||||
assertBody([]byte(`* {
|
||||
background-color: white;
|
||||
}`), res, t)
|
||||
}
|
||||
|
||||
func TestGzippedFile(t *testing.T) {
|
||||
h := GZIPHandler(StaticFileHandler("./testdata/styles.css"), nil)
|
||||
s := httptest.NewServer(h)
|
||||
defer s.Close()
|
||||
|
||||
req, err := http.NewRequest("GET", s.URL, nil)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
req.Header.Set("Accept-Encoding", "*")
|
||||
res, err := http.DefaultClient.Do(req)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
assertStatus(http.StatusOK, res.StatusCode, t)
|
||||
assertHeader("Content-Encoding", "gzip", res, t)
|
||||
assertHeader("Content-Type", "text/css; charset=utf-8", res, t)
|
||||
assertGzippedBody([]byte(`* {
|
||||
background-color: white;
|
||||
}`), res, t)
|
||||
}
|
BIN
vendor/github.com/PuerkitoBio/ghost/handlers/testdata/favicon.ico
generated
vendored
Normal file
BIN
vendor/github.com/PuerkitoBio/ghost/handlers/testdata/favicon.ico
generated
vendored
Normal file
Binary file not shown.
After Width: | Height: | Size: 1.4 KiB |
1
vendor/github.com/PuerkitoBio/ghost/handlers/testdata/script.js
generated
vendored
Normal file
1
vendor/github.com/PuerkitoBio/ghost/handlers/testdata/script.js
generated
vendored
Normal file
@@ -0,0 +1 @@
|
||||
var a = 0;
|
3
vendor/github.com/PuerkitoBio/ghost/handlers/testdata/styles.css
generated
vendored
Normal file
3
vendor/github.com/PuerkitoBio/ghost/handlers/testdata/styles.css
generated
vendored
Normal file
@@ -0,0 +1,3 @@
|
||||
* {
|
||||
background-color: white;
|
||||
}
|
68
vendor/github.com/PuerkitoBio/ghost/handlers/utils_test.go
generated
vendored
Normal file
68
vendor/github.com/PuerkitoBio/ghost/handlers/utils_test.go
generated
vendored
Normal file
@@ -0,0 +1,68 @@
|
||||
package handlers
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"compress/gzip"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func assertTrue(cond bool, msg string, t *testing.T) bool {
|
||||
if !cond {
|
||||
t.Error(msg)
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func assertStatus(ex, ac int, t *testing.T) {
|
||||
if ex != ac {
|
||||
t.Errorf("expected status code to be %d, got %d", ex, ac)
|
||||
}
|
||||
}
|
||||
|
||||
func assertBody(ex []byte, res *http.Response, t *testing.T) {
|
||||
buf, err := ioutil.ReadAll(res.Body)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
defer res.Body.Close()
|
||||
|
||||
if !bytes.Equal(ex, buf) {
|
||||
t.Errorf("expected body to be '%s' (%d), got '%s' (%d)", ex, len(ex), buf, len(buf))
|
||||
}
|
||||
}
|
||||
|
||||
func assertGzippedBody(ex []byte, res *http.Response, t *testing.T) {
|
||||
gr, err := gzip.NewReader(res.Body)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
defer res.Body.Close()
|
||||
|
||||
buf := bytes.NewBuffer(nil)
|
||||
_, err = io.Copy(buf, gr)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
if !bytes.Equal(ex, buf.Bytes()) {
|
||||
t.Errorf("expected unzipped body to be '%s' (%d), got '%s' (%d)", ex, len(ex), buf.Bytes(), buf.Len())
|
||||
}
|
||||
}
|
||||
|
||||
func assertHeader(hName, ex string, res *http.Response, t *testing.T) {
|
||||
hVal, ok := res.Header[hName]
|
||||
if (!ok || len(hVal) == 0) && len(ex) > 0 {
|
||||
t.Errorf("expected header %s to be %s, was not set", hName, ex)
|
||||
} else if len(hVal) > 0 && hVal[0] != ex {
|
||||
t.Errorf("expected header %s to be %s, got %s", hName, ex, hVal)
|
||||
}
|
||||
}
|
||||
|
||||
func assertPanic(t *testing.T) {
|
||||
if err := recover(); err == nil {
|
||||
t.Error("expected a panic, got none")
|
||||
}
|
||||
}
|
38
vendor/github.com/PuerkitoBio/ghost/templates/amber/amber.go
generated
vendored
Normal file
38
vendor/github.com/PuerkitoBio/ghost/templates/amber/amber.go
generated
vendored
Normal file
@@ -0,0 +1,38 @@
|
||||
package amber
|
||||
|
||||
import (
|
||||
"github.com/PuerkitoBio/ghost/templates"
|
||||
"github.com/eknkc/amber"
|
||||
)
|
||||
|
||||
// The template compiler for Amber templates.
|
||||
type AmberCompiler struct {
|
||||
Options amber.Options
|
||||
c *amber.Compiler
|
||||
}
|
||||
|
||||
// Create a new Amber compiler with the specified Amber-specific options.
|
||||
func NewAmberCompiler(opts amber.Options) *AmberCompiler {
|
||||
return &AmberCompiler{
|
||||
opts,
|
||||
nil,
|
||||
}
|
||||
}
|
||||
|
||||
// Implementation of the TemplateCompiler interface.
|
||||
func (this *AmberCompiler) Compile(f string) (templates.Templater, error) {
|
||||
// amber.CompileFile creates a new compiler each time. To limit the number
|
||||
// of allocations, reuse a compiler.
|
||||
if this.c == nil {
|
||||
this.c = amber.New()
|
||||
}
|
||||
this.c.Options = this.Options
|
||||
if err := this.c.ParseFile(f); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return this.c.Compile()
|
||||
}
|
||||
|
||||
func init() {
|
||||
templates.Register(".amber", NewAmberCompiler(amber.DefaultOptions))
|
||||
}
|
19
vendor/github.com/PuerkitoBio/ghost/templates/gotpl/gotpl.go
generated
vendored
Normal file
19
vendor/github.com/PuerkitoBio/ghost/templates/gotpl/gotpl.go
generated
vendored
Normal file
@@ -0,0 +1,19 @@
|
||||
package gotpl
|
||||
|
||||
import (
|
||||
"html/template"
|
||||
|
||||
"github.com/PuerkitoBio/ghost/templates"
|
||||
)
|
||||
|
||||
// The template compiler for native Go templates.
|
||||
type GoTemplateCompiler struct{}
|
||||
|
||||
// Implementation of the TemplateCompiler interface.
|
||||
func (this *GoTemplateCompiler) Compile(f string) (templates.Templater, error) {
|
||||
return template.ParseFiles(f)
|
||||
}
|
||||
|
||||
func init() {
|
||||
templates.Register(".tmpl", new(GoTemplateCompiler))
|
||||
}
|
129
vendor/github.com/PuerkitoBio/ghost/templates/template.go
generated
vendored
Normal file
129
vendor/github.com/PuerkitoBio/ghost/templates/template.go
generated
vendored
Normal file
@@ -0,0 +1,129 @@
|
||||
package templates
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"io"
|
||||
"net/http"
|
||||
"os"
|
||||
"path"
|
||||
"path/filepath"
|
||||
"sync"
|
||||
|
||||
"github.com/PuerkitoBio/ghost"
|
||||
)
|
||||
|
||||
var (
|
||||
ErrTemplateNotExist = errors.New("template does not exist")
|
||||
ErrDirNotExist = errors.New("directory does not exist")
|
||||
|
||||
compilers = make(map[string]TemplateCompiler)
|
||||
|
||||
// The mutex guards the templaters map
|
||||
mu sync.RWMutex
|
||||
templaters = make(map[string]Templater)
|
||||
)
|
||||
|
||||
// Defines the interface that the template compiler must return. The Go native
|
||||
// templates implement this interface.
|
||||
type Templater interface {
|
||||
Execute(wr io.Writer, data interface{}) error
|
||||
}
|
||||
|
||||
// The interface that a template engine must implement to be used by Ghost.
|
||||
type TemplateCompiler interface {
|
||||
Compile(fileName string) (Templater, error)
|
||||
}
|
||||
|
||||
// TODO : How to manage Go nested templates?
|
||||
// TODO : Support Go's port of the mustache template?
|
||||
|
||||
// Register a template compiler for the specified extension. Extensions are case-sensitive.
|
||||
// The extension must start with a dot (it is compared to the result of path.Ext() on a
|
||||
// given file name).
|
||||
//
|
||||
// Registering is not thread-safe. Compilers should be registered before the http server
|
||||
// is started.
|
||||
// Compiling templates, on the other hand, is thread-safe.
|
||||
func Register(ext string, c TemplateCompiler) {
|
||||
if c == nil {
|
||||
panic("ghost: Register TemplateCompiler is nil")
|
||||
}
|
||||
if _, dup := compilers[ext]; dup {
|
||||
panic("ghost: Register called twice for extension " + ext)
|
||||
}
|
||||
compilers[ext] = c
|
||||
}
|
||||
|
||||
// Compile all templates that have a matching compiler (based on their extension) in the
|
||||
// specified directory.
|
||||
func CompileDir(dir string) error {
|
||||
mu.Lock()
|
||||
defer mu.Unlock()
|
||||
|
||||
return filepath.Walk(dir, func(path string, fi os.FileInfo, err error) error {
|
||||
if fi == nil {
|
||||
return ErrDirNotExist
|
||||
}
|
||||
if !fi.IsDir() {
|
||||
err = compileTemplate(path, dir)
|
||||
if err != nil {
|
||||
ghost.LogFn("ghost.templates : error compiling template %s : %s", path, err)
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
// Compile a single template file, using the specified base directory. The base
|
||||
// directory is used to set the name of the template (the part of the path relative to this
|
||||
// base directory is used as the name of the template).
|
||||
func Compile(path, base string) error {
|
||||
mu.Lock()
|
||||
defer mu.Unlock()
|
||||
|
||||
return compileTemplate(path, base)
|
||||
}
|
||||
|
||||
// Compile the specified template file if there is a matching compiler.
|
||||
func compileTemplate(p, base string) error {
|
||||
ext := path.Ext(p)
|
||||
c, ok := compilers[ext]
|
||||
// Ignore file if no template compiler exist for this extension
|
||||
if ok {
|
||||
t, err := c.Compile(p)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
key, err := filepath.Rel(base, p)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
ghost.LogFn("ghost.templates : storing template for file %s", key)
|
||||
templaters[key] = t
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Execute the template.
|
||||
func Execute(tplName string, w io.Writer, data interface{}) error {
|
||||
mu.RLock()
|
||||
t, ok := templaters[tplName]
|
||||
mu.RUnlock()
|
||||
if !ok {
|
||||
return ErrTemplateNotExist
|
||||
}
|
||||
return t.Execute(w, data)
|
||||
}
|
||||
|
||||
// Render is the same as Execute, except that it takes a http.ResponseWriter
|
||||
// instead of a generic io.Writer, and sets the Content-Type to text/html.
|
||||
func Render(tplName string, w http.ResponseWriter, data interface{}) (err error) {
|
||||
w.Header().Set("Content-Type", "text/html")
|
||||
defer func() {
|
||||
if err != nil {
|
||||
w.Header().Del("Content-Type")
|
||||
}
|
||||
}()
|
||||
return Execute(tplName, w, data)
|
||||
}
|
24
vendor/github.com/dutchcoders/go-clamd/.gitignore
generated
vendored
Normal file
24
vendor/github.com/dutchcoders/go-clamd/.gitignore
generated
vendored
Normal file
@@ -0,0 +1,24 @@
|
||||
# Compiled Object files, Static and Dynamic libs (Shared Objects)
|
||||
*.o
|
||||
*.a
|
||||
*.so
|
||||
|
||||
# Folders
|
||||
_obj
|
||||
_test
|
||||
|
||||
# Architecture specific extensions/prefixes
|
||||
*.[568vq]
|
||||
[568vq].out
|
||||
|
||||
*.cgo1.go
|
||||
*.cgo2.c
|
||||
_cgo_defun.c
|
||||
_cgo_gotypes.go
|
||||
_cgo_export.*
|
||||
|
||||
_testmain.go
|
||||
|
||||
*.exe
|
||||
*.test
|
||||
*.prof
|
10
vendor/github.com/dutchcoders/go-clamd/.travis.yml
generated
vendored
Normal file
10
vendor/github.com/dutchcoders/go-clamd/.travis.yml
generated
vendored
Normal file
@@ -0,0 +1,10 @@
|
||||
language: go
|
||||
go:
|
||||
- 1.1
|
||||
- 1.2
|
||||
- 1.3
|
||||
- release
|
||||
- tip
|
||||
|
||||
script:
|
||||
- go test -v ./...
|
22
vendor/github.com/dutchcoders/go-clamd/LICENSE
generated
vendored
Normal file
22
vendor/github.com/dutchcoders/go-clamd/LICENSE
generated
vendored
Normal file
@@ -0,0 +1,22 @@
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2014 dutchcoders
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
|
35
vendor/github.com/dutchcoders/go-clamd/README.md
generated
vendored
Normal file
35
vendor/github.com/dutchcoders/go-clamd/README.md
generated
vendored
Normal file
@@ -0,0 +1,35 @@
|
||||
go-clamd
|
||||
========
|
||||
|
||||
Interface to clamd (clamav daemon). You can use go-clamd to implement virus detection capabilities to your application.
|
||||
|
||||
[](https://godoc.org/github.com/dutchcoders/go-clamd)
|
||||
[](https://travis-ci.org/dutchcoders/go-clamd)
|
||||
|
||||
## Examples
|
||||
|
||||
```
|
||||
c := clamd.NewClamd("/tmp/clamd.socket")
|
||||
|
||||
reader := bytes.NewReader(clamd.EICAR)
|
||||
response, err := c.ScanStream(reader)
|
||||
|
||||
for s := range response {
|
||||
fmt.Printf("%v %v\n", s, err)
|
||||
}
|
||||
```
|
||||
|
||||
## Contributions
|
||||
|
||||
Contributions are welcome.
|
||||
|
||||
## Creators
|
||||
|
||||
**Remco Verhoef**
|
||||
- <https://twitter.com/remco_verhoef>
|
||||
|
||||
- <https://twitter.com/dutchcoders>
|
||||
|
||||
## Copyright and license
|
||||
|
||||
Code and documentation copyright 2011-2014 Remco Verhoef. Code released under [the MIT license](LICENSE).
|
311
vendor/github.com/dutchcoders/go-clamd/clamd.go
generated
vendored
Normal file
311
vendor/github.com/dutchcoders/go-clamd/clamd.go
generated
vendored
Normal file
@@ -0,0 +1,311 @@
|
||||
/*
|
||||
Open Source Initiative OSI - The MIT License (MIT):Licensing
|
||||
|
||||
The MIT License (MIT)
|
||||
Copyright (c) 2013 DutchCoders <http://github.com/dutchcoders/>
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||
this software and associated documentation files (the "Software"), to deal in
|
||||
the Software without restriction, including without limitation the rights to
|
||||
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
|
||||
of the Software, and to permit persons to whom the Software is furnished to do
|
||||
so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
*/
|
||||
|
||||
package clamd
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/url"
|
||||
"strings"
|
||||
)
|
||||
|
||||
const (
|
||||
RES_OK = "OK"
|
||||
RES_FOUND = "FOUND"
|
||||
RES_ERROR = "ERROR"
|
||||
RES_PARSE_ERROR = "PARSE ERROR"
|
||||
)
|
||||
|
||||
type Clamd struct {
|
||||
address string
|
||||
}
|
||||
|
||||
type Stats struct {
|
||||
Pools string
|
||||
State string
|
||||
Threads string
|
||||
Memstats string
|
||||
Queue string
|
||||
}
|
||||
|
||||
type ScanResult struct {
|
||||
Raw string
|
||||
Description string
|
||||
Path string
|
||||
Hash string
|
||||
Size int
|
||||
Status string
|
||||
}
|
||||
|
||||
var EICAR = []byte(`X5O!P%@AP[4\PZX54(P^)7CC)7}$EICAR-STANDARD-ANTIVIRUS-TEST-FILE!$H+H*`)
|
||||
|
||||
func (c *Clamd) newConnection() (conn *CLAMDConn, err error) {
|
||||
|
||||
var u *url.URL
|
||||
|
||||
if u, err = url.Parse(c.address); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
switch u.Scheme {
|
||||
case "tcp":
|
||||
conn, err = newCLAMDTcpConn(u.Host)
|
||||
case "unix":
|
||||
conn, err = newCLAMDUnixConn(u.Path)
|
||||
default:
|
||||
conn, err = newCLAMDUnixConn(c.address)
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func (c *Clamd) simpleCommand(command string) (chan *ScanResult, error) {
|
||||
conn, err := c.newConnection()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
err = conn.sendCommand(command)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
ch, wg, err := conn.readResponse()
|
||||
|
||||
go func() {
|
||||
wg.Wait()
|
||||
conn.Close()
|
||||
}()
|
||||
|
||||
return ch, err
|
||||
}
|
||||
|
||||
/*
|
||||
Check the daemon's state (should reply with PONG).
|
||||
*/
|
||||
func (c *Clamd) Ping() error {
|
||||
ch, err := c.simpleCommand("PING")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
select {
|
||||
case s := (<-ch):
|
||||
switch s.Raw {
|
||||
case "PONG":
|
||||
return nil
|
||||
default:
|
||||
return errors.New(fmt.Sprintf("Invalid response, got %s.", s))
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
/*
|
||||
Print program and database versions.
|
||||
*/
|
||||
func (c *Clamd) Version() (chan *ScanResult, error) {
|
||||
dataArrays, err := c.simpleCommand("VERSION")
|
||||
return dataArrays, err
|
||||
}
|
||||
|
||||
/*
|
||||
On this command clamd provides statistics about the scan queue, contents of scan
|
||||
queue, and memory usage. The exact reply format is subject to changes in future
|
||||
releases.
|
||||
*/
|
||||
func (c *Clamd) Stats() (*Stats, error) {
|
||||
ch, err := c.simpleCommand("STATS")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
stats := &Stats{}
|
||||
|
||||
for s := range ch {
|
||||
if strings.HasPrefix(s.Raw, "POOLS") {
|
||||
stats.Pools = strings.Trim(s.Raw[6:], " ")
|
||||
} else if strings.HasPrefix(s.Raw, "STATE") {
|
||||
stats.State = s.Raw
|
||||
} else if strings.HasPrefix(s.Raw, "THREADS") {
|
||||
stats.Threads = s.Raw
|
||||
} else if strings.HasPrefix(s.Raw, "QUEUE") {
|
||||
stats.Queue = s.Raw
|
||||
} else if strings.HasPrefix(s.Raw, "MEMSTATS") {
|
||||
stats.Memstats = s.Raw
|
||||
} else if strings.HasPrefix(s.Raw, "END") {
|
||||
} else {
|
||||
// return nil, errors.New(fmt.Sprintf("Unknown response, got %s.", s))
|
||||
}
|
||||
}
|
||||
|
||||
return stats, nil
|
||||
}
|
||||
|
||||
/*
|
||||
Reload the databases.
|
||||
*/
|
||||
func (c *Clamd) Reload() error {
|
||||
ch, err := c.simpleCommand("RELOAD")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
select {
|
||||
case s := (<-ch):
|
||||
switch s.Raw {
|
||||
case "RELOADING":
|
||||
return nil
|
||||
default:
|
||||
return errors.New(fmt.Sprintf("Invalid response, got %s.", s))
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *Clamd) Shutdown() error {
|
||||
_, err := c.simpleCommand("SHUTDOWN")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
/*
|
||||
Scan file or directory (recursively) with archive support enabled (a full path is
|
||||
required).
|
||||
*/
|
||||
func (c *Clamd) ScanFile(path string) (chan *ScanResult, error) {
|
||||
command := fmt.Sprintf("SCAN %s", path)
|
||||
ch, err := c.simpleCommand(command)
|
||||
return ch, err
|
||||
}
|
||||
|
||||
/*
|
||||
Scan file or directory (recursively) with archive and special file support disabled
|
||||
(a full path is required).
|
||||
*/
|
||||
func (c *Clamd) RawScanFile(path string) (chan *ScanResult, error) {
|
||||
command := fmt.Sprintf("RAWSCAN %s", path)
|
||||
ch, err := c.simpleCommand(command)
|
||||
return ch, err
|
||||
}
|
||||
|
||||
/*
|
||||
Scan file in a standard way or scan directory (recursively) using multiple threads
|
||||
(to make the scanning faster on SMP machines).
|
||||
*/
|
||||
func (c *Clamd) MultiScanFile(path string) (chan *ScanResult, error) {
|
||||
command := fmt.Sprintf("MULTISCAN %s", path)
|
||||
ch, err := c.simpleCommand(command)
|
||||
return ch, err
|
||||
}
|
||||
|
||||
/*
|
||||
Scan file or directory (recursively) with archive support enabled and don’t stop
|
||||
the scanning when a virus is found.
|
||||
*/
|
||||
func (c *Clamd) ContScanFile(path string) (chan *ScanResult, error) {
|
||||
command := fmt.Sprintf("CONTSCAN %s", path)
|
||||
ch, err := c.simpleCommand(command)
|
||||
return ch, err
|
||||
}
|
||||
|
||||
/*
|
||||
Scan file or directory (recursively) with archive support enabled and don’t stop
|
||||
the scanning when a virus is found.
|
||||
*/
|
||||
func (c *Clamd) AllMatchScanFile(path string) (chan *ScanResult, error) {
|
||||
command := fmt.Sprintf("ALLMATCHSCAN %s", path)
|
||||
ch, err := c.simpleCommand(command)
|
||||
return ch, err
|
||||
}
|
||||
|
||||
/*
|
||||
Scan a stream of data. The stream is sent to clamd in chunks, after INSTREAM,
|
||||
on the same socket on which the command was sent. This avoids the overhead
|
||||
of establishing new TCP connections and problems with NAT. The format of the
|
||||
chunk is: <length><data> where <length> is the size of the following data in
|
||||
bytes expressed as a 4 byte unsigned integer in network byte order and <data> is
|
||||
the actual chunk. Streaming is terminated by sending a zero-length chunk. Note:
|
||||
do not exceed StreamMaxLength as defined in clamd.conf, otherwise clamd will
|
||||
reply with INSTREAM size limit exceeded and close the connection
|
||||
*/
|
||||
func (c *Clamd) ScanStream(r io.Reader, abort chan bool) (chan *ScanResult, error) {
|
||||
conn, err := c.newConnection()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
go func() {
|
||||
for {
|
||||
_, allowRunning := <-abort
|
||||
if !allowRunning {
|
||||
break
|
||||
}
|
||||
}
|
||||
conn.Close()
|
||||
}()
|
||||
|
||||
conn.sendCommand("INSTREAM")
|
||||
|
||||
for {
|
||||
buf := make([]byte, CHUNK_SIZE)
|
||||
|
||||
nr, err := r.Read(buf)
|
||||
if nr > 0 {
|
||||
conn.sendChunk(buf[0:nr])
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
break
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
err = conn.sendEOF()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
ch, wg, err := conn.readResponse()
|
||||
|
||||
go func() {
|
||||
wg.Wait()
|
||||
conn.Close()
|
||||
}()
|
||||
|
||||
return ch, nil
|
||||
}
|
||||
|
||||
func NewClamd(address string) *Clamd {
|
||||
clamd := &Clamd{address: address}
|
||||
return clamd
|
||||
}
|
178
vendor/github.com/dutchcoders/go-clamd/conn.go
generated
vendored
Normal file
178
vendor/github.com/dutchcoders/go-clamd/conn.go
generated
vendored
Normal file
@@ -0,0 +1,178 @@
|
||||
/*
|
||||
Open Source Initiative OSI - The MIT License (MIT):Licensing
|
||||
|
||||
The MIT License (MIT)
|
||||
Copyright (c) 2013 DutchCoders <http://github.com/dutchcoders/>
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||
this software and associated documentation files (the "Software"), to deal in
|
||||
the Software without restriction, including without limitation the rights to
|
||||
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
|
||||
of the Software, and to permit persons to whom the Software is furnished to do
|
||||
so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
*/
|
||||
|
||||
package clamd
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"fmt"
|
||||
"io"
|
||||
"net"
|
||||
"regexp"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
|
||||
const CHUNK_SIZE = 1024
|
||||
const TCP_TIMEOUT = time.Second * 2
|
||||
|
||||
var resultRegex = regexp.MustCompile(
|
||||
`^(?P<path>[^:]+): ((?P<desc>[^:]+)(\((?P<virhash>([^:]+)):(?P<virsize>\d+)\))? )?(?P<status>FOUND|ERROR|OK)$`,
|
||||
)
|
||||
|
||||
type CLAMDConn struct {
|
||||
net.Conn
|
||||
}
|
||||
|
||||
func (conn *CLAMDConn) sendCommand(command string) error {
|
||||
commandBytes := []byte(fmt.Sprintf("n%s\n", command))
|
||||
|
||||
_, err := conn.Write(commandBytes)
|
||||
return err
|
||||
}
|
||||
|
||||
func (conn *CLAMDConn) sendEOF() error {
|
||||
_, err := conn.Write([]byte{0, 0, 0, 0})
|
||||
return err
|
||||
}
|
||||
|
||||
func (conn *CLAMDConn) sendChunk(data []byte) error {
|
||||
var buf [4]byte
|
||||
lenData := len(data)
|
||||
buf[0] = byte(lenData >> 24)
|
||||
buf[1] = byte(lenData >> 16)
|
||||
buf[2] = byte(lenData >> 8)
|
||||
buf[3] = byte(lenData >> 0)
|
||||
|
||||
a := buf
|
||||
|
||||
b := make([]byte, len(a))
|
||||
for i := range a {
|
||||
b[i] = a[i]
|
||||
}
|
||||
|
||||
conn.Write(b)
|
||||
|
||||
_, err := conn.Write(data)
|
||||
return err
|
||||
}
|
||||
|
||||
func (c *CLAMDConn) readResponse() (chan *ScanResult, *sync.WaitGroup, error) {
|
||||
var wg sync.WaitGroup
|
||||
|
||||
wg.Add(1)
|
||||
reader := bufio.NewReader(c)
|
||||
ch := make(chan *ScanResult)
|
||||
|
||||
go func() {
|
||||
defer func() {
|
||||
close(ch)
|
||||
wg.Done()
|
||||
}()
|
||||
|
||||
for {
|
||||
line, err := reader.ReadString('\n')
|
||||
if err == io.EOF {
|
||||
return
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
line = strings.TrimRight(line, " \t\r\n")
|
||||
ch <- parseResult(line)
|
||||
}
|
||||
}()
|
||||
|
||||
return ch, &wg, nil
|
||||
}
|
||||
|
||||
func parseResult(line string) *ScanResult {
|
||||
res := &ScanResult{}
|
||||
res.Raw = line
|
||||
|
||||
matches := resultRegex.FindStringSubmatch(line)
|
||||
if len(matches) == 0 {
|
||||
res.Description = "Regex had no matches"
|
||||
res.Status = RES_PARSE_ERROR
|
||||
return res
|
||||
}
|
||||
|
||||
for i, name := range resultRegex.SubexpNames() {
|
||||
switch name {
|
||||
case "path":
|
||||
res.Path = matches[i]
|
||||
case "desc":
|
||||
res.Description = matches[i]
|
||||
case "virhash":
|
||||
res.Hash = matches[i]
|
||||
case "virsize":
|
||||
i, err := strconv.Atoi(matches[i])
|
||||
if err == nil {
|
||||
res.Size = i
|
||||
}
|
||||
case "status":
|
||||
switch matches[i] {
|
||||
case RES_OK:
|
||||
case RES_FOUND:
|
||||
case RES_ERROR:
|
||||
break
|
||||
default:
|
||||
res.Description = "Invalid status field: " + matches[i]
|
||||
res.Status = RES_PARSE_ERROR
|
||||
return res
|
||||
}
|
||||
res.Status = matches[i]
|
||||
}
|
||||
}
|
||||
|
||||
return res
|
||||
}
|
||||
|
||||
func newCLAMDTcpConn(address string) (*CLAMDConn, error) {
|
||||
conn, err := net.DialTimeout("tcp", address, TCP_TIMEOUT)
|
||||
|
||||
if err != nil {
|
||||
if nerr, isOk := err.(net.Error); isOk && nerr.Timeout() {
|
||||
return nil, nerr
|
||||
}
|
||||
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &CLAMDConn{Conn: conn}, err
|
||||
}
|
||||
|
||||
func newCLAMDUnixConn(address string) (*CLAMDConn, error) {
|
||||
conn, err := net.Dial("unix", address)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &CLAMDConn{Conn: conn}, err
|
||||
}
|
72
vendor/github.com/dutchcoders/go-clamd/examples/main.go
generated
vendored
Normal file
72
vendor/github.com/dutchcoders/go-clamd/examples/main.go
generated
vendored
Normal file
@@ -0,0 +1,72 @@
|
||||
/*
|
||||
Open Source Initiative OSI - The MIT License (MIT):Licensing
|
||||
|
||||
The MIT License (MIT)
|
||||
Copyright (c) 2013 DutchCoders <http://github.com/dutchcoders/>
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||
this software and associated documentation files (the "Software"), to deal in
|
||||
the Software without restriction, including without limitation the rights to
|
||||
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
|
||||
of the Software, and to permit persons to whom the Software is furnished to do
|
||||
so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
*/
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
_ "bytes"
|
||||
"fmt"
|
||||
"github.com/dutchcoders/go-clamd"
|
||||
)
|
||||
|
||||
func main() {
|
||||
fmt.Println("Made with <3 DutchCoders")
|
||||
|
||||
c := clamd.NewClamd("/tmp/clamd.socket")
|
||||
_ = c
|
||||
|
||||
/*
|
||||
reader := bytes.NewReader(clamd.EICAR)
|
||||
response, err := c.ScanStream(reader)
|
||||
|
||||
for s := range response {
|
||||
fmt.Printf("%v %v\n", s, err)
|
||||
}
|
||||
|
||||
response, err = c.ScanFile(".")
|
||||
|
||||
for s := range response {
|
||||
fmt.Printf("%v %v\n", s, err)
|
||||
}
|
||||
|
||||
response, err = c.Version()
|
||||
|
||||
for s := range response {
|
||||
fmt.Printf("%v %v\n", s, err)
|
||||
}
|
||||
*/
|
||||
|
||||
err := c.Ping()
|
||||
fmt.Printf("Ping: %v\n", err)
|
||||
|
||||
stats, err := c.Stats()
|
||||
fmt.Printf("%v %v\n", stats, err)
|
||||
|
||||
err = c.Reload()
|
||||
fmt.Printf("Reload: %v\n", err)
|
||||
|
||||
// response, err = c.Shutdown()
|
||||
// fmt.Println(response)
|
||||
}
|
23
vendor/github.com/dutchcoders/go-virustotal/.gitignore
generated
vendored
Normal file
23
vendor/github.com/dutchcoders/go-virustotal/.gitignore
generated
vendored
Normal file
@@ -0,0 +1,23 @@
|
||||
# Compiled Object files, Static and Dynamic libs (Shared Objects)
|
||||
*.o
|
||||
*.a
|
||||
*.so
|
||||
|
||||
# Folders
|
||||
_obj
|
||||
_test
|
||||
|
||||
# Architecture specific extensions/prefixes
|
||||
*.[568vq]
|
||||
[568vq].out
|
||||
|
||||
*.cgo1.go
|
||||
*.cgo2.c
|
||||
_cgo_defun.c
|
||||
_cgo_gotypes.go
|
||||
_cgo_export.*
|
||||
|
||||
_testmain.go
|
||||
|
||||
*.exe
|
||||
*.test
|
10
vendor/github.com/dutchcoders/go-virustotal/.travis.yml
generated
vendored
Normal file
10
vendor/github.com/dutchcoders/go-virustotal/.travis.yml
generated
vendored
Normal file
@@ -0,0 +1,10 @@
|
||||
language: go
|
||||
go:
|
||||
- 1.1
|
||||
- 1.2
|
||||
- 1.3
|
||||
- release
|
||||
- tip
|
||||
|
||||
script:
|
||||
- go test -v ./...
|
21
vendor/github.com/dutchcoders/go-virustotal/LICENSE
generated
vendored
Normal file
21
vendor/github.com/dutchcoders/go-virustotal/LICENSE
generated
vendored
Normal file
@@ -0,0 +1,21 @@
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2014 dutchcoders
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
38
vendor/github.com/dutchcoders/go-virustotal/README.md
generated
vendored
Normal file
38
vendor/github.com/dutchcoders/go-virustotal/README.md
generated
vendored
Normal file
@@ -0,0 +1,38 @@
|
||||
go-virustotal
|
||||
=============
|
||||
|
||||
VirusTotal public api interface implementation in Golang.
|
||||
|
||||
[](https://godoc.org/github.com/dutchcoders/go-virustotal)
|
||||
[](https://travis-ci.org/dutchcoders/go-virustotal)
|
||||
|
||||
Usage
|
||||
=====
|
||||
|
||||
You can also set the environment variable VIRUSTOTAL_APIKEY to the api key.
|
||||
|
||||
```
|
||||
go run ./bin/vt.go --apikey {key} (--debug) scan {file} {file} ...
|
||||
go run ./bin/vt.go --apikey {key} (--debug) rescan {hash} {hash} ...
|
||||
go run ./bin/vt.go --apikey {key} (--debug) report 99017f6eebbac24f351415dd410d522d
|
||||
go run ./bin/vt.go --apikey {key} (--debug) scan-url {url} {url} ...
|
||||
go run ./bin/vt.go --apikey {key} (--debug) report-url www.google.com
|
||||
go run ./bin/vt.go --apikey {key} (--debug) ipaddress 90.156.201.27
|
||||
go run ./bin/vt.go --apikey {key} (--debug) domain 027.ru
|
||||
go run ./bin/vt.go --apikey {key} (--debug) --resource 99017f6eebbac24f351415dd410d522d comment "How to disinfect you from this file... #disinfect #zbot"
|
||||
```
|
||||
|
||||
## Contributions
|
||||
|
||||
Contributions are welcome.
|
||||
|
||||
## Creators
|
||||
|
||||
**Remco Verhoef**
|
||||
- <https://twitter.com/remco_verhoef>
|
||||
|
||||
- <https://twitter.com/dutchcoders>
|
||||
|
||||
## Copyright and license
|
||||
|
||||
Code and documentation copyright 2011-2014 Remco Verhoef. Code released under [the MIT license](LICENSE).
|
361
vendor/github.com/dutchcoders/go-virustotal/virustotal.go
generated
vendored
Normal file
361
vendor/github.com/dutchcoders/go-virustotal/virustotal.go
generated
vendored
Normal file
@@ -0,0 +1,361 @@
|
||||
/*
|
||||
Open Source Initiative OSI - The MIT License (MIT):Licensing
|
||||
|
||||
The MIT License (MIT)
|
||||
Copyright (c) 2013 DutchCoders <http://github.com/dutchcoders/>
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||
this software and associated documentation files (the "Software"), to deal in
|
||||
the Software without restriction, including without limitation the rights to
|
||||
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
|
||||
of the Software, and to permit persons to whom the Software is furnished to do
|
||||
so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
*/
|
||||
|
||||
package virustotal
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"mime/multipart"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type VirusTotal struct {
|
||||
apikey string
|
||||
}
|
||||
|
||||
type VirusTotalResponse struct {
|
||||
ResponseCode int `json:"response_code"`
|
||||
Message string `json:"verbose_msg"`
|
||||
}
|
||||
|
||||
type ScanResponse struct {
|
||||
VirusTotalResponse
|
||||
|
||||
ScanId string `json:"scan_id"`
|
||||
Sha1 string `json:"sha1"`
|
||||
Resource string `json:"resource"`
|
||||
Sha256 string `json:"sha256"`
|
||||
Permalink string `json:"permalink"`
|
||||
Md5 string `json:"md5"`
|
||||
}
|
||||
|
||||
type FileScan struct {
|
||||
Detected bool `json:"detected"`
|
||||
Version string `json:"version"`
|
||||
Result string `json:"result"`
|
||||
Update string `json:"update"`
|
||||
}
|
||||
|
||||
type ReportResponse struct {
|
||||
VirusTotalResponse
|
||||
Resource string `json:"resource"`
|
||||
ScanId string `json:"scan_id"`
|
||||
Sha1 string `json:"sha1"`
|
||||
Sha256 string `json:"sha256"`
|
||||
Md5 string `json:"md5"`
|
||||
Scandate string `json:"scan_date"`
|
||||
Positives int `json:"positives"`
|
||||
Total int `json:"total"`
|
||||
Permalink string `json:"permalink"`
|
||||
Scans map[string]FileScan `json:"scans"`
|
||||
}
|
||||
|
||||
func (sr *ScanResponse) String() string {
|
||||
return fmt.Sprintf("scanid: %s, resource: %s, permalink: %s, md5: %s", sr.ScanId, sr.Resource, sr.Permalink, sr.Md5)
|
||||
}
|
||||
|
||||
type ScanUrlResponse struct {
|
||||
ScanResponse
|
||||
}
|
||||
|
||||
type RescanResponse struct {
|
||||
ScanResponse
|
||||
}
|
||||
|
||||
func (sr *RescanResponse) String() string {
|
||||
return fmt.Sprintf("scanid: %s, resource: %s, permalink: %s, md5: %s", sr.ScanId, sr.Resource, sr.Permalink, sr.Md5)
|
||||
}
|
||||
|
||||
type DetectedUrl struct {
|
||||
ScanDate string `json:"scan_date"`
|
||||
Url string `json:"url"`
|
||||
Positives int `json:"positives"`
|
||||
Total int `json:"total"`
|
||||
}
|
||||
|
||||
type Resolution struct {
|
||||
LastResolved string `json:"last_resolved"`
|
||||
Hostname string `json:"hostname"`
|
||||
}
|
||||
|
||||
type IpAddressReportResponse struct {
|
||||
VirusTotalResponse
|
||||
Resolutions []Resolution `json:"resolutions"`
|
||||
DetectedUrls []DetectedUrl `json:"detected_urls"`
|
||||
}
|
||||
|
||||
type DomainReportResponse struct {
|
||||
VirusTotalResponse
|
||||
Resolutions []Resolution `json:"resolutions"`
|
||||
DetectedUrls []DetectedUrl `json:"detected_urls"`
|
||||
}
|
||||
|
||||
type CommentResponse struct {
|
||||
VirusTotalResponse
|
||||
}
|
||||
|
||||
func NewVirusTotal(apikey string) (*VirusTotal, error) {
|
||||
vt := &VirusTotal{apikey: apikey}
|
||||
return vt, nil
|
||||
}
|
||||
|
||||
func (vt *VirusTotal) DomainReport(domain string) (*DomainReportResponse, error) {
|
||||
u, err := url.Parse("https://www.virustotal.com/vtapi/v2/domain/report")
|
||||
u.RawQuery = url.Values{"apikey": {vt.apikey}, "domain": {domain}}.Encode()
|
||||
|
||||
resp, err := http.Get(u.String())
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
defer resp.Body.Close()
|
||||
|
||||
contents, err := ioutil.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var domainReportResponse = &DomainReportResponse{}
|
||||
|
||||
err = json.Unmarshal(contents, &domainReportResponse)
|
||||
|
||||
return domainReportResponse, err
|
||||
}
|
||||
|
||||
func (vt *VirusTotal) ScanUrl(url2 *url.URL) (*ScanResponse, error) {
|
||||
u, err := url.Parse("https://www.virustotal.com/vtapi/v2/url/scan")
|
||||
|
||||
params := url.Values{"apikey": {vt.apikey}, "url": {url2.String()}}
|
||||
|
||||
resp, err := http.PostForm(u.String(), params)
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
defer resp.Body.Close()
|
||||
|
||||
contents, err := ioutil.ReadAll(resp.Body)
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var scanResponse = &ScanResponse{}
|
||||
|
||||
err = json.Unmarshal(contents, &scanResponse)
|
||||
|
||||
return scanResponse, err
|
||||
}
|
||||
|
||||
func (vt *VirusTotal) Report(resource string) (*ReportResponse, error) {
|
||||
u, err := url.Parse("https://www.virustotal.com/vtapi/v2/file/report")
|
||||
|
||||
params := url.Values{"apikey": {vt.apikey}, "resource": {resource}}
|
||||
|
||||
resp, err := http.PostForm(u.String(), params)
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
defer resp.Body.Close()
|
||||
|
||||
contents, err := ioutil.ReadAll(resp.Body)
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var reportResponse = &ReportResponse{}
|
||||
|
||||
err = json.Unmarshal(contents, &reportResponse)
|
||||
|
||||
return reportResponse, err
|
||||
}
|
||||
|
||||
func (vt *VirusTotal) ReportUrl(url2 *url.URL) (*ReportResponse, error) {
|
||||
params := url.Values{"apikey": {vt.apikey}, "resource": {url2.String()}}
|
||||
|
||||
u, err := url.Parse("https://www.virustotal.com/vtapi/v2/url/report")
|
||||
|
||||
resp, err := http.PostForm(u.String(), params)
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
defer resp.Body.Close()
|
||||
|
||||
contents, err := ioutil.ReadAll(resp.Body)
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var reportResponse = &ReportResponse{}
|
||||
|
||||
err = json.Unmarshal(contents, &reportResponse)
|
||||
|
||||
return reportResponse, err
|
||||
}
|
||||
|
||||
func (vt *VirusTotal) Comment(resource string, comment string) (*CommentResponse, error) {
|
||||
u, err := url.Parse("https://www.virustotal.com/vtapi/v2/comments/put")
|
||||
params := url.Values{"apikey": {vt.apikey}, "resource": {resource}, "comment": {comment}}
|
||||
|
||||
resp, err := http.PostForm(u.String(), params)
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
defer resp.Body.Close()
|
||||
|
||||
contents, err := ioutil.ReadAll(resp.Body)
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var commentResponse = &CommentResponse{}
|
||||
|
||||
err = json.Unmarshal(contents, &commentResponse)
|
||||
|
||||
return commentResponse, err
|
||||
}
|
||||
|
||||
func (vt *VirusTotal) IpAddressReport(ip string) (*IpAddressReportResponse, error) {
|
||||
u, err := url.Parse("http://www.virustotal.com/vtapi/v2/ip-address/report")
|
||||
u.RawQuery = url.Values{"apikey": {vt.apikey}, "ip": {ip}}.Encode()
|
||||
|
||||
resp, err := http.Get(u.String())
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
defer resp.Body.Close()
|
||||
|
||||
contents, err := ioutil.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var ipAddressReportResponse = &IpAddressReportResponse{}
|
||||
|
||||
err = json.Unmarshal(contents, &ipAddressReportResponse)
|
||||
|
||||
return ipAddressReportResponse, err
|
||||
}
|
||||
|
||||
func (vt *VirusTotal) Rescan(hash []string) (*RescanResponse, error) {
|
||||
resource := strings.Join(hash, ",")
|
||||
|
||||
resp, err := http.PostForm("https://www.virustotal.com/vtapi/v2/file/rescan", url.Values{"apikey": {vt.apikey}, "resource": {resource}})
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
defer resp.Body.Close()
|
||||
|
||||
contents, err := ioutil.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var rescanResponse = &RescanResponse{}
|
||||
|
||||
err = json.Unmarshal(contents, &rescanResponse)
|
||||
|
||||
return rescanResponse, err
|
||||
}
|
||||
|
||||
func (vt *VirusTotal) Scan(path string, file io.Reader) (*ScanResponse, error) {
|
||||
params := map[string]string{
|
||||
"apikey": vt.apikey,
|
||||
}
|
||||
|
||||
request, err := newfileUploadRequest("http://www.virustotal.com/vtapi/v2/file/scan", params, path, file)
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
client := &http.Client{}
|
||||
|
||||
resp, err := client.Do(request)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
defer resp.Body.Close()
|
||||
|
||||
contents, err := ioutil.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var scanResponse = &ScanResponse{}
|
||||
err = json.Unmarshal(contents, &scanResponse)
|
||||
|
||||
return scanResponse, err
|
||||
}
|
||||
|
||||
// Creates a new file upload http request with optional extra params
|
||||
func newfileUploadRequest(uri string, params map[string]string, path string, file io.Reader) (*http.Request, error) {
|
||||
body := &bytes.Buffer{}
|
||||
writer := multipart.NewWriter(body)
|
||||
|
||||
for key, val := range params {
|
||||
_ = writer.WriteField(key, val)
|
||||
}
|
||||
|
||||
part, err := writer.CreateFormFile("file", filepath.Base(path))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
_, err = io.Copy(part, file)
|
||||
|
||||
err = writer.Close()
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
req, err := http.NewRequest("POST", uri, body)
|
||||
|
||||
req.Header.Set("Content-Type", writer.FormDataContentType())
|
||||
return req, err
|
||||
}
|
3
vendor/github.com/dutchcoders/transfer.sh-web/.bowerrc
generated
vendored
Normal file
3
vendor/github.com/dutchcoders/transfer.sh-web/.bowerrc
generated
vendored
Normal file
@@ -0,0 +1,3 @@
|
||||
{
|
||||
"directory": "transfersh-web/bower_components"
|
||||
}
|
14
vendor/github.com/dutchcoders/transfer.sh-web/.gitignore
generated
vendored
Normal file
14
vendor/github.com/dutchcoders/transfer.sh-web/.gitignore
generated
vendored
Normal file
@@ -0,0 +1,14 @@
|
||||
build/
|
||||
pkg/
|
||||
dist/
|
||||
bin/
|
||||
*.pyc
|
||||
*.egg-info/
|
||||
|
||||
.tmp
|
||||
.vagrant
|
||||
|
||||
bower_components/
|
||||
node_modules/
|
||||
|
||||
letsencrypt.cache
|
308
vendor/github.com/dutchcoders/transfer.sh-web/Gruntfile.js
generated
vendored
Normal file
308
vendor/github.com/dutchcoders/transfer.sh-web/Gruntfile.js
generated
vendored
Normal file
@@ -0,0 +1,308 @@
|
||||
'use strict';
|
||||
|
||||
// # Globbing
|
||||
// for performance reasons we're only matching one level down:
|
||||
// 'test/spec/{,*/}*.js'
|
||||
// use this if you want to match all subfolders:
|
||||
// 'test/spec/**/*.js'
|
||||
|
||||
module.exports = function (grunt) {
|
||||
// load all grunt tasks
|
||||
require('load-grunt-tasks')(grunt);
|
||||
// show elapsed time at the end
|
||||
require('time-grunt')(grunt);
|
||||
|
||||
// configurable paths
|
||||
var yeomanConfig = {
|
||||
app: require('./bower.json').appPath || 'src',
|
||||
dist: 'dist/'
|
||||
};
|
||||
|
||||
grunt.initConfig({
|
||||
yeoman: yeomanConfig,
|
||||
watch: {
|
||||
less: {
|
||||
files: ['<%= yeoman.app %>/styles/{,*/}*.less'],
|
||||
tasks: ['less']
|
||||
},
|
||||
gruntfile: {
|
||||
files: ['Gruntfile.js']
|
||||
},
|
||||
includes: {
|
||||
files: ['<%= yeoman.app %>/*.html', '.tmp/*.html'],
|
||||
tasks: ['includes:server']
|
||||
},
|
||||
livereload: {
|
||||
options: {
|
||||
livereload: '<%= connect.options.livereload %>'
|
||||
},
|
||||
files: [
|
||||
'<%= yeoman.app %>/*.html',
|
||||
'{.tmp,<%= yeoman.app %>}/styles/{,*/}*.css',
|
||||
'{.tmp,<%= yeoman.app %>}/scripts/{,*/}*.js',
|
||||
'<%= yeoman.app %>/images/{,*/}*.{png,jpg,jpeg,gif,webp,svg}'
|
||||
],
|
||||
tasks: ['includes:server']
|
||||
}
|
||||
},
|
||||
connect: {
|
||||
options: {
|
||||
port: 9000,
|
||||
// change this to '0.0.0.0' to access the server from outside
|
||||
hostname: 'localhost',
|
||||
livereload: 35729
|
||||
},
|
||||
livereload: {
|
||||
options: {
|
||||
open: true,
|
||||
base: [
|
||||
'.tmp',
|
||||
'<%= yeoman.app %>'
|
||||
]
|
||||
}
|
||||
},
|
||||
test: {
|
||||
options: {
|
||||
port: 9001,
|
||||
base: [
|
||||
'.tmp',
|
||||
'test',
|
||||
'<%= yeoman.app %>'
|
||||
]
|
||||
}
|
||||
},
|
||||
dist: {
|
||||
options: {
|
||||
base: '<%= yeoman.dist %>'
|
||||
}
|
||||
}
|
||||
},
|
||||
clean: {
|
||||
dist: {
|
||||
files: [{
|
||||
dot: true,
|
||||
src: [
|
||||
'.tmp',
|
||||
'<%= yeoman.dist %>/*',
|
||||
'!<%= yeoman.dist %>/.git*'
|
||||
]
|
||||
}]
|
||||
},
|
||||
server: '.tmp'
|
||||
},
|
||||
jshint: {
|
||||
options: {
|
||||
jshintrc: '.jshintrc',
|
||||
reporter: require('jshint-stylish')
|
||||
},
|
||||
all: [
|
||||
'Gruntfile.js',
|
||||
'<%= yeoman.app %>/scripts/{,*/}*.js',
|
||||
'!<%= yeoman.app %>/scripts/vendor/*',
|
||||
'test/spec/{,*/}*.js'
|
||||
]
|
||||
},
|
||||
|
||||
|
||||
less: {
|
||||
dist: {
|
||||
files: {
|
||||
'<%= yeoman.app %>/styles/main.css': ['<%= yeoman.app %>/styles/main.less']
|
||||
},
|
||||
options: {
|
||||
sourceMap: true,
|
||||
sourceMapFilename: '<%= yeoman.app %>/styles/main.css.map',
|
||||
sourceMapBasepath: '<%= yeoman.app %>/',
|
||||
sourceMapRootpath: '/'
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
includes: {
|
||||
build: {
|
||||
cwd: '<%= yeoman.app %>',
|
||||
src: ['*.html', 'includes/*.html'],
|
||||
dest: '<%= yeoman.dist %>',
|
||||
options: {
|
||||
flatten: true,
|
||||
banner: ''
|
||||
}
|
||||
},
|
||||
server: {
|
||||
cwd: '<%= yeoman.app %>',
|
||||
src: ['*.html', 'includes/*.html'],
|
||||
dest: '.tmp/',
|
||||
options: {
|
||||
flatten: true,
|
||||
banner: ''
|
||||
}
|
||||
}
|
||||
},
|
||||
// not used since Uglify task does concat,
|
||||
// but still available if needed
|
||||
/*concat: {
|
||||
dist: {}
|
||||
},*/
|
||||
// not enabled since usemin task does concat and uglify
|
||||
// check index.html to edit your build targets
|
||||
// enable this task if you prefer defining your build targets here
|
||||
/*uglify: {
|
||||
dist: {}
|
||||
},*/
|
||||
rev: {
|
||||
dist: {
|
||||
files: {
|
||||
src: [
|
||||
'<%= yeoman.dist %>/scripts/{,*/}*.js',
|
||||
'<%= yeoman.dist %>/styles/{,*/}*.css',
|
||||
'<%= yeoman.dist %>/images/{,*/}*.{png,jpg,jpeg,gif,webp,svg}',
|
||||
'<%= yeoman.dist %>/fonts/{,*/}*.*'
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
useminPrepare: {
|
||||
html: '<%= yeoman.app %>/*.html',
|
||||
options: {
|
||||
dest: '<%= yeoman.dist %>'
|
||||
}
|
||||
},
|
||||
usemin: {
|
||||
html: ['<%= yeoman.dist %>/{,*/}*.html'],
|
||||
css: ['<%= yeoman.dist %>/styles/{,*/}*.css'],
|
||||
options: {
|
||||
dirs: ['<%= yeoman.dist %>']
|
||||
}
|
||||
},
|
||||
imagemin: {
|
||||
dist: {
|
||||
files: [{
|
||||
expand: true,
|
||||
cwd: '<%= yeoman.app %>/images',
|
||||
src: '{,*/}*.{png,jpg,jpeg}',
|
||||
dest: '<%= yeoman.dist %>/images'
|
||||
}]
|
||||
}
|
||||
},
|
||||
|
||||
cssmin: {
|
||||
dist: {
|
||||
files: {
|
||||
'<%= yeoman.dist %>/styles/main.css': [
|
||||
'.tmp/styles/{,*/}*.css',
|
||||
'<%= yeoman.app %>/styles/{,*/}*.css'
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
htmlmin: {
|
||||
dist: {
|
||||
options: {
|
||||
/*removeCommentsFromCDATA: true,
|
||||
// https://github.com/yeoman/grunt-usemin/issues/44
|
||||
//collapseWhitespace: true,
|
||||
collapseBooleanAttributes: true,
|
||||
removeAttributeQuotes: true,
|
||||
removeRedundantAttributes: true,
|
||||
useShortDoctype: true,
|
||||
removeEmptyAttributes: true,
|
||||
removeOptionalTags: true*/
|
||||
},
|
||||
files: [{
|
||||
expand: true,
|
||||
cwd: '<%= yeoman.app %>',
|
||||
src: '*.html',
|
||||
dest: '<%= yeoman.dist %>'
|
||||
}]
|
||||
}
|
||||
},
|
||||
copy: {
|
||||
dist: {
|
||||
files: [{
|
||||
expand: true,
|
||||
dot: true,
|
||||
cwd: '<%= yeoman.app %>',
|
||||
dest: '<%= yeoman.dist %>',
|
||||
src: [
|
||||
'*.{ico,png,txt}',
|
||||
'fonts/{,*/}*.*',
|
||||
'.htaccess',
|
||||
'index.txt',
|
||||
'404.txt',
|
||||
'images/{,*/}*.{webp,gif,svg}'
|
||||
]
|
||||
}]
|
||||
},
|
||||
server: {
|
||||
files: [{
|
||||
expand: true,
|
||||
dot: true,
|
||||
cwd: '<%= yeoman.app %>/bower_components/font-awesome/fonts/',
|
||||
dest: '<%= yeoman.app %>/fonts/font-awesome',
|
||||
src: ['*']
|
||||
}, {
|
||||
expand: true,
|
||||
dot: true,
|
||||
cwd: '<%= yeoman.app %>/bower_components/bootstrap/dist/fonts/',
|
||||
dest: '<%= yeoman.app %>/fonts/glyphicons',
|
||||
src: ['*']
|
||||
}]
|
||||
}
|
||||
},
|
||||
concurrent: {
|
||||
dist: [
|
||||
'less',
|
||||
'imagemin',
|
||||
'htmlmin'
|
||||
]
|
||||
}
|
||||
});
|
||||
|
||||
grunt.registerTask('serve', function (target) {
|
||||
if (target === 'dist') {
|
||||
return grunt.task.run(['build', 'connect:dist:keepalive']);
|
||||
}
|
||||
|
||||
grunt.task.run([
|
||||
'clean:server',
|
||||
'less',
|
||||
'includes:server',
|
||||
'copy:server',
|
||||
'connect:livereload',
|
||||
'watch'
|
||||
]);
|
||||
});
|
||||
|
||||
grunt.registerTask('server', function () {
|
||||
grunt.log.warn('The `server` task has been deprecated. Use `grunt serve` to start a server.');
|
||||
grunt.task.run(['serve']);
|
||||
});
|
||||
|
||||
grunt.registerTask('test', [
|
||||
'clean:server',
|
||||
'less',
|
||||
'copy:server',
|
||||
'connect:test',
|
||||
]);
|
||||
|
||||
grunt.registerTask('build', [
|
||||
'clean:dist',
|
||||
|
||||
'copy:server',
|
||||
'useminPrepare',
|
||||
'concurrent',
|
||||
'cssmin',
|
||||
'concat',
|
||||
'includes:build',
|
||||
'uglify',
|
||||
'copy',
|
||||
'usemin',
|
||||
|
||||
]);
|
||||
|
||||
grunt.registerTask('default', [
|
||||
'jshint',
|
||||
'test',
|
||||
'build'
|
||||
]);
|
||||
};
|
202
vendor/github.com/dutchcoders/transfer.sh-web/LICENSE
generated
vendored
Normal file
202
vendor/github.com/dutchcoders/transfer.sh-web/LICENSE
generated
vendored
Normal file
@@ -0,0 +1,202 @@
|
||||
|
||||
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.
|
5
vendor/github.com/dutchcoders/transfer.sh-web/bindata.go
generated
vendored
Normal file
5
vendor/github.com/dutchcoders/transfer.sh-web/bindata.go
generated
vendored
Normal file
@@ -0,0 +1,5 @@
|
||||
package web
|
||||
|
||||
//go:generate go-bindata -pkg web -o bindata_gen.go -ignore \.map\$ dist/...
|
||||
|
||||
var Prefix = "dist"
|
1313
vendor/github.com/dutchcoders/transfer.sh-web/bindata_gen.go
generated
vendored
Normal file
1313
vendor/github.com/dutchcoders/transfer.sh-web/bindata_gen.go
generated
vendored
Normal file
File diff suppressed because one or more lines are too long
25
vendor/github.com/dutchcoders/transfer.sh-web/bower.json
generated
vendored
Normal file
25
vendor/github.com/dutchcoders/transfer.sh-web/bower.json
generated
vendored
Normal file
@@ -0,0 +1,25 @@
|
||||
{
|
||||
"name": "transfer.sh",
|
||||
"version": "0.0.0",
|
||||
"moduleType": [
|
||||
"node"
|
||||
],
|
||||
"private": true,
|
||||
"ignore": [
|
||||
"**/.*",
|
||||
"node_modules",
|
||||
"bower_components",
|
||||
"transfersh-web/bower_components",
|
||||
"test",
|
||||
"tests"
|
||||
],
|
||||
"dependencies": {
|
||||
"bootstrap": "~3.0.0",
|
||||
"modernizr": "~2.6.2",
|
||||
"uri.js": "~1.14.1",
|
||||
"typed.js": "https://github.com/mattboldt/typed.js.git",
|
||||
"realistic-typewriter.js": "https://github.com/fardjad/realistic-typewriter.js.git",
|
||||
"animate.less": "*",
|
||||
"jquery-waypoints": "https://github.com/imakewebthings/jquery-waypoints.git#~2.0.5"
|
||||
}
|
||||
}
|
34
vendor/github.com/dutchcoders/transfer.sh-web/package.json
generated
vendored
Normal file
34
vendor/github.com/dutchcoders/transfer.sh-web/package.json
generated
vendored
Normal file
@@ -0,0 +1,34 @@
|
||||
{
|
||||
"name": "transfer.sh",
|
||||
"version": "0.0.0",
|
||||
"dependencies": {
|
||||
"wiredep": "^1.8.6"
|
||||
},
|
||||
"devDependencies": {
|
||||
"grunt": "~0.4.5",
|
||||
"grunt-concurrent": "~1.0.0",
|
||||
"grunt-contrib-clean": "~0.6.0",
|
||||
"grunt-contrib-concat": "~0.5.0",
|
||||
"grunt-contrib-connect": "~0.8.0",
|
||||
"grunt-contrib-copy": "~0.6.0",
|
||||
"grunt-contrib-cssmin": "~0.10.0",
|
||||
"grunt-contrib-htmlmin": "~0.3.0",
|
||||
"grunt-contrib-imagemin": "0.8.1",
|
||||
"grunt-contrib-jshint": "~0.10.0",
|
||||
"grunt-contrib-less": "~0.11.4",
|
||||
"grunt-contrib-uglify": "~0.6.0",
|
||||
"grunt-contrib-watch": "~0.6.1",
|
||||
"grunt-include-replace": "^2.0.0",
|
||||
"grunt-includes": "^0.4.5",
|
||||
"grunt-rev": "~0.1.0",
|
||||
"grunt-svgmin": "1.0.0",
|
||||
"grunt-usemin": "~2.4.0",
|
||||
"jshint-stylish": "~1.0.0",
|
||||
"load-grunt-tasks": "~0.6.0",
|
||||
"matchdep": "~0.3.0",
|
||||
"time-grunt": "~1.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=0.8.0"
|
||||
}
|
||||
}
|
23
vendor/github.com/elazarl/go-bindata-assetfs/LICENSE
generated
vendored
Normal file
23
vendor/github.com/elazarl/go-bindata-assetfs/LICENSE
generated
vendored
Normal file
@@ -0,0 +1,23 @@
|
||||
Copyright (c) 2014, Elazar Leibovich
|
||||
All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions are met:
|
||||
|
||||
* Redistributions of source code must retain the above copyright notice, this
|
||||
list of conditions and the following disclaimer.
|
||||
|
||||
* Redistributions in binary form must reproduce the above copyright notice,
|
||||
this list of conditions and the following disclaimer in the documentation
|
||||
and/or other materials provided with the distribution.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
||||
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
46
vendor/github.com/elazarl/go-bindata-assetfs/README.md
generated
vendored
Normal file
46
vendor/github.com/elazarl/go-bindata-assetfs/README.md
generated
vendored
Normal file
@@ -0,0 +1,46 @@
|
||||
# go-bindata-assetfs
|
||||
|
||||
Serve embedded files from [jteeuwen/go-bindata](https://github.com/jteeuwen/go-bindata) with `net/http`.
|
||||
|
||||
[GoDoc](http://godoc.org/github.com/elazarl/go-bindata-assetfs)
|
||||
|
||||
### Installation
|
||||
|
||||
Install with
|
||||
|
||||
$ go get github.com/jteeuwen/go-bindata/...
|
||||
$ go get github.com/elazarl/go-bindata-assetfs/...
|
||||
|
||||
### Creating embedded data
|
||||
|
||||
Usage is identical to [jteeuwen/go-bindata](https://github.com/jteeuwen/go-bindata) usage,
|
||||
instead of running `go-bindata` run `go-bindata-assetfs`.
|
||||
|
||||
The tool will create a `bindata_assetfs.go` file, which contains the embedded data.
|
||||
|
||||
A typical use case is
|
||||
|
||||
$ go-bindata-assetfs data/...
|
||||
|
||||
### Using assetFS in your code
|
||||
|
||||
The generated file provides an `assetFS()` function that returns a `http.Filesystem`
|
||||
wrapping the embedded files. What you usually want to do is:
|
||||
|
||||
http.Handle("/", http.FileServer(assetFS()))
|
||||
|
||||
This would run an HTTP server serving the embedded files.
|
||||
|
||||
## Without running binary tool
|
||||
|
||||
You can always just run the `go-bindata` tool, and then
|
||||
|
||||
use
|
||||
|
||||
import "github.com/elazarl/go-bindata-assetfs"
|
||||
...
|
||||
http.Handle("/",
|
||||
http.FileServer(
|
||||
&assetfs.AssetFS{Asset: Asset, AssetDir: AssetDir, AssetInfo: AssetInfo, Prefix: "data"}))
|
||||
|
||||
to serve files embedded from the `data` directory.
|
167
vendor/github.com/elazarl/go-bindata-assetfs/assetfs.go
generated
vendored
Normal file
167
vendor/github.com/elazarl/go-bindata-assetfs/assetfs.go
generated
vendored
Normal file
@@ -0,0 +1,167 @@
|
||||
package assetfs
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"errors"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"os"
|
||||
"path"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
var (
|
||||
defaultFileTimestamp = time.Now()
|
||||
)
|
||||
|
||||
// FakeFile implements os.FileInfo interface for a given path and size
|
||||
type FakeFile struct {
|
||||
// Path is the path of this file
|
||||
Path string
|
||||
// Dir marks of the path is a directory
|
||||
Dir bool
|
||||
// Len is the length of the fake file, zero if it is a directory
|
||||
Len int64
|
||||
// Timestamp is the ModTime of this file
|
||||
Timestamp time.Time
|
||||
}
|
||||
|
||||
func (f *FakeFile) Name() string {
|
||||
_, name := filepath.Split(f.Path)
|
||||
return name
|
||||
}
|
||||
|
||||
func (f *FakeFile) Mode() os.FileMode {
|
||||
mode := os.FileMode(0644)
|
||||
if f.Dir {
|
||||
return mode | os.ModeDir
|
||||
}
|
||||
return mode
|
||||
}
|
||||
|
||||
func (f *FakeFile) ModTime() time.Time {
|
||||
return f.Timestamp
|
||||
}
|
||||
|
||||
func (f *FakeFile) Size() int64 {
|
||||
return f.Len
|
||||
}
|
||||
|
||||
func (f *FakeFile) IsDir() bool {
|
||||
return f.Mode().IsDir()
|
||||
}
|
||||
|
||||
func (f *FakeFile) Sys() interface{} {
|
||||
return nil
|
||||
}
|
||||
|
||||
// AssetFile implements http.File interface for a no-directory file with content
|
||||
type AssetFile struct {
|
||||
*bytes.Reader
|
||||
io.Closer
|
||||
FakeFile
|
||||
}
|
||||
|
||||
func NewAssetFile(name string, content []byte, timestamp time.Time) *AssetFile {
|
||||
if timestamp.IsZero() {
|
||||
timestamp = defaultFileTimestamp
|
||||
}
|
||||
return &AssetFile{
|
||||
bytes.NewReader(content),
|
||||
ioutil.NopCloser(nil),
|
||||
FakeFile{name, false, int64(len(content)), timestamp}}
|
||||
}
|
||||
|
||||
func (f *AssetFile) Readdir(count int) ([]os.FileInfo, error) {
|
||||
return nil, errors.New("not a directory")
|
||||
}
|
||||
|
||||
func (f *AssetFile) Size() int64 {
|
||||
return f.FakeFile.Size()
|
||||
}
|
||||
|
||||
func (f *AssetFile) Stat() (os.FileInfo, error) {
|
||||
return f, nil
|
||||
}
|
||||
|
||||
// AssetDirectory implements http.File interface for a directory
|
||||
type AssetDirectory struct {
|
||||
AssetFile
|
||||
ChildrenRead int
|
||||
Children []os.FileInfo
|
||||
}
|
||||
|
||||
func NewAssetDirectory(name string, children []string, fs *AssetFS) *AssetDirectory {
|
||||
fileinfos := make([]os.FileInfo, 0, len(children))
|
||||
for _, child := range children {
|
||||
_, err := fs.AssetDir(filepath.Join(name, child))
|
||||
fileinfos = append(fileinfos, &FakeFile{child, err == nil, 0, time.Time{}})
|
||||
}
|
||||
return &AssetDirectory{
|
||||
AssetFile{
|
||||
bytes.NewReader(nil),
|
||||
ioutil.NopCloser(nil),
|
||||
FakeFile{name, true, 0, time.Time{}},
|
||||
},
|
||||
0,
|
||||
fileinfos}
|
||||
}
|
||||
|
||||
func (f *AssetDirectory) Readdir(count int) ([]os.FileInfo, error) {
|
||||
if count <= 0 {
|
||||
return f.Children, nil
|
||||
}
|
||||
if f.ChildrenRead+count > len(f.Children) {
|
||||
count = len(f.Children) - f.ChildrenRead
|
||||
}
|
||||
rv := f.Children[f.ChildrenRead : f.ChildrenRead+count]
|
||||
f.ChildrenRead += count
|
||||
return rv, nil
|
||||
}
|
||||
|
||||
func (f *AssetDirectory) Stat() (os.FileInfo, error) {
|
||||
return f, nil
|
||||
}
|
||||
|
||||
// AssetFS implements http.FileSystem, allowing
|
||||
// embedded files to be served from net/http package.
|
||||
type AssetFS struct {
|
||||
// Asset should return content of file in path if exists
|
||||
Asset func(path string) ([]byte, error)
|
||||
// AssetDir should return list of files in the path
|
||||
AssetDir func(path string) ([]string, error)
|
||||
// AssetInfo should return the info of file in path if exists
|
||||
AssetInfo func(path string) (os.FileInfo, error)
|
||||
// Prefix would be prepended to http requests
|
||||
Prefix string
|
||||
}
|
||||
|
||||
func (fs *AssetFS) Open(name string) (http.File, error) {
|
||||
name = path.Join(fs.Prefix, name)
|
||||
if len(name) > 0 && name[0] == '/' {
|
||||
name = name[1:]
|
||||
}
|
||||
if b, err := fs.Asset(name); err == nil {
|
||||
timestamp := defaultFileTimestamp
|
||||
if fs.AssetInfo != nil {
|
||||
if info, err := fs.AssetInfo(name); err == nil {
|
||||
timestamp = info.ModTime()
|
||||
}
|
||||
}
|
||||
return NewAssetFile(name, b, timestamp), nil
|
||||
}
|
||||
if children, err := fs.AssetDir(name); err == nil {
|
||||
return NewAssetDirectory(name, children, fs), nil
|
||||
} else {
|
||||
// If the error is not found, return an error that will
|
||||
// result in a 404 error. Otherwise the server returns
|
||||
// a 500 error for files not found.
|
||||
if strings.Contains(err.Error(), "not found") {
|
||||
return nil, os.ErrNotExist
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
}
|
13
vendor/github.com/elazarl/go-bindata-assetfs/doc.go
generated
vendored
Normal file
13
vendor/github.com/elazarl/go-bindata-assetfs/doc.go
generated
vendored
Normal file
@@ -0,0 +1,13 @@
|
||||
// assetfs allows packages to serve static content embedded
|
||||
// with the go-bindata tool with the standard net/http package.
|
||||
//
|
||||
// See https://github.com/jteeuwen/go-bindata for more information
|
||||
// about embedding binary data with go-bindata.
|
||||
//
|
||||
// Usage example, after running
|
||||
// $ go-bindata data/...
|
||||
// use:
|
||||
// http.Handle("/",
|
||||
// http.FileServer(
|
||||
// &assetfs.AssetFS{Asset: Asset, AssetDir: AssetDir, Prefix: "data"}))
|
||||
package assetfs
|
100
vendor/github.com/elazarl/go-bindata-assetfs/go-bindata-assetfs/main.go
generated
vendored
Normal file
100
vendor/github.com/elazarl/go-bindata-assetfs/go-bindata-assetfs/main.go
generated
vendored
Normal file
@@ -0,0 +1,100 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"bytes"
|
||||
"flag"
|
||||
"fmt"
|
||||
"os"
|
||||
"os/exec"
|
||||
"strings"
|
||||
)
|
||||
|
||||
const bindatafile = "bindata.go"
|
||||
|
||||
func isDebug(args []string) bool {
|
||||
flagset := flag.NewFlagSet("", flag.ContinueOnError)
|
||||
debug := flagset.Bool("debug", false, "")
|
||||
debugArgs := make([]string, 0)
|
||||
for _, arg := range args {
|
||||
if strings.HasPrefix(arg, "-debug") {
|
||||
debugArgs = append(debugArgs, arg)
|
||||
}
|
||||
}
|
||||
flagset.Parse(debugArgs)
|
||||
if debug == nil {
|
||||
return false
|
||||
}
|
||||
return *debug
|
||||
}
|
||||
|
||||
func main() {
|
||||
if _, err := exec.LookPath("go-bindata"); err != nil {
|
||||
fmt.Println("Cannot find go-bindata executable in path")
|
||||
fmt.Println("Maybe you need: go get github.com/elazarl/go-bindata-assetfs/...")
|
||||
os.Exit(1)
|
||||
}
|
||||
cmd := exec.Command("go-bindata", os.Args[1:]...)
|
||||
cmd.Stdin = os.Stdin
|
||||
cmd.Stdout = os.Stdout
|
||||
cmd.Stderr = os.Stderr
|
||||
if err := cmd.Run(); err != nil {
|
||||
os.Exit(1)
|
||||
}
|
||||
in, err := os.Open(bindatafile)
|
||||
if err != nil {
|
||||
fmt.Fprintln(os.Stderr, "Cannot read", bindatafile, err)
|
||||
return
|
||||
}
|
||||
out, err := os.Create("bindata_assetfs.go")
|
||||
if err != nil {
|
||||
fmt.Fprintln(os.Stderr, "Cannot write 'bindata_assetfs.go'", err)
|
||||
return
|
||||
}
|
||||
debug := isDebug(os.Args[1:])
|
||||
r := bufio.NewReader(in)
|
||||
done := false
|
||||
for line, isPrefix, err := r.ReadLine(); err == nil; line, isPrefix, err = r.ReadLine() {
|
||||
if !isPrefix {
|
||||
line = append(line, '\n')
|
||||
}
|
||||
if _, err := out.Write(line); err != nil {
|
||||
fmt.Fprintln(os.Stderr, "Cannot write to 'bindata_assetfs.go'", err)
|
||||
return
|
||||
}
|
||||
if !done && !isPrefix && bytes.HasPrefix(line, []byte("import (")) {
|
||||
if debug {
|
||||
fmt.Fprintln(out, "\t\"net/http\"")
|
||||
} else {
|
||||
fmt.Fprintln(out, "\t\"github.com/elazarl/go-bindata-assetfs\"")
|
||||
}
|
||||
done = true
|
||||
}
|
||||
}
|
||||
if debug {
|
||||
fmt.Fprintln(out, `
|
||||
func assetFS() http.FileSystem {
|
||||
for k := range _bintree.Children {
|
||||
return http.Dir(k)
|
||||
}
|
||||
panic("unreachable")
|
||||
}`)
|
||||
} else {
|
||||
fmt.Fprintln(out, `
|
||||
func assetFS() *assetfs.AssetFS {
|
||||
assetInfo := func(path string) (os.FileInfo, error) {
|
||||
return os.Stat(path)
|
||||
}
|
||||
for k := range _bintree.Children {
|
||||
return &assetfs.AssetFS{Asset: Asset, AssetDir: AssetDir, AssetInfo: assetInfo, Prefix: k}
|
||||
}
|
||||
panic("unreachable")
|
||||
}`)
|
||||
}
|
||||
// Close files BEFORE remove calls (don't use defer).
|
||||
in.Close()
|
||||
out.Close()
|
||||
if err := os.Remove(bindatafile); err != nil {
|
||||
fmt.Fprintln(os.Stderr, "Cannot remove", bindatafile, err)
|
||||
}
|
||||
}
|
5
vendor/github.com/fatih/color/.travis.yml
generated
vendored
Normal file
5
vendor/github.com/fatih/color/.travis.yml
generated
vendored
Normal file
@@ -0,0 +1,5 @@
|
||||
language: go
|
||||
go:
|
||||
- 1.6
|
||||
- tip
|
||||
|
20
vendor/github.com/fatih/color/LICENSE.md
generated
vendored
Normal file
20
vendor/github.com/fatih/color/LICENSE.md
generated
vendored
Normal file
@@ -0,0 +1,20 @@
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2013 Fatih Arslan
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||
this software and associated documentation files (the "Software"), to deal in
|
||||
the Software without restriction, including without limitation the rights to
|
||||
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
|
||||
the Software, and to permit persons to whom the Software is furnished to do so,
|
||||
subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
||||
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
||||
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
|
||||
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
177
vendor/github.com/fatih/color/README.md
generated
vendored
Normal file
177
vendor/github.com/fatih/color/README.md
generated
vendored
Normal file
@@ -0,0 +1,177 @@
|
||||
# Color [](http://godoc.org/github.com/fatih/color) [](https://travis-ci.org/fatih/color)
|
||||
|
||||
|
||||
|
||||
Color lets you use colorized outputs in terms of [ANSI Escape
|
||||
Codes](http://en.wikipedia.org/wiki/ANSI_escape_code#Colors) in Go (Golang). It
|
||||
has support for Windows too! The API can be used in several ways, pick one that
|
||||
suits you.
|
||||
|
||||
|
||||

|
||||
|
||||
|
||||
## Install
|
||||
|
||||
```bash
|
||||
go get github.com/fatih/color
|
||||
```
|
||||
|
||||
Note that the `vendor` folder is here for stability. Remove the folder if you
|
||||
already have the dependencies in your GOPATH.
|
||||
|
||||
## Examples
|
||||
|
||||
### Standard colors
|
||||
|
||||
```go
|
||||
// Print with default helper functions
|
||||
color.Cyan("Prints text in cyan.")
|
||||
|
||||
// A newline will be appended automatically
|
||||
color.Blue("Prints %s in blue.", "text")
|
||||
|
||||
// These are using the default foreground colors
|
||||
color.Red("We have red")
|
||||
color.Magenta("And many others ..")
|
||||
|
||||
```
|
||||
|
||||
### Mix and reuse colors
|
||||
|
||||
```go
|
||||
// Create a new color object
|
||||
c := color.New(color.FgCyan).Add(color.Underline)
|
||||
c.Println("Prints cyan text with an underline.")
|
||||
|
||||
// Or just add them to New()
|
||||
d := color.New(color.FgCyan, color.Bold)
|
||||
d.Printf("This prints bold cyan %s\n", "too!.")
|
||||
|
||||
// Mix up foreground and background colors, create new mixes!
|
||||
red := color.New(color.FgRed)
|
||||
|
||||
boldRed := red.Add(color.Bold)
|
||||
boldRed.Println("This will print text in bold red.")
|
||||
|
||||
whiteBackground := red.Add(color.BgWhite)
|
||||
whiteBackground.Println("Red text with white background.")
|
||||
```
|
||||
|
||||
### Use your own output (io.Writer)
|
||||
|
||||
```go
|
||||
// Use your own io.Writer output
|
||||
color.New(color.FgBlue).Fprintln(myWriter, "blue color!")
|
||||
|
||||
blue := color.New(color.FgBlue)
|
||||
blue.Fprint(writer, "This will print text in blue.")
|
||||
```
|
||||
|
||||
### Custom print functions (PrintFunc)
|
||||
|
||||
```go
|
||||
// Create a custom print function for convenience
|
||||
red := color.New(color.FgRed).PrintfFunc()
|
||||
red("Warning")
|
||||
red("Error: %s", err)
|
||||
|
||||
// Mix up multiple attributes
|
||||
notice := color.New(color.Bold, color.FgGreen).PrintlnFunc()
|
||||
notice("Don't forget this...")
|
||||
```
|
||||
|
||||
### Custom fprint functions (FprintFunc)
|
||||
|
||||
```go
|
||||
blue := color.New(FgBlue).FprintfFunc()
|
||||
blue(myWriter, "important notice: %s", stars)
|
||||
|
||||
// Mix up with multiple attributes
|
||||
success := color.New(color.Bold, color.FgGreen).FprintlnFunc()
|
||||
success(myWriter, "Don't forget this...")
|
||||
```
|
||||
|
||||
### Insert into noncolor strings (SprintFunc)
|
||||
|
||||
```go
|
||||
// Create SprintXxx functions to mix strings with other non-colorized strings:
|
||||
yellow := color.New(color.FgYellow).SprintFunc()
|
||||
red := color.New(color.FgRed).SprintFunc()
|
||||
fmt.Printf("This is a %s and this is %s.\n", yellow("warning"), red("error"))
|
||||
|
||||
info := color.New(color.FgWhite, color.BgGreen).SprintFunc()
|
||||
fmt.Printf("This %s rocks!\n", info("package"))
|
||||
|
||||
// Use helper functions
|
||||
fmt.Println("This", color.RedString("warning"), "should be not neglected.")
|
||||
fmt.Printf("%v %v\n", color.GreenString("Info:"), "an important message.")
|
||||
|
||||
// Windows supported too! Just don't forget to change the output to color.Output
|
||||
fmt.Fprintf(color.Output, "Windows support: %s", color.GreenString("PASS"))
|
||||
```
|
||||
|
||||
### Plug into existing code
|
||||
|
||||
```go
|
||||
// Use handy standard colors
|
||||
color.Set(color.FgYellow)
|
||||
|
||||
fmt.Println("Existing text will now be in yellow")
|
||||
fmt.Printf("This one %s\n", "too")
|
||||
|
||||
color.Unset() // Don't forget to unset
|
||||
|
||||
// You can mix up parameters
|
||||
color.Set(color.FgMagenta, color.Bold)
|
||||
defer color.Unset() // Use it in your function
|
||||
|
||||
fmt.Println("All text will now be bold magenta.")
|
||||
```
|
||||
|
||||
### Disable color
|
||||
|
||||
There might be a case where you want to disable color output (for example to
|
||||
pipe the standard output of your app to somewhere else). `Color` has support to
|
||||
disable colors both globally and for single color definition. For example
|
||||
suppose you have a CLI app and a `--no-color` bool flag. You can easily disable
|
||||
the color output with:
|
||||
|
||||
```go
|
||||
|
||||
var flagNoColor = flag.Bool("no-color", false, "Disable color output")
|
||||
|
||||
if *flagNoColor {
|
||||
color.NoColor = true // disables colorized output
|
||||
}
|
||||
```
|
||||
|
||||
It also has support for single color definitions (local). You can
|
||||
disable/enable color output on the fly:
|
||||
|
||||
```go
|
||||
c := color.New(color.FgCyan)
|
||||
c.Println("Prints cyan text")
|
||||
|
||||
c.DisableColor()
|
||||
c.Println("This is printed without any color")
|
||||
|
||||
c.EnableColor()
|
||||
c.Println("This prints again cyan...")
|
||||
```
|
||||
|
||||
## Todo
|
||||
|
||||
* Save/Return previous values
|
||||
* Evaluate fmt.Formatter interface
|
||||
|
||||
|
||||
## Credits
|
||||
|
||||
* [Fatih Arslan](https://github.com/fatih)
|
||||
* Windows support via @mattn: [colorable](https://github.com/mattn/go-colorable)
|
||||
|
||||
## License
|
||||
|
||||
The MIT License (MIT) - see [`LICENSE.md`](https://github.com/fatih/color/blob/master/LICENSE.md) for more details
|
||||
|
526
vendor/github.com/fatih/color/color.go
generated
vendored
Normal file
526
vendor/github.com/fatih/color/color.go
generated
vendored
Normal file
@@ -0,0 +1,526 @@
|
||||
package color
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
"github.com/mattn/go-colorable"
|
||||
"github.com/mattn/go-isatty"
|
||||
)
|
||||
|
||||
var (
|
||||
// NoColor defines if the output is colorized or not. It's dynamically set to
|
||||
// false or true based on the stdout's file descriptor referring to a terminal
|
||||
// or not. This is a global option and affects all colors. For more control
|
||||
// over each color block use the methods DisableColor() individually.
|
||||
NoColor = os.Getenv("TERM") == "dumb" ||
|
||||
(!isatty.IsTerminal(os.Stdout.Fd()))
|
||||
|
||||
// Output defines the standard output of the print functions. By default
|
||||
// os.Stdout is used.
|
||||
Output = colorable.NewColorableStdout()
|
||||
|
||||
// colorsCache is used to reduce the count of created Color objects and
|
||||
// allows to reuse already created objects with required Attribute.
|
||||
colorsCache = make(map[Attribute]*Color)
|
||||
colorsCacheMu sync.Mutex // protects colorsCache
|
||||
)
|
||||
|
||||
// Color defines a custom color object which is defined by SGR parameters.
|
||||
type Color struct {
|
||||
params []Attribute
|
||||
noColor *bool
|
||||
}
|
||||
|
||||
// Attribute defines a single SGR Code
|
||||
type Attribute int
|
||||
|
||||
const escape = "\x1b"
|
||||
|
||||
// Base attributes
|
||||
const (
|
||||
Reset Attribute = iota
|
||||
Bold
|
||||
Faint
|
||||
Italic
|
||||
Underline
|
||||
BlinkSlow
|
||||
BlinkRapid
|
||||
ReverseVideo
|
||||
Concealed
|
||||
CrossedOut
|
||||
)
|
||||
|
||||
// Foreground text colors
|
||||
const (
|
||||
FgBlack Attribute = iota + 30
|
||||
FgRed
|
||||
FgGreen
|
||||
FgYellow
|
||||
FgBlue
|
||||
FgMagenta
|
||||
FgCyan
|
||||
FgWhite
|
||||
)
|
||||
|
||||
// Foreground Hi-Intensity text colors
|
||||
const (
|
||||
FgHiBlack Attribute = iota + 90
|
||||
FgHiRed
|
||||
FgHiGreen
|
||||
FgHiYellow
|
||||
FgHiBlue
|
||||
FgHiMagenta
|
||||
FgHiCyan
|
||||
FgHiWhite
|
||||
)
|
||||
|
||||
// Background text colors
|
||||
const (
|
||||
BgBlack Attribute = iota + 40
|
||||
BgRed
|
||||
BgGreen
|
||||
BgYellow
|
||||
BgBlue
|
||||
BgMagenta
|
||||
BgCyan
|
||||
BgWhite
|
||||
)
|
||||
|
||||
// Background Hi-Intensity text colors
|
||||
const (
|
||||
BgHiBlack Attribute = iota + 100
|
||||
BgHiRed
|
||||
BgHiGreen
|
||||
BgHiYellow
|
||||
BgHiBlue
|
||||
BgHiMagenta
|
||||
BgHiCyan
|
||||
BgHiWhite
|
||||
)
|
||||
|
||||
// New returns a newly created color object.
|
||||
func New(value ...Attribute) *Color {
|
||||
c := &Color{params: make([]Attribute, 0)}
|
||||
c.Add(value...)
|
||||
return c
|
||||
}
|
||||
|
||||
// Set sets the given parameters immediately. It will change the color of
|
||||
// output with the given SGR parameters until color.Unset() is called.
|
||||
func Set(p ...Attribute) *Color {
|
||||
c := New(p...)
|
||||
c.Set()
|
||||
return c
|
||||
}
|
||||
|
||||
// Unset resets all escape attributes and clears the output. Usually should
|
||||
// be called after Set().
|
||||
func Unset() {
|
||||
if NoColor {
|
||||
return
|
||||
}
|
||||
|
||||
fmt.Fprintf(Output, "%s[%dm", escape, Reset)
|
||||
}
|
||||
|
||||
// Set sets the SGR sequence.
|
||||
func (c *Color) Set() *Color {
|
||||
if c.isNoColorSet() {
|
||||
return c
|
||||
}
|
||||
|
||||
fmt.Fprintf(Output, c.format())
|
||||
return c
|
||||
}
|
||||
|
||||
func (c *Color) unset() {
|
||||
if c.isNoColorSet() {
|
||||
return
|
||||
}
|
||||
|
||||
Unset()
|
||||
}
|
||||
|
||||
func (c *Color) setWriter(w io.Writer) *Color {
|
||||
if c.isNoColorSet() {
|
||||
return c
|
||||
}
|
||||
|
||||
fmt.Fprintf(w, c.format())
|
||||
return c
|
||||
}
|
||||
|
||||
func (c *Color) unsetWriter(w io.Writer) {
|
||||
if c.isNoColorSet() {
|
||||
return
|
||||
}
|
||||
|
||||
if NoColor {
|
||||
return
|
||||
}
|
||||
|
||||
fmt.Fprintf(w, "%s[%dm", escape, Reset)
|
||||
}
|
||||
|
||||
// Add is used to chain SGR parameters. Use as many as parameters to combine
|
||||
// and create custom color objects. Example: Add(color.FgRed, color.Underline).
|
||||
func (c *Color) Add(value ...Attribute) *Color {
|
||||
c.params = append(c.params, value...)
|
||||
return c
|
||||
}
|
||||
|
||||
func (c *Color) prepend(value Attribute) {
|
||||
c.params = append(c.params, 0)
|
||||
copy(c.params[1:], c.params[0:])
|
||||
c.params[0] = value
|
||||
}
|
||||
|
||||
// Fprint formats using the default formats for its operands and writes to w.
|
||||
// Spaces are added between operands when neither is a string.
|
||||
// It returns the number of bytes written and any write error encountered.
|
||||
// On Windows, users should wrap w with colorable.NewColorable() if w is of
|
||||
// type *os.File.
|
||||
func (c *Color) Fprint(w io.Writer, a ...interface{}) (n int, err error) {
|
||||
c.setWriter(w)
|
||||
defer c.unsetWriter(w)
|
||||
|
||||
return fmt.Fprint(w, a...)
|
||||
}
|
||||
|
||||
// Print formats using the default formats for its operands and writes to
|
||||
// standard output. Spaces are added between operands when neither is a
|
||||
// string. It returns the number of bytes written and any write error
|
||||
// encountered. This is the standard fmt.Print() method wrapped with the given
|
||||
// color.
|
||||
func (c *Color) Print(a ...interface{}) (n int, err error) {
|
||||
c.Set()
|
||||
defer c.unset()
|
||||
|
||||
return fmt.Fprint(Output, a...)
|
||||
}
|
||||
|
||||
// Fprintf formats according to a format specifier and writes to w.
|
||||
// It returns the number of bytes written and any write error encountered.
|
||||
// On Windows, users should wrap w with colorable.NewColorable() if w is of
|
||||
// type *os.File.
|
||||
func (c *Color) Fprintf(w io.Writer, format string, a ...interface{}) (n int, err error) {
|
||||
c.setWriter(w)
|
||||
defer c.unsetWriter(w)
|
||||
|
||||
return fmt.Fprintf(w, format, a...)
|
||||
}
|
||||
|
||||
// Printf formats according to a format specifier and writes to standard output.
|
||||
// It returns the number of bytes written and any write error encountered.
|
||||
// This is the standard fmt.Printf() method wrapped with the given color.
|
||||
func (c *Color) Printf(format string, a ...interface{}) (n int, err error) {
|
||||
c.Set()
|
||||
defer c.unset()
|
||||
|
||||
return fmt.Fprintf(Output, format, a...)
|
||||
}
|
||||
|
||||
// Fprintln formats using the default formats for its operands and writes to w.
|
||||
// Spaces are always added between operands and a newline is appended.
|
||||
// On Windows, users should wrap w with colorable.NewColorable() if w is of
|
||||
// type *os.File.
|
||||
func (c *Color) Fprintln(w io.Writer, a ...interface{}) (n int, err error) {
|
||||
c.setWriter(w)
|
||||
defer c.unsetWriter(w)
|
||||
|
||||
return fmt.Fprintln(w, a...)
|
||||
}
|
||||
|
||||
// Println formats using the default formats for its operands and writes to
|
||||
// standard output. Spaces are always added between operands and a newline is
|
||||
// appended. It returns the number of bytes written and any write error
|
||||
// encountered. This is the standard fmt.Print() method wrapped with the given
|
||||
// color.
|
||||
func (c *Color) Println(a ...interface{}) (n int, err error) {
|
||||
c.Set()
|
||||
defer c.unset()
|
||||
|
||||
return fmt.Fprintln(Output, a...)
|
||||
}
|
||||
|
||||
// Sprint is just like Print, but returns a string instead of printing it.
|
||||
func (c *Color) Sprint(a ...interface{}) string {
|
||||
return c.wrap(fmt.Sprint(a...))
|
||||
}
|
||||
|
||||
// Sprintln is just like Println, but returns a string instead of printing it.
|
||||
func (c *Color) Sprintln(a ...interface{}) string {
|
||||
return c.wrap(fmt.Sprintln(a...))
|
||||
}
|
||||
|
||||
// Sprintf is just like Printf, but returns a string instead of printing it.
|
||||
func (c *Color) Sprintf(format string, a ...interface{}) string {
|
||||
return c.wrap(fmt.Sprintf(format, a...))
|
||||
}
|
||||
|
||||
// FprintFunc returns a new function that prints the passed arguments as
|
||||
// colorized with color.Fprint().
|
||||
func (c *Color) FprintFunc() func(w io.Writer, a ...interface{}) {
|
||||
return func(w io.Writer, a ...interface{}) {
|
||||
c.Fprint(w, a...)
|
||||
}
|
||||
}
|
||||
|
||||
// PrintFunc returns a new function that prints the passed arguments as
|
||||
// colorized with color.Print().
|
||||
func (c *Color) PrintFunc() func(a ...interface{}) {
|
||||
return func(a ...interface{}) {
|
||||
c.Print(a...)
|
||||
}
|
||||
}
|
||||
|
||||
// FprintfFunc returns a new function that prints the passed arguments as
|
||||
// colorized with color.Fprintf().
|
||||
func (c *Color) FprintfFunc() func(w io.Writer, format string, a ...interface{}) {
|
||||
return func(w io.Writer, format string, a ...interface{}) {
|
||||
c.Fprintf(w, format, a...)
|
||||
}
|
||||
}
|
||||
|
||||
// PrintfFunc returns a new function that prints the passed arguments as
|
||||
// colorized with color.Printf().
|
||||
func (c *Color) PrintfFunc() func(format string, a ...interface{}) {
|
||||
return func(format string, a ...interface{}) {
|
||||
c.Printf(format, a...)
|
||||
}
|
||||
}
|
||||
|
||||
// FprintlnFunc returns a new function that prints the passed arguments as
|
||||
// colorized with color.Fprintln().
|
||||
func (c *Color) FprintlnFunc() func(w io.Writer, a ...interface{}) {
|
||||
return func(w io.Writer, a ...interface{}) {
|
||||
c.Fprintln(w, a...)
|
||||
}
|
||||
}
|
||||
|
||||
// PrintlnFunc returns a new function that prints the passed arguments as
|
||||
// colorized with color.Println().
|
||||
func (c *Color) PrintlnFunc() func(a ...interface{}) {
|
||||
return func(a ...interface{}) {
|
||||
c.Println(a...)
|
||||
}
|
||||
}
|
||||
|
||||
// SprintFunc returns a new function that returns colorized strings for the
|
||||
// given arguments with fmt.Sprint(). Useful to put into or mix into other
|
||||
// string. Windows users should use this in conjunction with color.Output, example:
|
||||
//
|
||||
// put := New(FgYellow).SprintFunc()
|
||||
// fmt.Fprintf(color.Output, "This is a %s", put("warning"))
|
||||
func (c *Color) SprintFunc() func(a ...interface{}) string {
|
||||
return func(a ...interface{}) string {
|
||||
return c.wrap(fmt.Sprint(a...))
|
||||
}
|
||||
}
|
||||
|
||||
// SprintfFunc returns a new function that returns colorized strings for the
|
||||
// given arguments with fmt.Sprintf(). Useful to put into or mix into other
|
||||
// string. Windows users should use this in conjunction with color.Output.
|
||||
func (c *Color) SprintfFunc() func(format string, a ...interface{}) string {
|
||||
return func(format string, a ...interface{}) string {
|
||||
return c.wrap(fmt.Sprintf(format, a...))
|
||||
}
|
||||
}
|
||||
|
||||
// SprintlnFunc returns a new function that returns colorized strings for the
|
||||
// given arguments with fmt.Sprintln(). Useful to put into or mix into other
|
||||
// string. Windows users should use this in conjunction with color.Output.
|
||||
func (c *Color) SprintlnFunc() func(a ...interface{}) string {
|
||||
return func(a ...interface{}) string {
|
||||
return c.wrap(fmt.Sprintln(a...))
|
||||
}
|
||||
}
|
||||
|
||||
// sequence returns a formated SGR sequence to be plugged into a "\x1b[...m"
|
||||
// an example output might be: "1;36" -> bold cyan
|
||||
func (c *Color) sequence() string {
|
||||
format := make([]string, len(c.params))
|
||||
for i, v := range c.params {
|
||||
format[i] = strconv.Itoa(int(v))
|
||||
}
|
||||
|
||||
return strings.Join(format, ";")
|
||||
}
|
||||
|
||||
// wrap wraps the s string with the colors attributes. The string is ready to
|
||||
// be printed.
|
||||
func (c *Color) wrap(s string) string {
|
||||
if c.isNoColorSet() {
|
||||
return s
|
||||
}
|
||||
|
||||
return c.format() + s + c.unformat()
|
||||
}
|
||||
|
||||
func (c *Color) format() string {
|
||||
return fmt.Sprintf("%s[%sm", escape, c.sequence())
|
||||
}
|
||||
|
||||
func (c *Color) unformat() string {
|
||||
return fmt.Sprintf("%s[%dm", escape, Reset)
|
||||
}
|
||||
|
||||
// DisableColor disables the color output. Useful to not change any existing
|
||||
// code and still being able to output. Can be used for flags like
|
||||
// "--no-color". To enable back use EnableColor() method.
|
||||
func (c *Color) DisableColor() {
|
||||
c.noColor = boolPtr(true)
|
||||
}
|
||||
|
||||
// EnableColor enables the color output. Use it in conjunction with
|
||||
// DisableColor(). Otherwise this method has no side effects.
|
||||
func (c *Color) EnableColor() {
|
||||
c.noColor = boolPtr(false)
|
||||
}
|
||||
|
||||
func (c *Color) isNoColorSet() bool {
|
||||
// check first if we have user setted action
|
||||
if c.noColor != nil {
|
||||
return *c.noColor
|
||||
}
|
||||
|
||||
// if not return the global option, which is disabled by default
|
||||
return NoColor
|
||||
}
|
||||
|
||||
// Equals returns a boolean value indicating whether two colors are equal.
|
||||
func (c *Color) Equals(c2 *Color) bool {
|
||||
if len(c.params) != len(c2.params) {
|
||||
return false
|
||||
}
|
||||
|
||||
for _, attr := range c.params {
|
||||
if !c2.attrExists(attr) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
func (c *Color) attrExists(a Attribute) bool {
|
||||
for _, attr := range c.params {
|
||||
if attr == a {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
func boolPtr(v bool) *bool {
|
||||
return &v
|
||||
}
|
||||
|
||||
func getCachedColor(p Attribute) *Color {
|
||||
colorsCacheMu.Lock()
|
||||
defer colorsCacheMu.Unlock()
|
||||
|
||||
c, ok := colorsCache[p]
|
||||
if !ok {
|
||||
c = New(p)
|
||||
colorsCache[p] = c
|
||||
}
|
||||
|
||||
return c
|
||||
}
|
||||
|
||||
func colorPrint(format string, p Attribute, a ...interface{}) {
|
||||
c := getCachedColor(p)
|
||||
|
||||
if !strings.HasSuffix(format, "\n") {
|
||||
format += "\n"
|
||||
}
|
||||
|
||||
if len(a) == 0 {
|
||||
c.Print(format)
|
||||
} else {
|
||||
c.Printf(format, a...)
|
||||
}
|
||||
}
|
||||
|
||||
func colorString(format string, p Attribute, a ...interface{}) string {
|
||||
c := getCachedColor(p)
|
||||
|
||||
if len(a) == 0 {
|
||||
return c.SprintFunc()(format)
|
||||
}
|
||||
|
||||
return c.SprintfFunc()(format, a...)
|
||||
}
|
||||
|
||||
// Black is an convenient helper function to print with black foreground. A
|
||||
// newline is appended to format by default.
|
||||
func Black(format string, a ...interface{}) { colorPrint(format, FgBlack, a...) }
|
||||
|
||||
// Red is an convenient helper function to print with red foreground. A
|
||||
// newline is appended to format by default.
|
||||
func Red(format string, a ...interface{}) { colorPrint(format, FgRed, a...) }
|
||||
|
||||
// Green is an convenient helper function to print with green foreground. A
|
||||
// newline is appended to format by default.
|
||||
func Green(format string, a ...interface{}) { colorPrint(format, FgGreen, a...) }
|
||||
|
||||
// Yellow is an convenient helper function to print with yellow foreground.
|
||||
// A newline is appended to format by default.
|
||||
func Yellow(format string, a ...interface{}) { colorPrint(format, FgYellow, a...) }
|
||||
|
||||
// Blue is an convenient helper function to print with blue foreground. A
|
||||
// newline is appended to format by default.
|
||||
func Blue(format string, a ...interface{}) { colorPrint(format, FgBlue, a...) }
|
||||
|
||||
// Magenta is an convenient helper function to print with magenta foreground.
|
||||
// A newline is appended to format by default.
|
||||
func Magenta(format string, a ...interface{}) { colorPrint(format, FgMagenta, a...) }
|
||||
|
||||
// Cyan is an convenient helper function to print with cyan foreground. A
|
||||
// newline is appended to format by default.
|
||||
func Cyan(format string, a ...interface{}) { colorPrint(format, FgCyan, a...) }
|
||||
|
||||
// White is an convenient helper function to print with white foreground. A
|
||||
// newline is appended to format by default.
|
||||
func White(format string, a ...interface{}) { colorPrint(format, FgWhite, a...) }
|
||||
|
||||
// BlackString is an convenient helper function to return a string with black
|
||||
// foreground.
|
||||
func BlackString(format string, a ...interface{}) string { return colorString(format, FgBlack, a...) }
|
||||
|
||||
// RedString is an convenient helper function to return a string with red
|
||||
// foreground.
|
||||
func RedString(format string, a ...interface{}) string { return colorString(format, FgRed, a...) }
|
||||
|
||||
// GreenString is an convenient helper function to return a string with green
|
||||
// foreground.
|
||||
func GreenString(format string, a ...interface{}) string { return colorString(format, FgGreen, a...) }
|
||||
|
||||
// YellowString is an convenient helper function to return a string with yellow
|
||||
// foreground.
|
||||
func YellowString(format string, a ...interface{}) string { return colorString(format, FgYellow, a...) }
|
||||
|
||||
// BlueString is an convenient helper function to return a string with blue
|
||||
// foreground.
|
||||
func BlueString(format string, a ...interface{}) string { return colorString(format, FgBlue, a...) }
|
||||
|
||||
// MagentaString is an convenient helper function to return a string with magenta
|
||||
// foreground.
|
||||
func MagentaString(format string, a ...interface{}) string {
|
||||
return colorString(format, FgMagenta, a...)
|
||||
}
|
||||
|
||||
// CyanString is an convenient helper function to return a string with cyan
|
||||
// foreground.
|
||||
func CyanString(format string, a ...interface{}) string { return colorString(format, FgCyan, a...) }
|
||||
|
||||
// WhiteString is an convenient helper function to return a string with white
|
||||
// foreground.
|
||||
func WhiteString(format string, a ...interface{}) string { return colorString(format, FgWhite, a...) }
|
294
vendor/github.com/fatih/color/color_test.go
generated
vendored
Normal file
294
vendor/github.com/fatih/color/color_test.go
generated
vendored
Normal file
@@ -0,0 +1,294 @@
|
||||
package color
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"os"
|
||||
"testing"
|
||||
|
||||
"github.com/mattn/go-colorable"
|
||||
)
|
||||
|
||||
// Testing colors is kinda different. First we test for given colors and their
|
||||
// escaped formatted results. Next we create some visual tests to be tested.
|
||||
// Each visual test includes the color name to be compared.
|
||||
func TestColor(t *testing.T) {
|
||||
rb := new(bytes.Buffer)
|
||||
Output = rb
|
||||
|
||||
NoColor = false
|
||||
|
||||
testColors := []struct {
|
||||
text string
|
||||
code Attribute
|
||||
}{
|
||||
{text: "black", code: FgBlack},
|
||||
{text: "red", code: FgRed},
|
||||
{text: "green", code: FgGreen},
|
||||
{text: "yellow", code: FgYellow},
|
||||
{text: "blue", code: FgBlue},
|
||||
{text: "magent", code: FgMagenta},
|
||||
{text: "cyan", code: FgCyan},
|
||||
{text: "white", code: FgWhite},
|
||||
{text: "hblack", code: FgHiBlack},
|
||||
{text: "hred", code: FgHiRed},
|
||||
{text: "hgreen", code: FgHiGreen},
|
||||
{text: "hyellow", code: FgHiYellow},
|
||||
{text: "hblue", code: FgHiBlue},
|
||||
{text: "hmagent", code: FgHiMagenta},
|
||||
{text: "hcyan", code: FgHiCyan},
|
||||
{text: "hwhite", code: FgHiWhite},
|
||||
}
|
||||
|
||||
for _, c := range testColors {
|
||||
New(c.code).Print(c.text)
|
||||
|
||||
line, _ := rb.ReadString('\n')
|
||||
scannedLine := fmt.Sprintf("%q", line)
|
||||
colored := fmt.Sprintf("\x1b[%dm%s\x1b[0m", c.code, c.text)
|
||||
escapedForm := fmt.Sprintf("%q", colored)
|
||||
|
||||
fmt.Printf("%s\t: %s\n", c.text, line)
|
||||
|
||||
if scannedLine != escapedForm {
|
||||
t.Errorf("Expecting %s, got '%s'\n", escapedForm, scannedLine)
|
||||
}
|
||||
}
|
||||
|
||||
for _, c := range testColors {
|
||||
line := New(c.code).Sprintf("%s", c.text)
|
||||
scannedLine := fmt.Sprintf("%q", line)
|
||||
colored := fmt.Sprintf("\x1b[%dm%s\x1b[0m", c.code, c.text)
|
||||
escapedForm := fmt.Sprintf("%q", colored)
|
||||
|
||||
fmt.Printf("%s\t: %s\n", c.text, line)
|
||||
|
||||
if scannedLine != escapedForm {
|
||||
t.Errorf("Expecting %s, got '%s'\n", escapedForm, scannedLine)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestColorEquals(t *testing.T) {
|
||||
fgblack1 := New(FgBlack)
|
||||
fgblack2 := New(FgBlack)
|
||||
bgblack := New(BgBlack)
|
||||
fgbgblack := New(FgBlack, BgBlack)
|
||||
fgblackbgred := New(FgBlack, BgRed)
|
||||
fgred := New(FgRed)
|
||||
bgred := New(BgRed)
|
||||
|
||||
if !fgblack1.Equals(fgblack2) {
|
||||
t.Error("Two black colors are not equal")
|
||||
}
|
||||
|
||||
if fgblack1.Equals(bgblack) {
|
||||
t.Error("Fg and bg black colors are equal")
|
||||
}
|
||||
|
||||
if fgblack1.Equals(fgbgblack) {
|
||||
t.Error("Fg black equals fg/bg black color")
|
||||
}
|
||||
|
||||
if fgblack1.Equals(fgred) {
|
||||
t.Error("Fg black equals Fg red")
|
||||
}
|
||||
|
||||
if fgblack1.Equals(bgred) {
|
||||
t.Error("Fg black equals Bg red")
|
||||
}
|
||||
|
||||
if fgblack1.Equals(fgblackbgred) {
|
||||
t.Error("Fg black equals fg black bg red")
|
||||
}
|
||||
}
|
||||
|
||||
func TestNoColor(t *testing.T) {
|
||||
rb := new(bytes.Buffer)
|
||||
Output = rb
|
||||
|
||||
testColors := []struct {
|
||||
text string
|
||||
code Attribute
|
||||
}{
|
||||
{text: "black", code: FgBlack},
|
||||
{text: "red", code: FgRed},
|
||||
{text: "green", code: FgGreen},
|
||||
{text: "yellow", code: FgYellow},
|
||||
{text: "blue", code: FgBlue},
|
||||
{text: "magent", code: FgMagenta},
|
||||
{text: "cyan", code: FgCyan},
|
||||
{text: "white", code: FgWhite},
|
||||
{text: "hblack", code: FgHiBlack},
|
||||
{text: "hred", code: FgHiRed},
|
||||
{text: "hgreen", code: FgHiGreen},
|
||||
{text: "hyellow", code: FgHiYellow},
|
||||
{text: "hblue", code: FgHiBlue},
|
||||
{text: "hmagent", code: FgHiMagenta},
|
||||
{text: "hcyan", code: FgHiCyan},
|
||||
{text: "hwhite", code: FgHiWhite},
|
||||
}
|
||||
|
||||
for _, c := range testColors {
|
||||
p := New(c.code)
|
||||
p.DisableColor()
|
||||
p.Print(c.text)
|
||||
|
||||
line, _ := rb.ReadString('\n')
|
||||
if line != c.text {
|
||||
t.Errorf("Expecting %s, got '%s'\n", c.text, line)
|
||||
}
|
||||
}
|
||||
|
||||
// global check
|
||||
NoColor = true
|
||||
defer func() {
|
||||
NoColor = false
|
||||
}()
|
||||
for _, c := range testColors {
|
||||
p := New(c.code)
|
||||
p.Print(c.text)
|
||||
|
||||
line, _ := rb.ReadString('\n')
|
||||
if line != c.text {
|
||||
t.Errorf("Expecting %s, got '%s'\n", c.text, line)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func TestColorVisual(t *testing.T) {
|
||||
// First Visual Test
|
||||
Output = colorable.NewColorableStdout()
|
||||
|
||||
New(FgRed).Printf("red\t")
|
||||
New(BgRed).Print(" ")
|
||||
New(FgRed, Bold).Println(" red")
|
||||
|
||||
New(FgGreen).Printf("green\t")
|
||||
New(BgGreen).Print(" ")
|
||||
New(FgGreen, Bold).Println(" green")
|
||||
|
||||
New(FgYellow).Printf("yellow\t")
|
||||
New(BgYellow).Print(" ")
|
||||
New(FgYellow, Bold).Println(" yellow")
|
||||
|
||||
New(FgBlue).Printf("blue\t")
|
||||
New(BgBlue).Print(" ")
|
||||
New(FgBlue, Bold).Println(" blue")
|
||||
|
||||
New(FgMagenta).Printf("magenta\t")
|
||||
New(BgMagenta).Print(" ")
|
||||
New(FgMagenta, Bold).Println(" magenta")
|
||||
|
||||
New(FgCyan).Printf("cyan\t")
|
||||
New(BgCyan).Print(" ")
|
||||
New(FgCyan, Bold).Println(" cyan")
|
||||
|
||||
New(FgWhite).Printf("white\t")
|
||||
New(BgWhite).Print(" ")
|
||||
New(FgWhite, Bold).Println(" white")
|
||||
fmt.Println("")
|
||||
|
||||
// Second Visual test
|
||||
Black("black")
|
||||
Red("red")
|
||||
Green("green")
|
||||
Yellow("yellow")
|
||||
Blue("blue")
|
||||
Magenta("magenta")
|
||||
Cyan("cyan")
|
||||
White("white")
|
||||
|
||||
// Third visual test
|
||||
fmt.Println()
|
||||
Set(FgBlue)
|
||||
fmt.Println("is this blue?")
|
||||
Unset()
|
||||
|
||||
Set(FgMagenta)
|
||||
fmt.Println("and this magenta?")
|
||||
Unset()
|
||||
|
||||
// Fourth Visual test
|
||||
fmt.Println()
|
||||
blue := New(FgBlue).PrintlnFunc()
|
||||
blue("blue text with custom print func")
|
||||
|
||||
red := New(FgRed).PrintfFunc()
|
||||
red("red text with a printf func: %d\n", 123)
|
||||
|
||||
put := New(FgYellow).SprintFunc()
|
||||
warn := New(FgRed).SprintFunc()
|
||||
|
||||
fmt.Fprintf(Output, "this is a %s and this is %s.\n", put("warning"), warn("error"))
|
||||
|
||||
info := New(FgWhite, BgGreen).SprintFunc()
|
||||
fmt.Fprintf(Output, "this %s rocks!\n", info("package"))
|
||||
|
||||
notice := New(FgBlue).FprintFunc()
|
||||
notice(os.Stderr, "just a blue notice to stderr")
|
||||
|
||||
// Fifth Visual Test
|
||||
fmt.Println()
|
||||
|
||||
fmt.Fprintln(Output, BlackString("black"))
|
||||
fmt.Fprintln(Output, RedString("red"))
|
||||
fmt.Fprintln(Output, GreenString("green"))
|
||||
fmt.Fprintln(Output, YellowString("yellow"))
|
||||
fmt.Fprintln(Output, BlueString("blue"))
|
||||
fmt.Fprintln(Output, MagentaString("magenta"))
|
||||
fmt.Fprintln(Output, CyanString("cyan"))
|
||||
fmt.Fprintln(Output, WhiteString("white"))
|
||||
}
|
||||
|
||||
func TestNoFormat(t *testing.T) {
|
||||
fmt.Printf("%s %%s = ", BlackString("Black"))
|
||||
Black("%s")
|
||||
|
||||
fmt.Printf("%s %%s = ", RedString("Red"))
|
||||
Red("%s")
|
||||
|
||||
fmt.Printf("%s %%s = ", GreenString("Green"))
|
||||
Green("%s")
|
||||
|
||||
fmt.Printf("%s %%s = ", YellowString("Yellow"))
|
||||
Yellow("%s")
|
||||
|
||||
fmt.Printf("%s %%s = ", BlueString("Blue"))
|
||||
Blue("%s")
|
||||
|
||||
fmt.Printf("%s %%s = ", MagentaString("Magenta"))
|
||||
Magenta("%s")
|
||||
|
||||
fmt.Printf("%s %%s = ", CyanString("Cyan"))
|
||||
Cyan("%s")
|
||||
|
||||
fmt.Printf("%s %%s = ", WhiteString("White"))
|
||||
White("%s")
|
||||
}
|
||||
|
||||
func TestNoFormatString(t *testing.T) {
|
||||
tests := []struct {
|
||||
f func(string, ...interface{}) string
|
||||
format string
|
||||
args []interface{}
|
||||
want string
|
||||
}{
|
||||
{BlackString, "%s", nil, "\x1b[30m%s\x1b[0m"},
|
||||
{RedString, "%s", nil, "\x1b[31m%s\x1b[0m"},
|
||||
{GreenString, "%s", nil, "\x1b[32m%s\x1b[0m"},
|
||||
{YellowString, "%s", nil, "\x1b[33m%s\x1b[0m"},
|
||||
{BlueString, "%s", nil, "\x1b[34m%s\x1b[0m"},
|
||||
{MagentaString, "%s", nil, "\x1b[35m%s\x1b[0m"},
|
||||
{CyanString, "%s", nil, "\x1b[36m%s\x1b[0m"},
|
||||
{WhiteString, "%s", nil, "\x1b[37m%s\x1b[0m"},
|
||||
}
|
||||
|
||||
for i, test := range tests {
|
||||
s := fmt.Sprintf("%s", test.f(test.format, test.args...))
|
||||
if s != test.want {
|
||||
t.Errorf("[%d] want: %q, got: %q", i, test.want, s)
|
||||
}
|
||||
}
|
||||
}
|
128
vendor/github.com/fatih/color/doc.go
generated
vendored
Normal file
128
vendor/github.com/fatih/color/doc.go
generated
vendored
Normal file
@@ -0,0 +1,128 @@
|
||||
/*
|
||||
Package color is an ANSI color package to output colorized or SGR defined
|
||||
output to the standard output. The API can be used in several way, pick one
|
||||
that suits you.
|
||||
|
||||
Use simple and default helper functions with predefined foreground colors:
|
||||
|
||||
color.Cyan("Prints text in cyan.")
|
||||
|
||||
// a newline will be appended automatically
|
||||
color.Blue("Prints %s in blue.", "text")
|
||||
|
||||
// More default foreground colors..
|
||||
color.Red("We have red")
|
||||
color.Yellow("Yellow color too!")
|
||||
color.Magenta("And many others ..")
|
||||
|
||||
However there are times where custom color mixes are required. Below are some
|
||||
examples to create custom color objects and use the print functions of each
|
||||
separate color object.
|
||||
|
||||
// Create a new color object
|
||||
c := color.New(color.FgCyan).Add(color.Underline)
|
||||
c.Println("Prints cyan text with an underline.")
|
||||
|
||||
// Or just add them to New()
|
||||
d := color.New(color.FgCyan, color.Bold)
|
||||
d.Printf("This prints bold cyan %s\n", "too!.")
|
||||
|
||||
|
||||
// Mix up foreground and background colors, create new mixes!
|
||||
red := color.New(color.FgRed)
|
||||
|
||||
boldRed := red.Add(color.Bold)
|
||||
boldRed.Println("This will print text in bold red.")
|
||||
|
||||
whiteBackground := red.Add(color.BgWhite)
|
||||
whiteBackground.Println("Red text with White background.")
|
||||
|
||||
// Use your own io.Writer output
|
||||
color.New(color.FgBlue).Fprintln(myWriter, "blue color!")
|
||||
|
||||
blue := color.New(color.FgBlue)
|
||||
blue.Fprint(myWriter, "This will print text in blue.")
|
||||
|
||||
You can create PrintXxx functions to simplify even more:
|
||||
|
||||
// Create a custom print function for convenient
|
||||
red := color.New(color.FgRed).PrintfFunc()
|
||||
red("warning")
|
||||
red("error: %s", err)
|
||||
|
||||
// Mix up multiple attributes
|
||||
notice := color.New(color.Bold, color.FgGreen).PrintlnFunc()
|
||||
notice("don't forget this...")
|
||||
|
||||
You can also FprintXxx functions to pass your own io.Writer:
|
||||
|
||||
blue := color.New(FgBlue).FprintfFunc()
|
||||
blue(myWriter, "important notice: %s", stars)
|
||||
|
||||
// Mix up with multiple attributes
|
||||
success := color.New(color.Bold, color.FgGreen).FprintlnFunc()
|
||||
success(myWriter, don't forget this...")
|
||||
|
||||
|
||||
Or create SprintXxx functions to mix strings with other non-colorized strings:
|
||||
|
||||
yellow := New(FgYellow).SprintFunc()
|
||||
red := New(FgRed).SprintFunc()
|
||||
|
||||
fmt.Printf("this is a %s and this is %s.\n", yellow("warning"), red("error"))
|
||||
|
||||
info := New(FgWhite, BgGreen).SprintFunc()
|
||||
fmt.Printf("this %s rocks!\n", info("package"))
|
||||
|
||||
Windows support is enabled by default. All Print functions works as intended.
|
||||
However only for color.SprintXXX functions, user should use fmt.FprintXXX and
|
||||
set the output to color.Output:
|
||||
|
||||
fmt.Fprintf(color.Output, "Windows support: %s", color.GreenString("PASS"))
|
||||
|
||||
info := New(FgWhite, BgGreen).SprintFunc()
|
||||
fmt.Fprintf(color.Output, "this %s rocks!\n", info("package"))
|
||||
|
||||
Using with existing code is possible. Just use the Set() method to set the
|
||||
standard output to the given parameters. That way a rewrite of an existing
|
||||
code is not required.
|
||||
|
||||
// Use handy standard colors.
|
||||
color.Set(color.FgYellow)
|
||||
|
||||
fmt.Println("Existing text will be now in Yellow")
|
||||
fmt.Printf("This one %s\n", "too")
|
||||
|
||||
color.Unset() // don't forget to unset
|
||||
|
||||
// You can mix up parameters
|
||||
color.Set(color.FgMagenta, color.Bold)
|
||||
defer color.Unset() // use it in your function
|
||||
|
||||
fmt.Println("All text will be now bold magenta.")
|
||||
|
||||
There might be a case where you want to disable color output (for example to
|
||||
pipe the standard output of your app to somewhere else). `Color` has support to
|
||||
disable colors both globally and for single color definition. For example
|
||||
suppose you have a CLI app and a `--no-color` bool flag. You can easily disable
|
||||
the color output with:
|
||||
|
||||
var flagNoColor = flag.Bool("no-color", false, "Disable color output")
|
||||
|
||||
if *flagNoColor {
|
||||
color.NoColor = true // disables colorized output
|
||||
}
|
||||
|
||||
It also has support for single color definitions (local). You can
|
||||
disable/enable color output on the fly:
|
||||
|
||||
c := color.New(color.FgCyan)
|
||||
c.Println("Prints cyan text")
|
||||
|
||||
c.DisableColor()
|
||||
c.Println("This is printed without any color")
|
||||
|
||||
c.EnableColor()
|
||||
c.Println("This prints again cyan...")
|
||||
*/
|
||||
package color
|
16
vendor/github.com/garyburd/redigo/.travis.yml
generated
vendored
Normal file
16
vendor/github.com/garyburd/redigo/.travis.yml
generated
vendored
Normal file
@@ -0,0 +1,16 @@
|
||||
language: go
|
||||
sudo: false
|
||||
services:
|
||||
- redis-server
|
||||
|
||||
go:
|
||||
- 1.4
|
||||
- 1.5
|
||||
- 1.6
|
||||
- tip
|
||||
|
||||
script:
|
||||
- go get -t -v ./...
|
||||
- diff -u <(echo -n) <(gofmt -d .)
|
||||
- go vet $(go list ./... | grep -v /vendor/)
|
||||
- go test -v -race ./...
|
175
vendor/github.com/garyburd/redigo/LICENSE
generated
vendored
Normal file
175
vendor/github.com/garyburd/redigo/LICENSE
generated
vendored
Normal file
@@ -0,0 +1,175 @@
|
||||
|
||||
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.
|
50
vendor/github.com/garyburd/redigo/README.markdown
generated
vendored
Normal file
50
vendor/github.com/garyburd/redigo/README.markdown
generated
vendored
Normal file
@@ -0,0 +1,50 @@
|
||||
Redigo
|
||||
======
|
||||
|
||||
[](https://travis-ci.org/garyburd/redigo)
|
||||
[](https://godoc.org/github.com/garyburd/redigo/redis)
|
||||
|
||||
Redigo is a [Go](http://golang.org/) client for the [Redis](http://redis.io/) database.
|
||||
|
||||
Features
|
||||
-------
|
||||
|
||||
* A [Print-like](http://godoc.org/github.com/garyburd/redigo/redis#hdr-Executing_Commands) API with support for all Redis commands.
|
||||
* [Pipelining](http://godoc.org/github.com/garyburd/redigo/redis#hdr-Pipelining), including pipelined transactions.
|
||||
* [Publish/Subscribe](http://godoc.org/github.com/garyburd/redigo/redis#hdr-Publish_and_Subscribe).
|
||||
* [Connection pooling](http://godoc.org/github.com/garyburd/redigo/redis#Pool).
|
||||
* [Script helper type](http://godoc.org/github.com/garyburd/redigo/redis#Script) with optimistic use of EVALSHA.
|
||||
* [Helper functions](http://godoc.org/github.com/garyburd/redigo/redis#hdr-Reply_Helpers) for working with command replies.
|
||||
|
||||
Documentation
|
||||
-------------
|
||||
|
||||
- [API Reference](http://godoc.org/github.com/garyburd/redigo/redis)
|
||||
- [FAQ](https://github.com/garyburd/redigo/wiki/FAQ)
|
||||
|
||||
Installation
|
||||
------------
|
||||
|
||||
Install Redigo using the "go get" command:
|
||||
|
||||
go get github.com/garyburd/redigo/redis
|
||||
|
||||
The Go distribution is Redigo's only dependency.
|
||||
|
||||
Related Projects
|
||||
----------------
|
||||
|
||||
- [rafaeljusto/redigomock](https://godoc.org/github.com/rafaeljusto/redigomock) - A mock library for Redigo.
|
||||
- [chasex/redis-go-cluster](https://github.com/chasex/redis-go-cluster) - A Redis cluster client implementation.
|
||||
- [FZambia/go-sentinel](https://github.com/FZambia/go-sentinel) - Redis Sentinel support for Redigo
|
||||
- [PuerkitoBio/redisc](https://github.com/PuerkitoBio/redisc) - Redis Cluster client built on top of Redigo
|
||||
|
||||
Contributing
|
||||
------------
|
||||
|
||||
Send email to Gary Burd (address in GitHub profile) before doing any work on Redigo.
|
||||
|
||||
License
|
||||
-------
|
||||
|
||||
Redigo is available under the [Apache License, Version 2.0](http://www.apache.org/licenses/LICENSE-2.0.html).
|
54
vendor/github.com/garyburd/redigo/internal/commandinfo.go
generated
vendored
Normal file
54
vendor/github.com/garyburd/redigo/internal/commandinfo.go
generated
vendored
Normal file
@@ -0,0 +1,54 @@
|
||||
// Copyright 2014 Gary Burd
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License"): you may
|
||||
// not use this file except in compliance with the License. You may obtain
|
||||
// a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
// License for the specific language governing permissions and limitations
|
||||
// under the License.
|
||||
|
||||
package internal // import "github.com/garyburd/redigo/internal"
|
||||
|
||||
import (
|
||||
"strings"
|
||||
)
|
||||
|
||||
const (
|
||||
WatchState = 1 << iota
|
||||
MultiState
|
||||
SubscribeState
|
||||
MonitorState
|
||||
)
|
||||
|
||||
type CommandInfo struct {
|
||||
Set, Clear int
|
||||
}
|
||||
|
||||
var commandInfos = map[string]CommandInfo{
|
||||
"WATCH": {Set: WatchState},
|
||||
"UNWATCH": {Clear: WatchState},
|
||||
"MULTI": {Set: MultiState},
|
||||
"EXEC": {Clear: WatchState | MultiState},
|
||||
"DISCARD": {Clear: WatchState | MultiState},
|
||||
"PSUBSCRIBE": {Set: SubscribeState},
|
||||
"SUBSCRIBE": {Set: SubscribeState},
|
||||
"MONITOR": {Set: MonitorState},
|
||||
}
|
||||
|
||||
func init() {
|
||||
for n, ci := range commandInfos {
|
||||
commandInfos[strings.ToLower(n)] = ci
|
||||
}
|
||||
}
|
||||
|
||||
func LookupCommandInfo(commandName string) CommandInfo {
|
||||
if ci, ok := commandInfos[commandName]; ok {
|
||||
return ci
|
||||
}
|
||||
return commandInfos[strings.ToUpper(commandName)]
|
||||
}
|
27
vendor/github.com/garyburd/redigo/internal/commandinfo_test.go
generated
vendored
Normal file
27
vendor/github.com/garyburd/redigo/internal/commandinfo_test.go
generated
vendored
Normal file
@@ -0,0 +1,27 @@
|
||||
package internal
|
||||
|
||||
import "testing"
|
||||
|
||||
func TestLookupCommandInfo(t *testing.T) {
|
||||
for _, n := range []string{"watch", "WATCH", "wAtch"} {
|
||||
if LookupCommandInfo(n) == (CommandInfo{}) {
|
||||
t.Errorf("LookupCommandInfo(%q) = CommandInfo{}, expected non-zero value", n)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func benchmarkLookupCommandInfo(b *testing.B, names ...string) {
|
||||
for i := 0; i < b.N; i++ {
|
||||
for _, c := range names {
|
||||
LookupCommandInfo(c)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkLookupCommandInfoCorrectCase(b *testing.B) {
|
||||
benchmarkLookupCommandInfo(b, "watch", "WATCH", "monitor", "MONITOR")
|
||||
}
|
||||
|
||||
func BenchmarkLookupCommandInfoMixedCase(b *testing.B) {
|
||||
benchmarkLookupCommandInfo(b, "wAtch", "WeTCH", "monItor", "MONiTOR")
|
||||
}
|
68
vendor/github.com/garyburd/redigo/internal/redistest/testdb.go
generated
vendored
Normal file
68
vendor/github.com/garyburd/redigo/internal/redistest/testdb.go
generated
vendored
Normal file
@@ -0,0 +1,68 @@
|
||||
// Copyright 2014 Gary Burd
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License"): you may
|
||||
// not use this file except in compliance with the License. You may obtain
|
||||
// a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
// License for the specific language governing permissions and limitations
|
||||
// under the License.
|
||||
|
||||
// Package redistest contains utilities for writing Redigo tests.
|
||||
package redistest
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"time"
|
||||
|
||||
"github.com/garyburd/redigo/redis"
|
||||
)
|
||||
|
||||
type testConn struct {
|
||||
redis.Conn
|
||||
}
|
||||
|
||||
func (t testConn) Close() error {
|
||||
_, err := t.Conn.Do("SELECT", "9")
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
_, err = t.Conn.Do("FLUSHDB")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return t.Conn.Close()
|
||||
}
|
||||
|
||||
// Dial dials the local Redis server and selects database 9. To prevent
|
||||
// stomping on real data, DialTestDB fails if database 9 contains data. The
|
||||
// returned connection flushes database 9 on close.
|
||||
func Dial() (redis.Conn, error) {
|
||||
c, err := redis.DialTimeout("tcp", ":6379", 0, 1*time.Second, 1*time.Second)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
_, err = c.Do("SELECT", "9")
|
||||
if err != nil {
|
||||
c.Close()
|
||||
return nil, err
|
||||
}
|
||||
|
||||
n, err := redis.Int(c.Do("DBSIZE"))
|
||||
if err != nil {
|
||||
c.Close()
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if n != 0 {
|
||||
c.Close()
|
||||
return nil, errors.New("database #9 is not empty, test can not continue")
|
||||
}
|
||||
|
||||
return testConn{c}, nil
|
||||
}
|
570
vendor/github.com/garyburd/redigo/redis/conn.go
generated
vendored
Normal file
570
vendor/github.com/garyburd/redigo/redis/conn.go
generated
vendored
Normal file
@@ -0,0 +1,570 @@
|
||||
// Copyright 2012 Gary Burd
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License"): you may
|
||||
// not use this file except in compliance with the License. You may obtain
|
||||
// a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
// License for the specific language governing permissions and limitations
|
||||
// under the License.
|
||||
|
||||
package redis
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"bytes"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"net"
|
||||
"net/url"
|
||||
"regexp"
|
||||
"strconv"
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
|
||||
// conn is the low-level implementation of Conn
|
||||
type conn struct {
|
||||
|
||||
// Shared
|
||||
mu sync.Mutex
|
||||
pending int
|
||||
err error
|
||||
conn net.Conn
|
||||
|
||||
// Read
|
||||
readTimeout time.Duration
|
||||
br *bufio.Reader
|
||||
|
||||
// Write
|
||||
writeTimeout time.Duration
|
||||
bw *bufio.Writer
|
||||
|
||||
// Scratch space for formatting argument length.
|
||||
// '*' or '$', length, "\r\n"
|
||||
lenScratch [32]byte
|
||||
|
||||
// Scratch space for formatting integers and floats.
|
||||
numScratch [40]byte
|
||||
}
|
||||
|
||||
// DialTimeout acts like Dial but takes timeouts for establishing the
|
||||
// connection to the server, writing a command and reading a reply.
|
||||
//
|
||||
// Deprecated: Use Dial with options instead.
|
||||
func DialTimeout(network, address string, connectTimeout, readTimeout, writeTimeout time.Duration) (Conn, error) {
|
||||
return Dial(network, address,
|
||||
DialConnectTimeout(connectTimeout),
|
||||
DialReadTimeout(readTimeout),
|
||||
DialWriteTimeout(writeTimeout))
|
||||
}
|
||||
|
||||
// DialOption specifies an option for dialing a Redis server.
|
||||
type DialOption struct {
|
||||
f func(*dialOptions)
|
||||
}
|
||||
|
||||
type dialOptions struct {
|
||||
readTimeout time.Duration
|
||||
writeTimeout time.Duration
|
||||
dial func(network, addr string) (net.Conn, error)
|
||||
db int
|
||||
password string
|
||||
}
|
||||
|
||||
// DialReadTimeout specifies the timeout for reading a single command reply.
|
||||
func DialReadTimeout(d time.Duration) DialOption {
|
||||
return DialOption{func(do *dialOptions) {
|
||||
do.readTimeout = d
|
||||
}}
|
||||
}
|
||||
|
||||
// DialWriteTimeout specifies the timeout for writing a single command.
|
||||
func DialWriteTimeout(d time.Duration) DialOption {
|
||||
return DialOption{func(do *dialOptions) {
|
||||
do.writeTimeout = d
|
||||
}}
|
||||
}
|
||||
|
||||
// DialConnectTimeout specifies the timeout for connecting to the Redis server.
|
||||
func DialConnectTimeout(d time.Duration) DialOption {
|
||||
return DialOption{func(do *dialOptions) {
|
||||
dialer := net.Dialer{Timeout: d}
|
||||
do.dial = dialer.Dial
|
||||
}}
|
||||
}
|
||||
|
||||
// DialNetDial specifies a custom dial function for creating TCP
|
||||
// connections. If this option is left out, then net.Dial is
|
||||
// used. DialNetDial overrides DialConnectTimeout.
|
||||
func DialNetDial(dial func(network, addr string) (net.Conn, error)) DialOption {
|
||||
return DialOption{func(do *dialOptions) {
|
||||
do.dial = dial
|
||||
}}
|
||||
}
|
||||
|
||||
// DialDatabase specifies the database to select when dialing a connection.
|
||||
func DialDatabase(db int) DialOption {
|
||||
return DialOption{func(do *dialOptions) {
|
||||
do.db = db
|
||||
}}
|
||||
}
|
||||
|
||||
// DialPassword specifies the password to use when connecting to
|
||||
// the Redis server.
|
||||
func DialPassword(password string) DialOption {
|
||||
return DialOption{func(do *dialOptions) {
|
||||
do.password = password
|
||||
}}
|
||||
}
|
||||
|
||||
// Dial connects to the Redis server at the given network and
|
||||
// address using the specified options.
|
||||
func Dial(network, address string, options ...DialOption) (Conn, error) {
|
||||
do := dialOptions{
|
||||
dial: net.Dial,
|
||||
}
|
||||
for _, option := range options {
|
||||
option.f(&do)
|
||||
}
|
||||
|
||||
netConn, err := do.dial(network, address)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
c := &conn{
|
||||
conn: netConn,
|
||||
bw: bufio.NewWriter(netConn),
|
||||
br: bufio.NewReader(netConn),
|
||||
readTimeout: do.readTimeout,
|
||||
writeTimeout: do.writeTimeout,
|
||||
}
|
||||
|
||||
if do.password != "" {
|
||||
if _, err := c.Do("AUTH", do.password); err != nil {
|
||||
netConn.Close()
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
if do.db != 0 {
|
||||
if _, err := c.Do("SELECT", do.db); err != nil {
|
||||
netConn.Close()
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
return c, nil
|
||||
}
|
||||
|
||||
var pathDBRegexp = regexp.MustCompile(`/(\d*)\z`)
|
||||
|
||||
// DialURL connects to a Redis server at the given URL using the Redis
|
||||
// URI scheme. URLs should follow the draft IANA specification for the
|
||||
// scheme (https://www.iana.org/assignments/uri-schemes/prov/redis).
|
||||
func DialURL(rawurl string, options ...DialOption) (Conn, error) {
|
||||
u, err := url.Parse(rawurl)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if u.Scheme != "redis" {
|
||||
return nil, fmt.Errorf("invalid redis URL scheme: %s", u.Scheme)
|
||||
}
|
||||
|
||||
// As per the IANA draft spec, the host defaults to localhost and
|
||||
// the port defaults to 6379.
|
||||
host, port, err := net.SplitHostPort(u.Host)
|
||||
if err != nil {
|
||||
// assume port is missing
|
||||
host = u.Host
|
||||
port = "6379"
|
||||
}
|
||||
if host == "" {
|
||||
host = "localhost"
|
||||
}
|
||||
address := net.JoinHostPort(host, port)
|
||||
|
||||
if u.User != nil {
|
||||
password, isSet := u.User.Password()
|
||||
if isSet {
|
||||
options = append(options, DialPassword(password))
|
||||
}
|
||||
}
|
||||
|
||||
match := pathDBRegexp.FindStringSubmatch(u.Path)
|
||||
if len(match) == 2 {
|
||||
db := 0
|
||||
if len(match[1]) > 0 {
|
||||
db, err = strconv.Atoi(match[1])
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("invalid database: %s", u.Path[1:])
|
||||
}
|
||||
}
|
||||
if db != 0 {
|
||||
options = append(options, DialDatabase(db))
|
||||
}
|
||||
} else if u.Path != "" {
|
||||
return nil, fmt.Errorf("invalid database: %s", u.Path[1:])
|
||||
}
|
||||
|
||||
return Dial("tcp", address, options...)
|
||||
}
|
||||
|
||||
// NewConn returns a new Redigo connection for the given net connection.
|
||||
func NewConn(netConn net.Conn, readTimeout, writeTimeout time.Duration) Conn {
|
||||
return &conn{
|
||||
conn: netConn,
|
||||
bw: bufio.NewWriter(netConn),
|
||||
br: bufio.NewReader(netConn),
|
||||
readTimeout: readTimeout,
|
||||
writeTimeout: writeTimeout,
|
||||
}
|
||||
}
|
||||
|
||||
func (c *conn) Close() error {
|
||||
c.mu.Lock()
|
||||
err := c.err
|
||||
if c.err == nil {
|
||||
c.err = errors.New("redigo: closed")
|
||||
err = c.conn.Close()
|
||||
}
|
||||
c.mu.Unlock()
|
||||
return err
|
||||
}
|
||||
|
||||
func (c *conn) fatal(err error) error {
|
||||
c.mu.Lock()
|
||||
if c.err == nil {
|
||||
c.err = err
|
||||
// Close connection to force errors on subsequent calls and to unblock
|
||||
// other reader or writer.
|
||||
c.conn.Close()
|
||||
}
|
||||
c.mu.Unlock()
|
||||
return err
|
||||
}
|
||||
|
||||
func (c *conn) Err() error {
|
||||
c.mu.Lock()
|
||||
err := c.err
|
||||
c.mu.Unlock()
|
||||
return err
|
||||
}
|
||||
|
||||
func (c *conn) writeLen(prefix byte, n int) error {
|
||||
c.lenScratch[len(c.lenScratch)-1] = '\n'
|
||||
c.lenScratch[len(c.lenScratch)-2] = '\r'
|
||||
i := len(c.lenScratch) - 3
|
||||
for {
|
||||
c.lenScratch[i] = byte('0' + n%10)
|
||||
i -= 1
|
||||
n = n / 10
|
||||
if n == 0 {
|
||||
break
|
||||
}
|
||||
}
|
||||
c.lenScratch[i] = prefix
|
||||
_, err := c.bw.Write(c.lenScratch[i:])
|
||||
return err
|
||||
}
|
||||
|
||||
func (c *conn) writeString(s string) error {
|
||||
c.writeLen('$', len(s))
|
||||
c.bw.WriteString(s)
|
||||
_, err := c.bw.WriteString("\r\n")
|
||||
return err
|
||||
}
|
||||
|
||||
func (c *conn) writeBytes(p []byte) error {
|
||||
c.writeLen('$', len(p))
|
||||
c.bw.Write(p)
|
||||
_, err := c.bw.WriteString("\r\n")
|
||||
return err
|
||||
}
|
||||
|
||||
func (c *conn) writeInt64(n int64) error {
|
||||
return c.writeBytes(strconv.AppendInt(c.numScratch[:0], n, 10))
|
||||
}
|
||||
|
||||
func (c *conn) writeFloat64(n float64) error {
|
||||
return c.writeBytes(strconv.AppendFloat(c.numScratch[:0], n, 'g', -1, 64))
|
||||
}
|
||||
|
||||
func (c *conn) writeCommand(cmd string, args []interface{}) (err error) {
|
||||
c.writeLen('*', 1+len(args))
|
||||
err = c.writeString(cmd)
|
||||
for _, arg := range args {
|
||||
if err != nil {
|
||||
break
|
||||
}
|
||||
switch arg := arg.(type) {
|
||||
case string:
|
||||
err = c.writeString(arg)
|
||||
case []byte:
|
||||
err = c.writeBytes(arg)
|
||||
case int:
|
||||
err = c.writeInt64(int64(arg))
|
||||
case int64:
|
||||
err = c.writeInt64(arg)
|
||||
case float64:
|
||||
err = c.writeFloat64(arg)
|
||||
case bool:
|
||||
if arg {
|
||||
err = c.writeString("1")
|
||||
} else {
|
||||
err = c.writeString("0")
|
||||
}
|
||||
case nil:
|
||||
err = c.writeString("")
|
||||
default:
|
||||
var buf bytes.Buffer
|
||||
fmt.Fprint(&buf, arg)
|
||||
err = c.writeBytes(buf.Bytes())
|
||||
}
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
type protocolError string
|
||||
|
||||
func (pe protocolError) Error() string {
|
||||
return fmt.Sprintf("redigo: %s (possible server error or unsupported concurrent read by application)", string(pe))
|
||||
}
|
||||
|
||||
func (c *conn) readLine() ([]byte, error) {
|
||||
p, err := c.br.ReadSlice('\n')
|
||||
if err == bufio.ErrBufferFull {
|
||||
return nil, protocolError("long response line")
|
||||
}
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
i := len(p) - 2
|
||||
if i < 0 || p[i] != '\r' {
|
||||
return nil, protocolError("bad response line terminator")
|
||||
}
|
||||
return p[:i], nil
|
||||
}
|
||||
|
||||
// parseLen parses bulk string and array lengths.
|
||||
func parseLen(p []byte) (int, error) {
|
||||
if len(p) == 0 {
|
||||
return -1, protocolError("malformed length")
|
||||
}
|
||||
|
||||
if p[0] == '-' && len(p) == 2 && p[1] == '1' {
|
||||
// handle $-1 and $-1 null replies.
|
||||
return -1, nil
|
||||
}
|
||||
|
||||
var n int
|
||||
for _, b := range p {
|
||||
n *= 10
|
||||
if b < '0' || b > '9' {
|
||||
return -1, protocolError("illegal bytes in length")
|
||||
}
|
||||
n += int(b - '0')
|
||||
}
|
||||
|
||||
return n, nil
|
||||
}
|
||||
|
||||
// parseInt parses an integer reply.
|
||||
func parseInt(p []byte) (interface{}, error) {
|
||||
if len(p) == 0 {
|
||||
return 0, protocolError("malformed integer")
|
||||
}
|
||||
|
||||
var negate bool
|
||||
if p[0] == '-' {
|
||||
negate = true
|
||||
p = p[1:]
|
||||
if len(p) == 0 {
|
||||
return 0, protocolError("malformed integer")
|
||||
}
|
||||
}
|
||||
|
||||
var n int64
|
||||
for _, b := range p {
|
||||
n *= 10
|
||||
if b < '0' || b > '9' {
|
||||
return 0, protocolError("illegal bytes in length")
|
||||
}
|
||||
n += int64(b - '0')
|
||||
}
|
||||
|
||||
if negate {
|
||||
n = -n
|
||||
}
|
||||
return n, nil
|
||||
}
|
||||
|
||||
var (
|
||||
okReply interface{} = "OK"
|
||||
pongReply interface{} = "PONG"
|
||||
)
|
||||
|
||||
func (c *conn) readReply() (interface{}, error) {
|
||||
line, err := c.readLine()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if len(line) == 0 {
|
||||
return nil, protocolError("short response line")
|
||||
}
|
||||
switch line[0] {
|
||||
case '+':
|
||||
switch {
|
||||
case len(line) == 3 && line[1] == 'O' && line[2] == 'K':
|
||||
// Avoid allocation for frequent "+OK" response.
|
||||
return okReply, nil
|
||||
case len(line) == 5 && line[1] == 'P' && line[2] == 'O' && line[3] == 'N' && line[4] == 'G':
|
||||
// Avoid allocation in PING command benchmarks :)
|
||||
return pongReply, nil
|
||||
default:
|
||||
return string(line[1:]), nil
|
||||
}
|
||||
case '-':
|
||||
return Error(string(line[1:])), nil
|
||||
case ':':
|
||||
return parseInt(line[1:])
|
||||
case '$':
|
||||
n, err := parseLen(line[1:])
|
||||
if n < 0 || err != nil {
|
||||
return nil, err
|
||||
}
|
||||
p := make([]byte, n)
|
||||
_, err = io.ReadFull(c.br, p)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if line, err := c.readLine(); err != nil {
|
||||
return nil, err
|
||||
} else if len(line) != 0 {
|
||||
return nil, protocolError("bad bulk string format")
|
||||
}
|
||||
return p, nil
|
||||
case '*':
|
||||
n, err := parseLen(line[1:])
|
||||
if n < 0 || err != nil {
|
||||
return nil, err
|
||||
}
|
||||
r := make([]interface{}, n)
|
||||
for i := range r {
|
||||
r[i], err = c.readReply()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
return r, nil
|
||||
}
|
||||
return nil, protocolError("unexpected response line")
|
||||
}
|
||||
|
||||
func (c *conn) Send(cmd string, args ...interface{}) error {
|
||||
c.mu.Lock()
|
||||
c.pending += 1
|
||||
c.mu.Unlock()
|
||||
if c.writeTimeout != 0 {
|
||||
c.conn.SetWriteDeadline(time.Now().Add(c.writeTimeout))
|
||||
}
|
||||
if err := c.writeCommand(cmd, args); err != nil {
|
||||
return c.fatal(err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *conn) Flush() error {
|
||||
if c.writeTimeout != 0 {
|
||||
c.conn.SetWriteDeadline(time.Now().Add(c.writeTimeout))
|
||||
}
|
||||
if err := c.bw.Flush(); err != nil {
|
||||
return c.fatal(err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *conn) Receive() (reply interface{}, err error) {
|
||||
if c.readTimeout != 0 {
|
||||
c.conn.SetReadDeadline(time.Now().Add(c.readTimeout))
|
||||
}
|
||||
if reply, err = c.readReply(); err != nil {
|
||||
return nil, c.fatal(err)
|
||||
}
|
||||
// When using pub/sub, the number of receives can be greater than the
|
||||
// number of sends. To enable normal use of the connection after
|
||||
// unsubscribing from all channels, we do not decrement pending to a
|
||||
// negative value.
|
||||
//
|
||||
// The pending field is decremented after the reply is read to handle the
|
||||
// case where Receive is called before Send.
|
||||
c.mu.Lock()
|
||||
if c.pending > 0 {
|
||||
c.pending -= 1
|
||||
}
|
||||
c.mu.Unlock()
|
||||
if err, ok := reply.(Error); ok {
|
||||
return nil, err
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (c *conn) Do(cmd string, args ...interface{}) (interface{}, error) {
|
||||
c.mu.Lock()
|
||||
pending := c.pending
|
||||
c.pending = 0
|
||||
c.mu.Unlock()
|
||||
|
||||
if cmd == "" && pending == 0 {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
if c.writeTimeout != 0 {
|
||||
c.conn.SetWriteDeadline(time.Now().Add(c.writeTimeout))
|
||||
}
|
||||
|
||||
if cmd != "" {
|
||||
if err := c.writeCommand(cmd, args); err != nil {
|
||||
return nil, c.fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
if err := c.bw.Flush(); err != nil {
|
||||
return nil, c.fatal(err)
|
||||
}
|
||||
|
||||
if c.readTimeout != 0 {
|
||||
c.conn.SetReadDeadline(time.Now().Add(c.readTimeout))
|
||||
}
|
||||
|
||||
if cmd == "" {
|
||||
reply := make([]interface{}, pending)
|
||||
for i := range reply {
|
||||
r, e := c.readReply()
|
||||
if e != nil {
|
||||
return nil, c.fatal(e)
|
||||
}
|
||||
reply[i] = r
|
||||
}
|
||||
return reply, nil
|
||||
}
|
||||
|
||||
var err error
|
||||
var reply interface{}
|
||||
for i := 0; i <= pending; i++ {
|
||||
var e error
|
||||
if reply, e = c.readReply(); e != nil {
|
||||
return nil, c.fatal(e)
|
||||
}
|
||||
if e, ok := reply.(Error); ok && err == nil {
|
||||
err = e
|
||||
}
|
||||
}
|
||||
return reply, err
|
||||
}
|
670
vendor/github.com/garyburd/redigo/redis/conn_test.go
generated
vendored
Normal file
670
vendor/github.com/garyburd/redigo/redis/conn_test.go
generated
vendored
Normal file
@@ -0,0 +1,670 @@
|
||||
// Copyright 2012 Gary Burd
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License"): you may
|
||||
// not use this file except in compliance with the License. You may obtain
|
||||
// a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
// License for the specific language governing permissions and limitations
|
||||
// under the License.
|
||||
|
||||
package redis_test
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"io"
|
||||
"math"
|
||||
"net"
|
||||
"os"
|
||||
"reflect"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/garyburd/redigo/redis"
|
||||
)
|
||||
|
||||
type testConn struct {
|
||||
io.Reader
|
||||
io.Writer
|
||||
}
|
||||
|
||||
func (*testConn) Close() error { return nil }
|
||||
func (*testConn) LocalAddr() net.Addr { return nil }
|
||||
func (*testConn) RemoteAddr() net.Addr { return nil }
|
||||
func (*testConn) SetDeadline(t time.Time) error { return nil }
|
||||
func (*testConn) SetReadDeadline(t time.Time) error { return nil }
|
||||
func (*testConn) SetWriteDeadline(t time.Time) error { return nil }
|
||||
|
||||
func dialTestConn(r io.Reader, w io.Writer) redis.DialOption {
|
||||
return redis.DialNetDial(func(net, addr string) (net.Conn, error) {
|
||||
return &testConn{Reader: r, Writer: w}, nil
|
||||
})
|
||||
}
|
||||
|
||||
var writeTests = []struct {
|
||||
args []interface{}
|
||||
expected string
|
||||
}{
|
||||
{
|
||||
[]interface{}{"SET", "key", "value"},
|
||||
"*3\r\n$3\r\nSET\r\n$3\r\nkey\r\n$5\r\nvalue\r\n",
|
||||
},
|
||||
{
|
||||
[]interface{}{"SET", "key", "value"},
|
||||
"*3\r\n$3\r\nSET\r\n$3\r\nkey\r\n$5\r\nvalue\r\n",
|
||||
},
|
||||
{
|
||||
[]interface{}{"SET", "key", byte(100)},
|
||||
"*3\r\n$3\r\nSET\r\n$3\r\nkey\r\n$3\r\n100\r\n",
|
||||
},
|
||||
{
|
||||
[]interface{}{"SET", "key", 100},
|
||||
"*3\r\n$3\r\nSET\r\n$3\r\nkey\r\n$3\r\n100\r\n",
|
||||
},
|
||||
{
|
||||
[]interface{}{"SET", "key", int64(math.MinInt64)},
|
||||
"*3\r\n$3\r\nSET\r\n$3\r\nkey\r\n$20\r\n-9223372036854775808\r\n",
|
||||
},
|
||||
{
|
||||
[]interface{}{"SET", "key", float64(1349673917.939762)},
|
||||
"*3\r\n$3\r\nSET\r\n$3\r\nkey\r\n$21\r\n1.349673917939762e+09\r\n",
|
||||
},
|
||||
{
|
||||
[]interface{}{"SET", "key", ""},
|
||||
"*3\r\n$3\r\nSET\r\n$3\r\nkey\r\n$0\r\n\r\n",
|
||||
},
|
||||
{
|
||||
[]interface{}{"SET", "key", nil},
|
||||
"*3\r\n$3\r\nSET\r\n$3\r\nkey\r\n$0\r\n\r\n",
|
||||
},
|
||||
{
|
||||
[]interface{}{"ECHO", true, false},
|
||||
"*3\r\n$4\r\nECHO\r\n$1\r\n1\r\n$1\r\n0\r\n",
|
||||
},
|
||||
}
|
||||
|
||||
func TestWrite(t *testing.T) {
|
||||
for _, tt := range writeTests {
|
||||
var buf bytes.Buffer
|
||||
c, _ := redis.Dial("", "", dialTestConn(nil, &buf))
|
||||
err := c.Send(tt.args[0].(string), tt.args[1:]...)
|
||||
if err != nil {
|
||||
t.Errorf("Send(%v) returned error %v", tt.args, err)
|
||||
continue
|
||||
}
|
||||
c.Flush()
|
||||
actual := buf.String()
|
||||
if actual != tt.expected {
|
||||
t.Errorf("Send(%v) = %q, want %q", tt.args, actual, tt.expected)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var errorSentinel = &struct{}{}
|
||||
|
||||
var readTests = []struct {
|
||||
reply string
|
||||
expected interface{}
|
||||
}{
|
||||
{
|
||||
"+OK\r\n",
|
||||
"OK",
|
||||
},
|
||||
{
|
||||
"+PONG\r\n",
|
||||
"PONG",
|
||||
},
|
||||
{
|
||||
"@OK\r\n",
|
||||
errorSentinel,
|
||||
},
|
||||
{
|
||||
"$6\r\nfoobar\r\n",
|
||||
[]byte("foobar"),
|
||||
},
|
||||
{
|
||||
"$-1\r\n",
|
||||
nil,
|
||||
},
|
||||
{
|
||||
":1\r\n",
|
||||
int64(1),
|
||||
},
|
||||
{
|
||||
":-2\r\n",
|
||||
int64(-2),
|
||||
},
|
||||
{
|
||||
"*0\r\n",
|
||||
[]interface{}{},
|
||||
},
|
||||
{
|
||||
"*-1\r\n",
|
||||
nil,
|
||||
},
|
||||
{
|
||||
"*4\r\n$3\r\nfoo\r\n$3\r\nbar\r\n$5\r\nHello\r\n$5\r\nWorld\r\n",
|
||||
[]interface{}{[]byte("foo"), []byte("bar"), []byte("Hello"), []byte("World")},
|
||||
},
|
||||
{
|
||||
"*3\r\n$3\r\nfoo\r\n$-1\r\n$3\r\nbar\r\n",
|
||||
[]interface{}{[]byte("foo"), nil, []byte("bar")},
|
||||
},
|
||||
|
||||
{
|
||||
// "x" is not a valid length
|
||||
"$x\r\nfoobar\r\n",
|
||||
errorSentinel,
|
||||
},
|
||||
{
|
||||
// -2 is not a valid length
|
||||
"$-2\r\n",
|
||||
errorSentinel,
|
||||
},
|
||||
{
|
||||
// "x" is not a valid integer
|
||||
":x\r\n",
|
||||
errorSentinel,
|
||||
},
|
||||
{
|
||||
// missing \r\n following value
|
||||
"$6\r\nfoobar",
|
||||
errorSentinel,
|
||||
},
|
||||
{
|
||||
// short value
|
||||
"$6\r\nxx",
|
||||
errorSentinel,
|
||||
},
|
||||
{
|
||||
// long value
|
||||
"$6\r\nfoobarx\r\n",
|
||||
errorSentinel,
|
||||
},
|
||||
}
|
||||
|
||||
func TestRead(t *testing.T) {
|
||||
for _, tt := range readTests {
|
||||
c, _ := redis.Dial("", "", dialTestConn(strings.NewReader(tt.reply), nil))
|
||||
actual, err := c.Receive()
|
||||
if tt.expected == errorSentinel {
|
||||
if err == nil {
|
||||
t.Errorf("Receive(%q) did not return expected error", tt.reply)
|
||||
}
|
||||
} else {
|
||||
if err != nil {
|
||||
t.Errorf("Receive(%q) returned error %v", tt.reply, err)
|
||||
continue
|
||||
}
|
||||
if !reflect.DeepEqual(actual, tt.expected) {
|
||||
t.Errorf("Receive(%q) = %v, want %v", tt.reply, actual, tt.expected)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var testCommands = []struct {
|
||||
args []interface{}
|
||||
expected interface{}
|
||||
}{
|
||||
{
|
||||
[]interface{}{"PING"},
|
||||
"PONG",
|
||||
},
|
||||
{
|
||||
[]interface{}{"SET", "foo", "bar"},
|
||||
"OK",
|
||||
},
|
||||
{
|
||||
[]interface{}{"GET", "foo"},
|
||||
[]byte("bar"),
|
||||
},
|
||||
{
|
||||
[]interface{}{"GET", "nokey"},
|
||||
nil,
|
||||
},
|
||||
{
|
||||
[]interface{}{"MGET", "nokey", "foo"},
|
||||
[]interface{}{nil, []byte("bar")},
|
||||
},
|
||||
{
|
||||
[]interface{}{"INCR", "mycounter"},
|
||||
int64(1),
|
||||
},
|
||||
{
|
||||
[]interface{}{"LPUSH", "mylist", "foo"},
|
||||
int64(1),
|
||||
},
|
||||
{
|
||||
[]interface{}{"LPUSH", "mylist", "bar"},
|
||||
int64(2),
|
||||
},
|
||||
{
|
||||
[]interface{}{"LRANGE", "mylist", 0, -1},
|
||||
[]interface{}{[]byte("bar"), []byte("foo")},
|
||||
},
|
||||
{
|
||||
[]interface{}{"MULTI"},
|
||||
"OK",
|
||||
},
|
||||
{
|
||||
[]interface{}{"LRANGE", "mylist", 0, -1},
|
||||
"QUEUED",
|
||||
},
|
||||
{
|
||||
[]interface{}{"PING"},
|
||||
"QUEUED",
|
||||
},
|
||||
{
|
||||
[]interface{}{"EXEC"},
|
||||
[]interface{}{
|
||||
[]interface{}{[]byte("bar"), []byte("foo")},
|
||||
"PONG",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
func TestDoCommands(t *testing.T) {
|
||||
c, err := redis.DialDefaultServer()
|
||||
if err != nil {
|
||||
t.Fatalf("error connection to database, %v", err)
|
||||
}
|
||||
defer c.Close()
|
||||
|
||||
for _, cmd := range testCommands {
|
||||
actual, err := c.Do(cmd.args[0].(string), cmd.args[1:]...)
|
||||
if err != nil {
|
||||
t.Errorf("Do(%v) returned error %v", cmd.args, err)
|
||||
continue
|
||||
}
|
||||
if !reflect.DeepEqual(actual, cmd.expected) {
|
||||
t.Errorf("Do(%v) = %v, want %v", cmd.args, actual, cmd.expected)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestPipelineCommands(t *testing.T) {
|
||||
c, err := redis.DialDefaultServer()
|
||||
if err != nil {
|
||||
t.Fatalf("error connection to database, %v", err)
|
||||
}
|
||||
defer c.Close()
|
||||
|
||||
for _, cmd := range testCommands {
|
||||
if err := c.Send(cmd.args[0].(string), cmd.args[1:]...); err != nil {
|
||||
t.Fatalf("Send(%v) returned error %v", cmd.args, err)
|
||||
}
|
||||
}
|
||||
if err := c.Flush(); err != nil {
|
||||
t.Errorf("Flush() returned error %v", err)
|
||||
}
|
||||
for _, cmd := range testCommands {
|
||||
actual, err := c.Receive()
|
||||
if err != nil {
|
||||
t.Fatalf("Receive(%v) returned error %v", cmd.args, err)
|
||||
}
|
||||
if !reflect.DeepEqual(actual, cmd.expected) {
|
||||
t.Errorf("Receive(%v) = %v, want %v", cmd.args, actual, cmd.expected)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestBlankCommmand(t *testing.T) {
|
||||
c, err := redis.DialDefaultServer()
|
||||
if err != nil {
|
||||
t.Fatalf("error connection to database, %v", err)
|
||||
}
|
||||
defer c.Close()
|
||||
|
||||
for _, cmd := range testCommands {
|
||||
if err := c.Send(cmd.args[0].(string), cmd.args[1:]...); err != nil {
|
||||
t.Fatalf("Send(%v) returned error %v", cmd.args, err)
|
||||
}
|
||||
}
|
||||
reply, err := redis.Values(c.Do(""))
|
||||
if err != nil {
|
||||
t.Fatalf("Do() returned error %v", err)
|
||||
}
|
||||
if len(reply) != len(testCommands) {
|
||||
t.Fatalf("len(reply)=%d, want %d", len(reply), len(testCommands))
|
||||
}
|
||||
for i, cmd := range testCommands {
|
||||
actual := reply[i]
|
||||
if !reflect.DeepEqual(actual, cmd.expected) {
|
||||
t.Errorf("Receive(%v) = %v, want %v", cmd.args, actual, cmd.expected)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestRecvBeforeSend(t *testing.T) {
|
||||
c, err := redis.DialDefaultServer()
|
||||
if err != nil {
|
||||
t.Fatalf("error connection to database, %v", err)
|
||||
}
|
||||
defer c.Close()
|
||||
done := make(chan struct{})
|
||||
go func() {
|
||||
c.Receive()
|
||||
close(done)
|
||||
}()
|
||||
time.Sleep(time.Millisecond)
|
||||
c.Send("PING")
|
||||
c.Flush()
|
||||
<-done
|
||||
_, err = c.Do("")
|
||||
if err != nil {
|
||||
t.Fatalf("error=%v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestError(t *testing.T) {
|
||||
c, err := redis.DialDefaultServer()
|
||||
if err != nil {
|
||||
t.Fatalf("error connection to database, %v", err)
|
||||
}
|
||||
defer c.Close()
|
||||
|
||||
c.Do("SET", "key", "val")
|
||||
_, err = c.Do("HSET", "key", "fld", "val")
|
||||
if err == nil {
|
||||
t.Errorf("Expected err for HSET on string key.")
|
||||
}
|
||||
if c.Err() != nil {
|
||||
t.Errorf("Conn has Err()=%v, expect nil", c.Err())
|
||||
}
|
||||
_, err = c.Do("SET", "key", "val")
|
||||
if err != nil {
|
||||
t.Errorf("Do(SET, key, val) returned error %v, expected nil.", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestReadTimeout(t *testing.T) {
|
||||
l, err := net.Listen("tcp", "127.0.0.1:0")
|
||||
if err != nil {
|
||||
t.Fatalf("net.Listen returned %v", err)
|
||||
}
|
||||
defer l.Close()
|
||||
|
||||
go func() {
|
||||
for {
|
||||
c, err := l.Accept()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
go func() {
|
||||
time.Sleep(time.Second)
|
||||
c.Write([]byte("+OK\r\n"))
|
||||
c.Close()
|
||||
}()
|
||||
}
|
||||
}()
|
||||
|
||||
// Do
|
||||
|
||||
c1, err := redis.Dial(l.Addr().Network(), l.Addr().String(), redis.DialReadTimeout(time.Millisecond))
|
||||
if err != nil {
|
||||
t.Fatalf("redis.Dial returned %v", err)
|
||||
}
|
||||
defer c1.Close()
|
||||
|
||||
_, err = c1.Do("PING")
|
||||
if err == nil {
|
||||
t.Fatalf("c1.Do() returned nil, expect error")
|
||||
}
|
||||
if c1.Err() == nil {
|
||||
t.Fatalf("c1.Err() = nil, expect error")
|
||||
}
|
||||
|
||||
// Send/Flush/Receive
|
||||
|
||||
c2, err := redis.Dial(l.Addr().Network(), l.Addr().String(), redis.DialReadTimeout(time.Millisecond))
|
||||
if err != nil {
|
||||
t.Fatalf("redis.Dial returned %v", err)
|
||||
}
|
||||
defer c2.Close()
|
||||
|
||||
c2.Send("PING")
|
||||
c2.Flush()
|
||||
_, err = c2.Receive()
|
||||
if err == nil {
|
||||
t.Fatalf("c2.Receive() returned nil, expect error")
|
||||
}
|
||||
if c2.Err() == nil {
|
||||
t.Fatalf("c2.Err() = nil, expect error")
|
||||
}
|
||||
}
|
||||
|
||||
var dialErrors = []struct {
|
||||
rawurl string
|
||||
expectedError string
|
||||
}{
|
||||
{
|
||||
"localhost",
|
||||
"invalid redis URL scheme",
|
||||
},
|
||||
// The error message for invalid hosts is diffferent in different
|
||||
// versions of Go, so just check that there is an error message.
|
||||
{
|
||||
"redis://weird url",
|
||||
"",
|
||||
},
|
||||
{
|
||||
"redis://foo:bar:baz",
|
||||
"",
|
||||
},
|
||||
{
|
||||
"http://www.google.com",
|
||||
"invalid redis URL scheme: http",
|
||||
},
|
||||
{
|
||||
"redis://localhost:6379/abc123",
|
||||
"invalid database: abc123",
|
||||
},
|
||||
}
|
||||
|
||||
func TestDialURLErrors(t *testing.T) {
|
||||
for _, d := range dialErrors {
|
||||
_, err := redis.DialURL(d.rawurl)
|
||||
if err == nil || !strings.Contains(err.Error(), d.expectedError) {
|
||||
t.Errorf("DialURL did not return expected error (expected %v to contain %s)", err, d.expectedError)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestDialURLPort(t *testing.T) {
|
||||
checkPort := func(network, address string) (net.Conn, error) {
|
||||
if address != "localhost:6379" {
|
||||
t.Errorf("DialURL did not set port to 6379 by default (got %v)", address)
|
||||
}
|
||||
return nil, nil
|
||||
}
|
||||
_, err := redis.DialURL("redis://localhost", redis.DialNetDial(checkPort))
|
||||
if err != nil {
|
||||
t.Error("dial error:", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestDialURLHost(t *testing.T) {
|
||||
checkHost := func(network, address string) (net.Conn, error) {
|
||||
if address != "localhost:6379" {
|
||||
t.Errorf("DialURL did not set host to localhost by default (got %v)", address)
|
||||
}
|
||||
return nil, nil
|
||||
}
|
||||
_, err := redis.DialURL("redis://:6379", redis.DialNetDial(checkHost))
|
||||
if err != nil {
|
||||
t.Error("dial error:", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestDialURLPassword(t *testing.T) {
|
||||
var buf bytes.Buffer
|
||||
_, err := redis.DialURL("redis://x:abc123@localhost", dialTestConn(strings.NewReader("+OK\r\n"), &buf))
|
||||
if err != nil {
|
||||
t.Error("dial error:", err)
|
||||
}
|
||||
expected := "*2\r\n$4\r\nAUTH\r\n$6\r\nabc123\r\n"
|
||||
actual := buf.String()
|
||||
if actual != expected {
|
||||
t.Errorf("commands = %q, want %q", actual, expected)
|
||||
}
|
||||
}
|
||||
|
||||
func TestDialURLDatabase(t *testing.T) {
|
||||
var buf3 bytes.Buffer
|
||||
_, err3 := redis.DialURL("redis://localhost/3", dialTestConn(strings.NewReader("+OK\r\n"), &buf3))
|
||||
if err3 != nil {
|
||||
t.Error("dial error:", err3)
|
||||
}
|
||||
expected3 := "*2\r\n$6\r\nSELECT\r\n$1\r\n3\r\n"
|
||||
actual3 := buf3.String()
|
||||
if actual3 != expected3 {
|
||||
t.Errorf("commands = %q, want %q", actual3, expected3)
|
||||
}
|
||||
// empty DB means 0
|
||||
var buf0 bytes.Buffer
|
||||
_, err0 := redis.DialURL("redis://localhost/", dialTestConn(strings.NewReader("+OK\r\n"), &buf0))
|
||||
if err0 != nil {
|
||||
t.Error("dial error:", err0)
|
||||
}
|
||||
expected0 := ""
|
||||
actual0 := buf0.String()
|
||||
if actual0 != expected0 {
|
||||
t.Errorf("commands = %q, want %q", actual0, expected0)
|
||||
}
|
||||
}
|
||||
|
||||
// Connect to local instance of Redis running on the default port.
|
||||
func ExampleDial() {
|
||||
c, err := redis.Dial("tcp", ":6379")
|
||||
if err != nil {
|
||||
// handle error
|
||||
}
|
||||
defer c.Close()
|
||||
}
|
||||
|
||||
// Connect to remote instance of Redis using a URL.
|
||||
func ExampleDialURL() {
|
||||
c, err := redis.DialURL(os.Getenv("REDIS_URL"))
|
||||
if err != nil {
|
||||
// handle connection error
|
||||
}
|
||||
defer c.Close()
|
||||
}
|
||||
|
||||
// TextExecError tests handling of errors in a transaction. See
|
||||
// http://redis.io/topics/transactions for information on how Redis handles
|
||||
// errors in a transaction.
|
||||
func TestExecError(t *testing.T) {
|
||||
c, err := redis.DialDefaultServer()
|
||||
if err != nil {
|
||||
t.Fatalf("error connection to database, %v", err)
|
||||
}
|
||||
defer c.Close()
|
||||
|
||||
// Execute commands that fail before EXEC is called.
|
||||
|
||||
c.Do("DEL", "k0")
|
||||
c.Do("ZADD", "k0", 0, 0)
|
||||
c.Send("MULTI")
|
||||
c.Send("NOTACOMMAND", "k0", 0, 0)
|
||||
c.Send("ZINCRBY", "k0", 0, 0)
|
||||
v, err := c.Do("EXEC")
|
||||
if err == nil {
|
||||
t.Fatalf("EXEC returned values %v, expected error", v)
|
||||
}
|
||||
|
||||
// Execute commands that fail after EXEC is called. The first command
|
||||
// returns an error.
|
||||
|
||||
c.Do("DEL", "k1")
|
||||
c.Do("ZADD", "k1", 0, 0)
|
||||
c.Send("MULTI")
|
||||
c.Send("HSET", "k1", 0, 0)
|
||||
c.Send("ZINCRBY", "k1", 0, 0)
|
||||
v, err = c.Do("EXEC")
|
||||
if err != nil {
|
||||
t.Fatalf("EXEC returned error %v", err)
|
||||
}
|
||||
|
||||
vs, err := redis.Values(v, nil)
|
||||
if err != nil {
|
||||
t.Fatalf("Values(v) returned error %v", err)
|
||||
}
|
||||
|
||||
if len(vs) != 2 {
|
||||
t.Fatalf("len(vs) == %d, want 2", len(vs))
|
||||
}
|
||||
|
||||
if _, ok := vs[0].(error); !ok {
|
||||
t.Fatalf("first result is type %T, expected error", vs[0])
|
||||
}
|
||||
|
||||
if _, ok := vs[1].([]byte); !ok {
|
||||
t.Fatalf("second result is type %T, expected []byte", vs[1])
|
||||
}
|
||||
|
||||
// Execute commands that fail after EXEC is called. The second command
|
||||
// returns an error.
|
||||
|
||||
c.Do("ZADD", "k2", 0, 0)
|
||||
c.Send("MULTI")
|
||||
c.Send("ZINCRBY", "k2", 0, 0)
|
||||
c.Send("HSET", "k2", 0, 0)
|
||||
v, err = c.Do("EXEC")
|
||||
if err != nil {
|
||||
t.Fatalf("EXEC returned error %v", err)
|
||||
}
|
||||
|
||||
vs, err = redis.Values(v, nil)
|
||||
if err != nil {
|
||||
t.Fatalf("Values(v) returned error %v", err)
|
||||
}
|
||||
|
||||
if len(vs) != 2 {
|
||||
t.Fatalf("len(vs) == %d, want 2", len(vs))
|
||||
}
|
||||
|
||||
if _, ok := vs[0].([]byte); !ok {
|
||||
t.Fatalf("first result is type %T, expected []byte", vs[0])
|
||||
}
|
||||
|
||||
if _, ok := vs[1].(error); !ok {
|
||||
t.Fatalf("second result is type %T, expected error", vs[2])
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkDoEmpty(b *testing.B) {
|
||||
b.StopTimer()
|
||||
c, err := redis.DialDefaultServer()
|
||||
if err != nil {
|
||||
b.Fatal(err)
|
||||
}
|
||||
defer c.Close()
|
||||
b.StartTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
if _, err := c.Do(""); err != nil {
|
||||
b.Fatal(err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkDoPing(b *testing.B) {
|
||||
b.StopTimer()
|
||||
c, err := redis.DialDefaultServer()
|
||||
if err != nil {
|
||||
b.Fatal(err)
|
||||
}
|
||||
defer c.Close()
|
||||
b.StartTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
if _, err := c.Do("PING"); err != nil {
|
||||
b.Fatal(err)
|
||||
}
|
||||
}
|
||||
}
|
169
vendor/github.com/garyburd/redigo/redis/doc.go
generated
vendored
Normal file
169
vendor/github.com/garyburd/redigo/redis/doc.go
generated
vendored
Normal file
@@ -0,0 +1,169 @@
|
||||
// Copyright 2012 Gary Burd
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License"): you may
|
||||
// not use this file except in compliance with the License. You may obtain
|
||||
// a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
// License for the specific language governing permissions and limitations
|
||||
// under the License.
|
||||
|
||||
// Package redis is a client for the Redis database.
|
||||
//
|
||||
// The Redigo FAQ (https://github.com/garyburd/redigo/wiki/FAQ) contains more
|
||||
// documentation about this package.
|
||||
//
|
||||
// Connections
|
||||
//
|
||||
// The Conn interface is the primary interface for working with Redis.
|
||||
// Applications create connections by calling the Dial, DialWithTimeout or
|
||||
// NewConn functions. In the future, functions will be added for creating
|
||||
// sharded and other types of connections.
|
||||
//
|
||||
// The application must call the connection Close method when the application
|
||||
// is done with the connection.
|
||||
//
|
||||
// Executing Commands
|
||||
//
|
||||
// The Conn interface has a generic method for executing Redis commands:
|
||||
//
|
||||
// Do(commandName string, args ...interface{}) (reply interface{}, err error)
|
||||
//
|
||||
// The Redis command reference (http://redis.io/commands) lists the available
|
||||
// commands. An example of using the Redis APPEND command is:
|
||||
//
|
||||
// n, err := conn.Do("APPEND", "key", "value")
|
||||
//
|
||||
// The Do method converts command arguments to binary strings for transmission
|
||||
// to the server as follows:
|
||||
//
|
||||
// Go Type Conversion
|
||||
// []byte Sent as is
|
||||
// string Sent as is
|
||||
// int, int64 strconv.FormatInt(v)
|
||||
// float64 strconv.FormatFloat(v, 'g', -1, 64)
|
||||
// bool true -> "1", false -> "0"
|
||||
// nil ""
|
||||
// all other types fmt.Print(v)
|
||||
//
|
||||
// Redis command reply types are represented using the following Go types:
|
||||
//
|
||||
// Redis type Go type
|
||||
// error redis.Error
|
||||
// integer int64
|
||||
// simple string string
|
||||
// bulk string []byte or nil if value not present.
|
||||
// array []interface{} or nil if value not present.
|
||||
//
|
||||
// Use type assertions or the reply helper functions to convert from
|
||||
// interface{} to the specific Go type for the command result.
|
||||
//
|
||||
// Pipelining
|
||||
//
|
||||
// Connections support pipelining using the Send, Flush and Receive methods.
|
||||
//
|
||||
// Send(commandName string, args ...interface{}) error
|
||||
// Flush() error
|
||||
// Receive() (reply interface{}, err error)
|
||||
//
|
||||
// Send writes the command to the connection's output buffer. Flush flushes the
|
||||
// connection's output buffer to the server. Receive reads a single reply from
|
||||
// the server. The following example shows a simple pipeline.
|
||||
//
|
||||
// c.Send("SET", "foo", "bar")
|
||||
// c.Send("GET", "foo")
|
||||
// c.Flush()
|
||||
// c.Receive() // reply from SET
|
||||
// v, err = c.Receive() // reply from GET
|
||||
//
|
||||
// The Do method combines the functionality of the Send, Flush and Receive
|
||||
// methods. The Do method starts by writing the command and flushing the output
|
||||
// buffer. Next, the Do method receives all pending replies including the reply
|
||||
// for the command just sent by Do. If any of the received replies is an error,
|
||||
// then Do returns the error. If there are no errors, then Do returns the last
|
||||
// reply. If the command argument to the Do method is "", then the Do method
|
||||
// will flush the output buffer and receive pending replies without sending a
|
||||
// command.
|
||||
//
|
||||
// Use the Send and Do methods to implement pipelined transactions.
|
||||
//
|
||||
// c.Send("MULTI")
|
||||
// c.Send("INCR", "foo")
|
||||
// c.Send("INCR", "bar")
|
||||
// r, err := c.Do("EXEC")
|
||||
// fmt.Println(r) // prints [1, 1]
|
||||
//
|
||||
// Concurrency
|
||||
//
|
||||
// Connections do not support concurrent calls to the write methods (Send,
|
||||
// Flush) or concurrent calls to the read method (Receive). Connections do
|
||||
// allow a concurrent reader and writer.
|
||||
//
|
||||
// Because the Do method combines the functionality of Send, Flush and Receive,
|
||||
// the Do method cannot be called concurrently with the other methods.
|
||||
//
|
||||
// For full concurrent access to Redis, use the thread-safe Pool to get and
|
||||
// release connections from within a goroutine.
|
||||
//
|
||||
// Publish and Subscribe
|
||||
//
|
||||
// Use the Send, Flush and Receive methods to implement Pub/Sub subscribers.
|
||||
//
|
||||
// c.Send("SUBSCRIBE", "example")
|
||||
// c.Flush()
|
||||
// for {
|
||||
// reply, err := c.Receive()
|
||||
// if err != nil {
|
||||
// return err
|
||||
// }
|
||||
// // process pushed message
|
||||
// }
|
||||
//
|
||||
// The PubSubConn type wraps a Conn with convenience methods for implementing
|
||||
// subscribers. The Subscribe, PSubscribe, Unsubscribe and PUnsubscribe methods
|
||||
// send and flush a subscription management command. The receive method
|
||||
// converts a pushed message to convenient types for use in a type switch.
|
||||
//
|
||||
// psc := redis.PubSubConn{c}
|
||||
// psc.Subscribe("example")
|
||||
// for {
|
||||
// switch v := psc.Receive().(type) {
|
||||
// case redis.Message:
|
||||
// fmt.Printf("%s: message: %s\n", v.Channel, v.Data)
|
||||
// case redis.Subscription:
|
||||
// fmt.Printf("%s: %s %d\n", v.Channel, v.Kind, v.Count)
|
||||
// case error:
|
||||
// return v
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// Reply Helpers
|
||||
//
|
||||
// The Bool, Int, Bytes, String, Strings and Values functions convert a reply
|
||||
// to a value of a specific type. To allow convenient wrapping of calls to the
|
||||
// connection Do and Receive methods, the functions take a second argument of
|
||||
// type error. If the error is non-nil, then the helper function returns the
|
||||
// error. If the error is nil, the function converts the reply to the specified
|
||||
// type:
|
||||
//
|
||||
// exists, err := redis.Bool(c.Do("EXISTS", "foo"))
|
||||
// if err != nil {
|
||||
// // handle error return from c.Do or type conversion error.
|
||||
// }
|
||||
//
|
||||
// The Scan function converts elements of a array reply to Go types:
|
||||
//
|
||||
// var value1 int
|
||||
// var value2 string
|
||||
// reply, err := redis.Values(c.Do("MGET", "key1", "key2"))
|
||||
// if err != nil {
|
||||
// // handle error
|
||||
// }
|
||||
// if _, err := redis.Scan(reply, &value1, &value2); err != nil {
|
||||
// // handle error
|
||||
// }
|
||||
package redis // import "github.com/garyburd/redigo/redis"
|
117
vendor/github.com/garyburd/redigo/redis/log.go
generated
vendored
Normal file
117
vendor/github.com/garyburd/redigo/redis/log.go
generated
vendored
Normal file
@@ -0,0 +1,117 @@
|
||||
// Copyright 2012 Gary Burd
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License"): you may
|
||||
// not use this file except in compliance with the License. You may obtain
|
||||
// a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
// License for the specific language governing permissions and limitations
|
||||
// under the License.
|
||||
|
||||
package redis
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"log"
|
||||
)
|
||||
|
||||
// NewLoggingConn returns a logging wrapper around a connection.
|
||||
func NewLoggingConn(conn Conn, logger *log.Logger, prefix string) Conn {
|
||||
if prefix != "" {
|
||||
prefix = prefix + "."
|
||||
}
|
||||
return &loggingConn{conn, logger, prefix}
|
||||
}
|
||||
|
||||
type loggingConn struct {
|
||||
Conn
|
||||
logger *log.Logger
|
||||
prefix string
|
||||
}
|
||||
|
||||
func (c *loggingConn) Close() error {
|
||||
err := c.Conn.Close()
|
||||
var buf bytes.Buffer
|
||||
fmt.Fprintf(&buf, "%sClose() -> (%v)", c.prefix, err)
|
||||
c.logger.Output(2, buf.String())
|
||||
return err
|
||||
}
|
||||
|
||||
func (c *loggingConn) printValue(buf *bytes.Buffer, v interface{}) {
|
||||
const chop = 32
|
||||
switch v := v.(type) {
|
||||
case []byte:
|
||||
if len(v) > chop {
|
||||
fmt.Fprintf(buf, "%q...", v[:chop])
|
||||
} else {
|
||||
fmt.Fprintf(buf, "%q", v)
|
||||
}
|
||||
case string:
|
||||
if len(v) > chop {
|
||||
fmt.Fprintf(buf, "%q...", v[:chop])
|
||||
} else {
|
||||
fmt.Fprintf(buf, "%q", v)
|
||||
}
|
||||
case []interface{}:
|
||||
if len(v) == 0 {
|
||||
buf.WriteString("[]")
|
||||
} else {
|
||||
sep := "["
|
||||
fin := "]"
|
||||
if len(v) > chop {
|
||||
v = v[:chop]
|
||||
fin = "...]"
|
||||
}
|
||||
for _, vv := range v {
|
||||
buf.WriteString(sep)
|
||||
c.printValue(buf, vv)
|
||||
sep = ", "
|
||||
}
|
||||
buf.WriteString(fin)
|
||||
}
|
||||
default:
|
||||
fmt.Fprint(buf, v)
|
||||
}
|
||||
}
|
||||
|
||||
func (c *loggingConn) print(method, commandName string, args []interface{}, reply interface{}, err error) {
|
||||
var buf bytes.Buffer
|
||||
fmt.Fprintf(&buf, "%s%s(", c.prefix, method)
|
||||
if method != "Receive" {
|
||||
buf.WriteString(commandName)
|
||||
for _, arg := range args {
|
||||
buf.WriteString(", ")
|
||||
c.printValue(&buf, arg)
|
||||
}
|
||||
}
|
||||
buf.WriteString(") -> (")
|
||||
if method != "Send" {
|
||||
c.printValue(&buf, reply)
|
||||
buf.WriteString(", ")
|
||||
}
|
||||
fmt.Fprintf(&buf, "%v)", err)
|
||||
c.logger.Output(3, buf.String())
|
||||
}
|
||||
|
||||
func (c *loggingConn) Do(commandName string, args ...interface{}) (interface{}, error) {
|
||||
reply, err := c.Conn.Do(commandName, args...)
|
||||
c.print("Do", commandName, args, reply, err)
|
||||
return reply, err
|
||||
}
|
||||
|
||||
func (c *loggingConn) Send(commandName string, args ...interface{}) error {
|
||||
err := c.Conn.Send(commandName, args...)
|
||||
c.print("Send", commandName, args, nil, err)
|
||||
return err
|
||||
}
|
||||
|
||||
func (c *loggingConn) Receive() (interface{}, error) {
|
||||
reply, err := c.Conn.Receive()
|
||||
c.print("Receive", "", nil, reply, err)
|
||||
return reply, err
|
||||
}
|
393
vendor/github.com/garyburd/redigo/redis/pool.go
generated
vendored
Normal file
393
vendor/github.com/garyburd/redigo/redis/pool.go
generated
vendored
Normal file
@@ -0,0 +1,393 @@
|
||||
// Copyright 2012 Gary Burd
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License"): you may
|
||||
// not use this file except in compliance with the License. You may obtain
|
||||
// a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
// License for the specific language governing permissions and limitations
|
||||
// under the License.
|
||||
|
||||
package redis
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"container/list"
|
||||
"crypto/rand"
|
||||
"crypto/sha1"
|
||||
"errors"
|
||||
"io"
|
||||
"strconv"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/garyburd/redigo/internal"
|
||||
)
|
||||
|
||||
var nowFunc = time.Now // for testing
|
||||
|
||||
// ErrPoolExhausted is returned from a pool connection method (Do, Send,
|
||||
// Receive, Flush, Err) when the maximum number of database connections in the
|
||||
// pool has been reached.
|
||||
var ErrPoolExhausted = errors.New("redigo: connection pool exhausted")
|
||||
|
||||
var (
|
||||
errPoolClosed = errors.New("redigo: connection pool closed")
|
||||
errConnClosed = errors.New("redigo: connection closed")
|
||||
)
|
||||
|
||||
// Pool maintains a pool of connections. The application calls the Get method
|
||||
// to get a connection from the pool and the connection's Close method to
|
||||
// return the connection's resources to the pool.
|
||||
//
|
||||
// The following example shows how to use a pool in a web application. The
|
||||
// application creates a pool at application startup and makes it available to
|
||||
// request handlers using a global variable.
|
||||
//
|
||||
// func newPool(server, password string) *redis.Pool {
|
||||
// return &redis.Pool{
|
||||
// MaxIdle: 3,
|
||||
// IdleTimeout: 240 * time.Second,
|
||||
// Dial: func () (redis.Conn, error) {
|
||||
// c, err := redis.Dial("tcp", server)
|
||||
// if err != nil {
|
||||
// return nil, err
|
||||
// }
|
||||
// if _, err := c.Do("AUTH", password); err != nil {
|
||||
// c.Close()
|
||||
// return nil, err
|
||||
// }
|
||||
// return c, err
|
||||
// },
|
||||
// TestOnBorrow: func(c redis.Conn, t time.Time) error {
|
||||
// _, err := c.Do("PING")
|
||||
// return err
|
||||
// },
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// var (
|
||||
// pool *redis.Pool
|
||||
// redisServer = flag.String("redisServer", ":6379", "")
|
||||
// redisPassword = flag.String("redisPassword", "", "")
|
||||
// )
|
||||
//
|
||||
// func main() {
|
||||
// flag.Parse()
|
||||
// pool = newPool(*redisServer, *redisPassword)
|
||||
// ...
|
||||
// }
|
||||
//
|
||||
// A request handler gets a connection from the pool and closes the connection
|
||||
// when the handler is done:
|
||||
//
|
||||
// func serveHome(w http.ResponseWriter, r *http.Request) {
|
||||
// conn := pool.Get()
|
||||
// defer conn.Close()
|
||||
// ....
|
||||
// }
|
||||
//
|
||||
type Pool struct {
|
||||
|
||||
// Dial is an application supplied function for creating and configuring a
|
||||
// connection.
|
||||
//
|
||||
// The connection returned from Dial must not be in a special state
|
||||
// (subscribed to pubsub channel, transaction started, ...).
|
||||
Dial func() (Conn, error)
|
||||
|
||||
// TestOnBorrow is an optional application supplied function for checking
|
||||
// the health of an idle connection before the connection is used again by
|
||||
// the application. Argument t is the time that the connection was returned
|
||||
// to the pool. If the function returns an error, then the connection is
|
||||
// closed.
|
||||
TestOnBorrow func(c Conn, t time.Time) error
|
||||
|
||||
// Maximum number of idle connections in the pool.
|
||||
MaxIdle int
|
||||
|
||||
// Maximum number of connections allocated by the pool at a given time.
|
||||
// When zero, there is no limit on the number of connections in the pool.
|
||||
MaxActive int
|
||||
|
||||
// Close connections after remaining idle for this duration. If the value
|
||||
// is zero, then idle connections are not closed. Applications should set
|
||||
// the timeout to a value less than the server's timeout.
|
||||
IdleTimeout time.Duration
|
||||
|
||||
// If Wait is true and the pool is at the MaxActive limit, then Get() waits
|
||||
// for a connection to be returned to the pool before returning.
|
||||
Wait bool
|
||||
|
||||
// mu protects fields defined below.
|
||||
mu sync.Mutex
|
||||
cond *sync.Cond
|
||||
closed bool
|
||||
active int
|
||||
|
||||
// Stack of idleConn with most recently used at the front.
|
||||
idle list.List
|
||||
}
|
||||
|
||||
type idleConn struct {
|
||||
c Conn
|
||||
t time.Time
|
||||
}
|
||||
|
||||
// NewPool creates a new pool.
|
||||
//
|
||||
// Deprecated: Initialize the Pool directory as shown in the example.
|
||||
func NewPool(newFn func() (Conn, error), maxIdle int) *Pool {
|
||||
return &Pool{Dial: newFn, MaxIdle: maxIdle}
|
||||
}
|
||||
|
||||
// Get gets a connection. The application must close the returned connection.
|
||||
// This method always returns a valid connection so that applications can defer
|
||||
// error handling to the first use of the connection. If there is an error
|
||||
// getting an underlying connection, then the connection Err, Do, Send, Flush
|
||||
// and Receive methods return that error.
|
||||
func (p *Pool) Get() Conn {
|
||||
c, err := p.get()
|
||||
if err != nil {
|
||||
return errorConnection{err}
|
||||
}
|
||||
return &pooledConnection{p: p, c: c}
|
||||
}
|
||||
|
||||
// ActiveCount returns the number of active connections in the pool.
|
||||
func (p *Pool) ActiveCount() int {
|
||||
p.mu.Lock()
|
||||
active := p.active
|
||||
p.mu.Unlock()
|
||||
return active
|
||||
}
|
||||
|
||||
// Close releases the resources used by the pool.
|
||||
func (p *Pool) Close() error {
|
||||
p.mu.Lock()
|
||||
idle := p.idle
|
||||
p.idle.Init()
|
||||
p.closed = true
|
||||
p.active -= idle.Len()
|
||||
if p.cond != nil {
|
||||
p.cond.Broadcast()
|
||||
}
|
||||
p.mu.Unlock()
|
||||
for e := idle.Front(); e != nil; e = e.Next() {
|
||||
e.Value.(idleConn).c.Close()
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// release decrements the active count and signals waiters. The caller must
|
||||
// hold p.mu during the call.
|
||||
func (p *Pool) release() {
|
||||
p.active -= 1
|
||||
if p.cond != nil {
|
||||
p.cond.Signal()
|
||||
}
|
||||
}
|
||||
|
||||
// get prunes stale connections and returns a connection from the idle list or
|
||||
// creates a new connection.
|
||||
func (p *Pool) get() (Conn, error) {
|
||||
p.mu.Lock()
|
||||
|
||||
// Prune stale connections.
|
||||
|
||||
if timeout := p.IdleTimeout; timeout > 0 {
|
||||
for i, n := 0, p.idle.Len(); i < n; i++ {
|
||||
e := p.idle.Back()
|
||||
if e == nil {
|
||||
break
|
||||
}
|
||||
ic := e.Value.(idleConn)
|
||||
if ic.t.Add(timeout).After(nowFunc()) {
|
||||
break
|
||||
}
|
||||
p.idle.Remove(e)
|
||||
p.release()
|
||||
p.mu.Unlock()
|
||||
ic.c.Close()
|
||||
p.mu.Lock()
|
||||
}
|
||||
}
|
||||
|
||||
for {
|
||||
|
||||
// Get idle connection.
|
||||
|
||||
for i, n := 0, p.idle.Len(); i < n; i++ {
|
||||
e := p.idle.Front()
|
||||
if e == nil {
|
||||
break
|
||||
}
|
||||
ic := e.Value.(idleConn)
|
||||
p.idle.Remove(e)
|
||||
test := p.TestOnBorrow
|
||||
p.mu.Unlock()
|
||||
if test == nil || test(ic.c, ic.t) == nil {
|
||||
return ic.c, nil
|
||||
}
|
||||
ic.c.Close()
|
||||
p.mu.Lock()
|
||||
p.release()
|
||||
}
|
||||
|
||||
// Check for pool closed before dialing a new connection.
|
||||
|
||||
if p.closed {
|
||||
p.mu.Unlock()
|
||||
return nil, errors.New("redigo: get on closed pool")
|
||||
}
|
||||
|
||||
// Dial new connection if under limit.
|
||||
|
||||
if p.MaxActive == 0 || p.active < p.MaxActive {
|
||||
dial := p.Dial
|
||||
p.active += 1
|
||||
p.mu.Unlock()
|
||||
c, err := dial()
|
||||
if err != nil {
|
||||
p.mu.Lock()
|
||||
p.release()
|
||||
p.mu.Unlock()
|
||||
c = nil
|
||||
}
|
||||
return c, err
|
||||
}
|
||||
|
||||
if !p.Wait {
|
||||
p.mu.Unlock()
|
||||
return nil, ErrPoolExhausted
|
||||
}
|
||||
|
||||
if p.cond == nil {
|
||||
p.cond = sync.NewCond(&p.mu)
|
||||
}
|
||||
p.cond.Wait()
|
||||
}
|
||||
}
|
||||
|
||||
func (p *Pool) put(c Conn, forceClose bool) error {
|
||||
err := c.Err()
|
||||
p.mu.Lock()
|
||||
if !p.closed && err == nil && !forceClose {
|
||||
p.idle.PushFront(idleConn{t: nowFunc(), c: c})
|
||||
if p.idle.Len() > p.MaxIdle {
|
||||
c = p.idle.Remove(p.idle.Back()).(idleConn).c
|
||||
} else {
|
||||
c = nil
|
||||
}
|
||||
}
|
||||
|
||||
if c == nil {
|
||||
if p.cond != nil {
|
||||
p.cond.Signal()
|
||||
}
|
||||
p.mu.Unlock()
|
||||
return nil
|
||||
}
|
||||
|
||||
p.release()
|
||||
p.mu.Unlock()
|
||||
return c.Close()
|
||||
}
|
||||
|
||||
type pooledConnection struct {
|
||||
p *Pool
|
||||
c Conn
|
||||
state int
|
||||
}
|
||||
|
||||
var (
|
||||
sentinel []byte
|
||||
sentinelOnce sync.Once
|
||||
)
|
||||
|
||||
func initSentinel() {
|
||||
p := make([]byte, 64)
|
||||
if _, err := rand.Read(p); err == nil {
|
||||
sentinel = p
|
||||
} else {
|
||||
h := sha1.New()
|
||||
io.WriteString(h, "Oops, rand failed. Use time instead.")
|
||||
io.WriteString(h, strconv.FormatInt(time.Now().UnixNano(), 10))
|
||||
sentinel = h.Sum(nil)
|
||||
}
|
||||
}
|
||||
|
||||
func (pc *pooledConnection) Close() error {
|
||||
c := pc.c
|
||||
if _, ok := c.(errorConnection); ok {
|
||||
return nil
|
||||
}
|
||||
pc.c = errorConnection{errConnClosed}
|
||||
|
||||
if pc.state&internal.MultiState != 0 {
|
||||
c.Send("DISCARD")
|
||||
pc.state &^= (internal.MultiState | internal.WatchState)
|
||||
} else if pc.state&internal.WatchState != 0 {
|
||||
c.Send("UNWATCH")
|
||||
pc.state &^= internal.WatchState
|
||||
}
|
||||
if pc.state&internal.SubscribeState != 0 {
|
||||
c.Send("UNSUBSCRIBE")
|
||||
c.Send("PUNSUBSCRIBE")
|
||||
// To detect the end of the message stream, ask the server to echo
|
||||
// a sentinel value and read until we see that value.
|
||||
sentinelOnce.Do(initSentinel)
|
||||
c.Send("ECHO", sentinel)
|
||||
c.Flush()
|
||||
for {
|
||||
p, err := c.Receive()
|
||||
if err != nil {
|
||||
break
|
||||
}
|
||||
if p, ok := p.([]byte); ok && bytes.Equal(p, sentinel) {
|
||||
pc.state &^= internal.SubscribeState
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
c.Do("")
|
||||
pc.p.put(c, pc.state != 0)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (pc *pooledConnection) Err() error {
|
||||
return pc.c.Err()
|
||||
}
|
||||
|
||||
func (pc *pooledConnection) Do(commandName string, args ...interface{}) (reply interface{}, err error) {
|
||||
ci := internal.LookupCommandInfo(commandName)
|
||||
pc.state = (pc.state | ci.Set) &^ ci.Clear
|
||||
return pc.c.Do(commandName, args...)
|
||||
}
|
||||
|
||||
func (pc *pooledConnection) Send(commandName string, args ...interface{}) error {
|
||||
ci := internal.LookupCommandInfo(commandName)
|
||||
pc.state = (pc.state | ci.Set) &^ ci.Clear
|
||||
return pc.c.Send(commandName, args...)
|
||||
}
|
||||
|
||||
func (pc *pooledConnection) Flush() error {
|
||||
return pc.c.Flush()
|
||||
}
|
||||
|
||||
func (pc *pooledConnection) Receive() (reply interface{}, err error) {
|
||||
return pc.c.Receive()
|
||||
}
|
||||
|
||||
type errorConnection struct{ err error }
|
||||
|
||||
func (ec errorConnection) Do(string, ...interface{}) (interface{}, error) { return nil, ec.err }
|
||||
func (ec errorConnection) Send(string, ...interface{}) error { return ec.err }
|
||||
func (ec errorConnection) Err() error { return ec.err }
|
||||
func (ec errorConnection) Close() error { return ec.err }
|
||||
func (ec errorConnection) Flush() error { return ec.err }
|
||||
func (ec errorConnection) Receive() (interface{}, error) { return nil, ec.err }
|
684
vendor/github.com/garyburd/redigo/redis/pool_test.go
generated
vendored
Normal file
684
vendor/github.com/garyburd/redigo/redis/pool_test.go
generated
vendored
Normal file
@@ -0,0 +1,684 @@
|
||||
// Copyright 2011 Gary Burd
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License"): you may
|
||||
// not use this file except in compliance with the License. You may obtain
|
||||
// a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
// License for the specific language governing permissions and limitations
|
||||
// under the License.
|
||||
|
||||
package redis_test
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"io"
|
||||
"reflect"
|
||||
"sync"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/garyburd/redigo/redis"
|
||||
)
|
||||
|
||||
type poolTestConn struct {
|
||||
d *poolDialer
|
||||
err error
|
||||
redis.Conn
|
||||
}
|
||||
|
||||
func (c *poolTestConn) Close() error {
|
||||
c.d.mu.Lock()
|
||||
c.d.open -= 1
|
||||
c.d.mu.Unlock()
|
||||
return c.Conn.Close()
|
||||
}
|
||||
|
||||
func (c *poolTestConn) Err() error { return c.err }
|
||||
|
||||
func (c *poolTestConn) Do(commandName string, args ...interface{}) (interface{}, error) {
|
||||
if commandName == "ERR" {
|
||||
c.err = args[0].(error)
|
||||
commandName = "PING"
|
||||
}
|
||||
if commandName != "" {
|
||||
c.d.commands = append(c.d.commands, commandName)
|
||||
}
|
||||
return c.Conn.Do(commandName, args...)
|
||||
}
|
||||
|
||||
func (c *poolTestConn) Send(commandName string, args ...interface{}) error {
|
||||
c.d.commands = append(c.d.commands, commandName)
|
||||
return c.Conn.Send(commandName, args...)
|
||||
}
|
||||
|
||||
type poolDialer struct {
|
||||
mu sync.Mutex
|
||||
t *testing.T
|
||||
dialed int
|
||||
open int
|
||||
commands []string
|
||||
dialErr error
|
||||
}
|
||||
|
||||
func (d *poolDialer) dial() (redis.Conn, error) {
|
||||
d.mu.Lock()
|
||||
d.dialed += 1
|
||||
dialErr := d.dialErr
|
||||
d.mu.Unlock()
|
||||
if dialErr != nil {
|
||||
return nil, d.dialErr
|
||||
}
|
||||
c, err := redis.DialDefaultServer()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
d.mu.Lock()
|
||||
d.open += 1
|
||||
d.mu.Unlock()
|
||||
return &poolTestConn{d: d, Conn: c}, nil
|
||||
}
|
||||
|
||||
func (d *poolDialer) check(message string, p *redis.Pool, dialed, open int) {
|
||||
d.mu.Lock()
|
||||
if d.dialed != dialed {
|
||||
d.t.Errorf("%s: dialed=%d, want %d", message, d.dialed, dialed)
|
||||
}
|
||||
if d.open != open {
|
||||
d.t.Errorf("%s: open=%d, want %d", message, d.open, open)
|
||||
}
|
||||
if active := p.ActiveCount(); active != open {
|
||||
d.t.Errorf("%s: active=%d, want %d", message, active, open)
|
||||
}
|
||||
d.mu.Unlock()
|
||||
}
|
||||
|
||||
func TestPoolReuse(t *testing.T) {
|
||||
d := poolDialer{t: t}
|
||||
p := &redis.Pool{
|
||||
MaxIdle: 2,
|
||||
Dial: d.dial,
|
||||
}
|
||||
|
||||
for i := 0; i < 10; i++ {
|
||||
c1 := p.Get()
|
||||
c1.Do("PING")
|
||||
c2 := p.Get()
|
||||
c2.Do("PING")
|
||||
c1.Close()
|
||||
c2.Close()
|
||||
}
|
||||
|
||||
d.check("before close", p, 2, 2)
|
||||
p.Close()
|
||||
d.check("after close", p, 2, 0)
|
||||
}
|
||||
|
||||
func TestPoolMaxIdle(t *testing.T) {
|
||||
d := poolDialer{t: t}
|
||||
p := &redis.Pool{
|
||||
MaxIdle: 2,
|
||||
Dial: d.dial,
|
||||
}
|
||||
defer p.Close()
|
||||
|
||||
for i := 0; i < 10; i++ {
|
||||
c1 := p.Get()
|
||||
c1.Do("PING")
|
||||
c2 := p.Get()
|
||||
c2.Do("PING")
|
||||
c3 := p.Get()
|
||||
c3.Do("PING")
|
||||
c1.Close()
|
||||
c2.Close()
|
||||
c3.Close()
|
||||
}
|
||||
d.check("before close", p, 12, 2)
|
||||
p.Close()
|
||||
d.check("after close", p, 12, 0)
|
||||
}
|
||||
|
||||
func TestPoolError(t *testing.T) {
|
||||
d := poolDialer{t: t}
|
||||
p := &redis.Pool{
|
||||
MaxIdle: 2,
|
||||
Dial: d.dial,
|
||||
}
|
||||
defer p.Close()
|
||||
|
||||
c := p.Get()
|
||||
c.Do("ERR", io.EOF)
|
||||
if c.Err() == nil {
|
||||
t.Errorf("expected c.Err() != nil")
|
||||
}
|
||||
c.Close()
|
||||
|
||||
c = p.Get()
|
||||
c.Do("ERR", io.EOF)
|
||||
c.Close()
|
||||
|
||||
d.check(".", p, 2, 0)
|
||||
}
|
||||
|
||||
func TestPoolClose(t *testing.T) {
|
||||
d := poolDialer{t: t}
|
||||
p := &redis.Pool{
|
||||
MaxIdle: 2,
|
||||
Dial: d.dial,
|
||||
}
|
||||
defer p.Close()
|
||||
|
||||
c1 := p.Get()
|
||||
c1.Do("PING")
|
||||
c2 := p.Get()
|
||||
c2.Do("PING")
|
||||
c3 := p.Get()
|
||||
c3.Do("PING")
|
||||
|
||||
c1.Close()
|
||||
if _, err := c1.Do("PING"); err == nil {
|
||||
t.Errorf("expected error after connection closed")
|
||||
}
|
||||
|
||||
c2.Close()
|
||||
c2.Close()
|
||||
|
||||
p.Close()
|
||||
|
||||
d.check("after pool close", p, 3, 1)
|
||||
|
||||
if _, err := c1.Do("PING"); err == nil {
|
||||
t.Errorf("expected error after connection and pool closed")
|
||||
}
|
||||
|
||||
c3.Close()
|
||||
|
||||
d.check("after conn close", p, 3, 0)
|
||||
|
||||
c1 = p.Get()
|
||||
if _, err := c1.Do("PING"); err == nil {
|
||||
t.Errorf("expected error after pool closed")
|
||||
}
|
||||
}
|
||||
|
||||
func TestPoolTimeout(t *testing.T) {
|
||||
d := poolDialer{t: t}
|
||||
p := &redis.Pool{
|
||||
MaxIdle: 2,
|
||||
IdleTimeout: 300 * time.Second,
|
||||
Dial: d.dial,
|
||||
}
|
||||
defer p.Close()
|
||||
|
||||
now := time.Now()
|
||||
redis.SetNowFunc(func() time.Time { return now })
|
||||
defer redis.SetNowFunc(time.Now)
|
||||
|
||||
c := p.Get()
|
||||
c.Do("PING")
|
||||
c.Close()
|
||||
|
||||
d.check("1", p, 1, 1)
|
||||
|
||||
now = now.Add(p.IdleTimeout)
|
||||
|
||||
c = p.Get()
|
||||
c.Do("PING")
|
||||
c.Close()
|
||||
|
||||
d.check("2", p, 2, 1)
|
||||
}
|
||||
|
||||
func TestPoolConcurrenSendReceive(t *testing.T) {
|
||||
p := &redis.Pool{
|
||||
Dial: redis.DialDefaultServer,
|
||||
}
|
||||
defer p.Close()
|
||||
|
||||
c := p.Get()
|
||||
done := make(chan error, 1)
|
||||
go func() {
|
||||
_, err := c.Receive()
|
||||
done <- err
|
||||
}()
|
||||
c.Send("PING")
|
||||
c.Flush()
|
||||
err := <-done
|
||||
if err != nil {
|
||||
t.Fatalf("Receive() returned error %v", err)
|
||||
}
|
||||
_, err = c.Do("")
|
||||
if err != nil {
|
||||
t.Fatalf("Do() returned error %v", err)
|
||||
}
|
||||
c.Close()
|
||||
}
|
||||
|
||||
func TestPoolBorrowCheck(t *testing.T) {
|
||||
d := poolDialer{t: t}
|
||||
p := &redis.Pool{
|
||||
MaxIdle: 2,
|
||||
Dial: d.dial,
|
||||
TestOnBorrow: func(redis.Conn, time.Time) error { return redis.Error("BLAH") },
|
||||
}
|
||||
defer p.Close()
|
||||
|
||||
for i := 0; i < 10; i++ {
|
||||
c := p.Get()
|
||||
c.Do("PING")
|
||||
c.Close()
|
||||
}
|
||||
d.check("1", p, 10, 1)
|
||||
}
|
||||
|
||||
func TestPoolMaxActive(t *testing.T) {
|
||||
d := poolDialer{t: t}
|
||||
p := &redis.Pool{
|
||||
MaxIdle: 2,
|
||||
MaxActive: 2,
|
||||
Dial: d.dial,
|
||||
}
|
||||
defer p.Close()
|
||||
|
||||
c1 := p.Get()
|
||||
c1.Do("PING")
|
||||
c2 := p.Get()
|
||||
c2.Do("PING")
|
||||
|
||||
d.check("1", p, 2, 2)
|
||||
|
||||
c3 := p.Get()
|
||||
if _, err := c3.Do("PING"); err != redis.ErrPoolExhausted {
|
||||
t.Errorf("expected pool exhausted")
|
||||
}
|
||||
|
||||
c3.Close()
|
||||
d.check("2", p, 2, 2)
|
||||
c2.Close()
|
||||
d.check("3", p, 2, 2)
|
||||
|
||||
c3 = p.Get()
|
||||
if _, err := c3.Do("PING"); err != nil {
|
||||
t.Errorf("expected good channel, err=%v", err)
|
||||
}
|
||||
c3.Close()
|
||||
|
||||
d.check("4", p, 2, 2)
|
||||
}
|
||||
|
||||
func TestPoolMonitorCleanup(t *testing.T) {
|
||||
d := poolDialer{t: t}
|
||||
p := &redis.Pool{
|
||||
MaxIdle: 2,
|
||||
MaxActive: 2,
|
||||
Dial: d.dial,
|
||||
}
|
||||
defer p.Close()
|
||||
|
||||
c := p.Get()
|
||||
c.Send("MONITOR")
|
||||
c.Close()
|
||||
|
||||
d.check("", p, 1, 0)
|
||||
}
|
||||
|
||||
func TestPoolPubSubCleanup(t *testing.T) {
|
||||
d := poolDialer{t: t}
|
||||
p := &redis.Pool{
|
||||
MaxIdle: 2,
|
||||
MaxActive: 2,
|
||||
Dial: d.dial,
|
||||
}
|
||||
defer p.Close()
|
||||
|
||||
c := p.Get()
|
||||
c.Send("SUBSCRIBE", "x")
|
||||
c.Close()
|
||||
|
||||
want := []string{"SUBSCRIBE", "UNSUBSCRIBE", "PUNSUBSCRIBE", "ECHO"}
|
||||
if !reflect.DeepEqual(d.commands, want) {
|
||||
t.Errorf("got commands %v, want %v", d.commands, want)
|
||||
}
|
||||
d.commands = nil
|
||||
|
||||
c = p.Get()
|
||||
c.Send("PSUBSCRIBE", "x*")
|
||||
c.Close()
|
||||
|
||||
want = []string{"PSUBSCRIBE", "UNSUBSCRIBE", "PUNSUBSCRIBE", "ECHO"}
|
||||
if !reflect.DeepEqual(d.commands, want) {
|
||||
t.Errorf("got commands %v, want %v", d.commands, want)
|
||||
}
|
||||
d.commands = nil
|
||||
}
|
||||
|
||||
func TestPoolTransactionCleanup(t *testing.T) {
|
||||
d := poolDialer{t: t}
|
||||
p := &redis.Pool{
|
||||
MaxIdle: 2,
|
||||
MaxActive: 2,
|
||||
Dial: d.dial,
|
||||
}
|
||||
defer p.Close()
|
||||
|
||||
c := p.Get()
|
||||
c.Do("WATCH", "key")
|
||||
c.Do("PING")
|
||||
c.Close()
|
||||
|
||||
want := []string{"WATCH", "PING", "UNWATCH"}
|
||||
if !reflect.DeepEqual(d.commands, want) {
|
||||
t.Errorf("got commands %v, want %v", d.commands, want)
|
||||
}
|
||||
d.commands = nil
|
||||
|
||||
c = p.Get()
|
||||
c.Do("WATCH", "key")
|
||||
c.Do("UNWATCH")
|
||||
c.Do("PING")
|
||||
c.Close()
|
||||
|
||||
want = []string{"WATCH", "UNWATCH", "PING"}
|
||||
if !reflect.DeepEqual(d.commands, want) {
|
||||
t.Errorf("got commands %v, want %v", d.commands, want)
|
||||
}
|
||||
d.commands = nil
|
||||
|
||||
c = p.Get()
|
||||
c.Do("WATCH", "key")
|
||||
c.Do("MULTI")
|
||||
c.Do("PING")
|
||||
c.Close()
|
||||
|
||||
want = []string{"WATCH", "MULTI", "PING", "DISCARD"}
|
||||
if !reflect.DeepEqual(d.commands, want) {
|
||||
t.Errorf("got commands %v, want %v", d.commands, want)
|
||||
}
|
||||
d.commands = nil
|
||||
|
||||
c = p.Get()
|
||||
c.Do("WATCH", "key")
|
||||
c.Do("MULTI")
|
||||
c.Do("DISCARD")
|
||||
c.Do("PING")
|
||||
c.Close()
|
||||
|
||||
want = []string{"WATCH", "MULTI", "DISCARD", "PING"}
|
||||
if !reflect.DeepEqual(d.commands, want) {
|
||||
t.Errorf("got commands %v, want %v", d.commands, want)
|
||||
}
|
||||
d.commands = nil
|
||||
|
||||
c = p.Get()
|
||||
c.Do("WATCH", "key")
|
||||
c.Do("MULTI")
|
||||
c.Do("EXEC")
|
||||
c.Do("PING")
|
||||
c.Close()
|
||||
|
||||
want = []string{"WATCH", "MULTI", "EXEC", "PING"}
|
||||
if !reflect.DeepEqual(d.commands, want) {
|
||||
t.Errorf("got commands %v, want %v", d.commands, want)
|
||||
}
|
||||
d.commands = nil
|
||||
}
|
||||
|
||||
func startGoroutines(p *redis.Pool, cmd string, args ...interface{}) chan error {
|
||||
errs := make(chan error, 10)
|
||||
for i := 0; i < cap(errs); i++ {
|
||||
go func() {
|
||||
c := p.Get()
|
||||
_, err := c.Do(cmd, args...)
|
||||
errs <- err
|
||||
c.Close()
|
||||
}()
|
||||
}
|
||||
|
||||
// Wait for goroutines to block.
|
||||
time.Sleep(time.Second / 4)
|
||||
|
||||
return errs
|
||||
}
|
||||
|
||||
func TestWaitPool(t *testing.T) {
|
||||
d := poolDialer{t: t}
|
||||
p := &redis.Pool{
|
||||
MaxIdle: 1,
|
||||
MaxActive: 1,
|
||||
Dial: d.dial,
|
||||
Wait: true,
|
||||
}
|
||||
defer p.Close()
|
||||
|
||||
c := p.Get()
|
||||
errs := startGoroutines(p, "PING")
|
||||
d.check("before close", p, 1, 1)
|
||||
c.Close()
|
||||
timeout := time.After(2 * time.Second)
|
||||
for i := 0; i < cap(errs); i++ {
|
||||
select {
|
||||
case err := <-errs:
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
case <-timeout:
|
||||
t.Fatalf("timeout waiting for blocked goroutine %d", i)
|
||||
}
|
||||
}
|
||||
d.check("done", p, 1, 1)
|
||||
}
|
||||
|
||||
func TestWaitPoolClose(t *testing.T) {
|
||||
d := poolDialer{t: t}
|
||||
p := &redis.Pool{
|
||||
MaxIdle: 1,
|
||||
MaxActive: 1,
|
||||
Dial: d.dial,
|
||||
Wait: true,
|
||||
}
|
||||
defer p.Close()
|
||||
|
||||
c := p.Get()
|
||||
if _, err := c.Do("PING"); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
errs := startGoroutines(p, "PING")
|
||||
d.check("before close", p, 1, 1)
|
||||
p.Close()
|
||||
timeout := time.After(2 * time.Second)
|
||||
for i := 0; i < cap(errs); i++ {
|
||||
select {
|
||||
case err := <-errs:
|
||||
switch err {
|
||||
case nil:
|
||||
t.Fatal("blocked goroutine did not get error")
|
||||
case redis.ErrPoolExhausted:
|
||||
t.Fatal("blocked goroutine got pool exhausted error")
|
||||
}
|
||||
case <-timeout:
|
||||
t.Fatal("timeout waiting for blocked goroutine")
|
||||
}
|
||||
}
|
||||
c.Close()
|
||||
d.check("done", p, 1, 0)
|
||||
}
|
||||
|
||||
func TestWaitPoolCommandError(t *testing.T) {
|
||||
testErr := errors.New("test")
|
||||
d := poolDialer{t: t}
|
||||
p := &redis.Pool{
|
||||
MaxIdle: 1,
|
||||
MaxActive: 1,
|
||||
Dial: d.dial,
|
||||
Wait: true,
|
||||
}
|
||||
defer p.Close()
|
||||
|
||||
c := p.Get()
|
||||
errs := startGoroutines(p, "ERR", testErr)
|
||||
d.check("before close", p, 1, 1)
|
||||
c.Close()
|
||||
timeout := time.After(2 * time.Second)
|
||||
for i := 0; i < cap(errs); i++ {
|
||||
select {
|
||||
case err := <-errs:
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
case <-timeout:
|
||||
t.Fatalf("timeout waiting for blocked goroutine %d", i)
|
||||
}
|
||||
}
|
||||
d.check("done", p, cap(errs), 0)
|
||||
}
|
||||
|
||||
func TestWaitPoolDialError(t *testing.T) {
|
||||
testErr := errors.New("test")
|
||||
d := poolDialer{t: t}
|
||||
p := &redis.Pool{
|
||||
MaxIdle: 1,
|
||||
MaxActive: 1,
|
||||
Dial: d.dial,
|
||||
Wait: true,
|
||||
}
|
||||
defer p.Close()
|
||||
|
||||
c := p.Get()
|
||||
errs := startGoroutines(p, "ERR", testErr)
|
||||
d.check("before close", p, 1, 1)
|
||||
|
||||
d.dialErr = errors.New("dial")
|
||||
c.Close()
|
||||
|
||||
nilCount := 0
|
||||
errCount := 0
|
||||
timeout := time.After(2 * time.Second)
|
||||
for i := 0; i < cap(errs); i++ {
|
||||
select {
|
||||
case err := <-errs:
|
||||
switch err {
|
||||
case nil:
|
||||
nilCount++
|
||||
case d.dialErr:
|
||||
errCount++
|
||||
default:
|
||||
t.Fatalf("expected dial error or nil, got %v", err)
|
||||
}
|
||||
case <-timeout:
|
||||
t.Fatalf("timeout waiting for blocked goroutine %d", i)
|
||||
}
|
||||
}
|
||||
if nilCount != 1 {
|
||||
t.Errorf("expected one nil error, got %d", nilCount)
|
||||
}
|
||||
if errCount != cap(errs)-1 {
|
||||
t.Errorf("expected %d dial erors, got %d", cap(errs)-1, errCount)
|
||||
}
|
||||
d.check("done", p, cap(errs), 0)
|
||||
}
|
||||
|
||||
// Borrowing requires us to iterate over the idle connections, unlock the pool,
|
||||
// and perform a blocking operation to check the connection still works. If
|
||||
// TestOnBorrow fails, we must reacquire the lock and continue iteration. This
|
||||
// test ensures that iteration will work correctly if multiple threads are
|
||||
// iterating simultaneously.
|
||||
func TestLocking_TestOnBorrowFails_PoolDoesntCrash(t *testing.T) {
|
||||
const count = 100
|
||||
|
||||
// First we'll Create a pool where the pilfering of idle connections fails.
|
||||
d := poolDialer{t: t}
|
||||
p := &redis.Pool{
|
||||
MaxIdle: count,
|
||||
MaxActive: count,
|
||||
Dial: d.dial,
|
||||
TestOnBorrow: func(c redis.Conn, t time.Time) error {
|
||||
return errors.New("No way back into the real world.")
|
||||
},
|
||||
}
|
||||
defer p.Close()
|
||||
|
||||
// Fill the pool with idle connections.
|
||||
conns := make([]redis.Conn, count)
|
||||
for i := range conns {
|
||||
conns[i] = p.Get()
|
||||
}
|
||||
for i := range conns {
|
||||
conns[i].Close()
|
||||
}
|
||||
|
||||
// Spawn a bunch of goroutines to thrash the pool.
|
||||
var wg sync.WaitGroup
|
||||
wg.Add(count)
|
||||
for i := 0; i < count; i++ {
|
||||
go func() {
|
||||
c := p.Get()
|
||||
if c.Err() != nil {
|
||||
t.Errorf("pool get failed: %v", c.Err())
|
||||
}
|
||||
c.Close()
|
||||
wg.Done()
|
||||
}()
|
||||
}
|
||||
wg.Wait()
|
||||
if d.dialed != count*2 {
|
||||
t.Errorf("Expected %d dials, got %d", count*2, d.dialed)
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkPoolGet(b *testing.B) {
|
||||
b.StopTimer()
|
||||
p := redis.Pool{Dial: redis.DialDefaultServer, MaxIdle: 2}
|
||||
c := p.Get()
|
||||
if err := c.Err(); err != nil {
|
||||
b.Fatal(err)
|
||||
}
|
||||
c.Close()
|
||||
defer p.Close()
|
||||
b.StartTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
c = p.Get()
|
||||
c.Close()
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkPoolGetErr(b *testing.B) {
|
||||
b.StopTimer()
|
||||
p := redis.Pool{Dial: redis.DialDefaultServer, MaxIdle: 2}
|
||||
c := p.Get()
|
||||
if err := c.Err(); err != nil {
|
||||
b.Fatal(err)
|
||||
}
|
||||
c.Close()
|
||||
defer p.Close()
|
||||
b.StartTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
c = p.Get()
|
||||
if err := c.Err(); err != nil {
|
||||
b.Fatal(err)
|
||||
}
|
||||
c.Close()
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkPoolGetPing(b *testing.B) {
|
||||
b.StopTimer()
|
||||
p := redis.Pool{Dial: redis.DialDefaultServer, MaxIdle: 2}
|
||||
c := p.Get()
|
||||
if err := c.Err(); err != nil {
|
||||
b.Fatal(err)
|
||||
}
|
||||
c.Close()
|
||||
defer p.Close()
|
||||
b.StartTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
c = p.Get()
|
||||
if _, err := c.Do("PING"); err != nil {
|
||||
b.Fatal(err)
|
||||
}
|
||||
c.Close()
|
||||
}
|
||||
}
|
144
vendor/github.com/garyburd/redigo/redis/pubsub.go
generated
vendored
Normal file
144
vendor/github.com/garyburd/redigo/redis/pubsub.go
generated
vendored
Normal file
@@ -0,0 +1,144 @@
|
||||
// Copyright 2012 Gary Burd
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License"): you may
|
||||
// not use this file except in compliance with the License. You may obtain
|
||||
// a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
// License for the specific language governing permissions and limitations
|
||||
// under the License.
|
||||
|
||||
package redis
|
||||
|
||||
import "errors"
|
||||
|
||||
// Subscription represents a subscribe or unsubscribe notification.
|
||||
type Subscription struct {
|
||||
|
||||
// Kind is "subscribe", "unsubscribe", "psubscribe" or "punsubscribe"
|
||||
Kind string
|
||||
|
||||
// The channel that was changed.
|
||||
Channel string
|
||||
|
||||
// The current number of subscriptions for connection.
|
||||
Count int
|
||||
}
|
||||
|
||||
// Message represents a message notification.
|
||||
type Message struct {
|
||||
|
||||
// The originating channel.
|
||||
Channel string
|
||||
|
||||
// The message data.
|
||||
Data []byte
|
||||
}
|
||||
|
||||
// PMessage represents a pmessage notification.
|
||||
type PMessage struct {
|
||||
|
||||
// The matched pattern.
|
||||
Pattern string
|
||||
|
||||
// The originating channel.
|
||||
Channel string
|
||||
|
||||
// The message data.
|
||||
Data []byte
|
||||
}
|
||||
|
||||
// Pong represents a pubsub pong notification.
|
||||
type Pong struct {
|
||||
Data string
|
||||
}
|
||||
|
||||
// PubSubConn wraps a Conn with convenience methods for subscribers.
|
||||
type PubSubConn struct {
|
||||
Conn Conn
|
||||
}
|
||||
|
||||
// Close closes the connection.
|
||||
func (c PubSubConn) Close() error {
|
||||
return c.Conn.Close()
|
||||
}
|
||||
|
||||
// Subscribe subscribes the connection to the specified channels.
|
||||
func (c PubSubConn) Subscribe(channel ...interface{}) error {
|
||||
c.Conn.Send("SUBSCRIBE", channel...)
|
||||
return c.Conn.Flush()
|
||||
}
|
||||
|
||||
// PSubscribe subscribes the connection to the given patterns.
|
||||
func (c PubSubConn) PSubscribe(channel ...interface{}) error {
|
||||
c.Conn.Send("PSUBSCRIBE", channel...)
|
||||
return c.Conn.Flush()
|
||||
}
|
||||
|
||||
// Unsubscribe unsubscribes the connection from the given channels, or from all
|
||||
// of them if none is given.
|
||||
func (c PubSubConn) Unsubscribe(channel ...interface{}) error {
|
||||
c.Conn.Send("UNSUBSCRIBE", channel...)
|
||||
return c.Conn.Flush()
|
||||
}
|
||||
|
||||
// PUnsubscribe unsubscribes the connection from the given patterns, or from all
|
||||
// of them if none is given.
|
||||
func (c PubSubConn) PUnsubscribe(channel ...interface{}) error {
|
||||
c.Conn.Send("PUNSUBSCRIBE", channel...)
|
||||
return c.Conn.Flush()
|
||||
}
|
||||
|
||||
// Ping sends a PING to the server with the specified data.
|
||||
func (c PubSubConn) Ping(data string) error {
|
||||
c.Conn.Send("PING", data)
|
||||
return c.Conn.Flush()
|
||||
}
|
||||
|
||||
// Receive returns a pushed message as a Subscription, Message, PMessage, Pong
|
||||
// or error. The return value is intended to be used directly in a type switch
|
||||
// as illustrated in the PubSubConn example.
|
||||
func (c PubSubConn) Receive() interface{} {
|
||||
reply, err := Values(c.Conn.Receive())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
var kind string
|
||||
reply, err = Scan(reply, &kind)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
switch kind {
|
||||
case "message":
|
||||
var m Message
|
||||
if _, err := Scan(reply, &m.Channel, &m.Data); err != nil {
|
||||
return err
|
||||
}
|
||||
return m
|
||||
case "pmessage":
|
||||
var pm PMessage
|
||||
if _, err := Scan(reply, &pm.Pattern, &pm.Channel, &pm.Data); err != nil {
|
||||
return err
|
||||
}
|
||||
return pm
|
||||
case "subscribe", "psubscribe", "unsubscribe", "punsubscribe":
|
||||
s := Subscription{Kind: kind}
|
||||
if _, err := Scan(reply, &s.Channel, &s.Count); err != nil {
|
||||
return err
|
||||
}
|
||||
return s
|
||||
case "pong":
|
||||
var p Pong
|
||||
if _, err := Scan(reply, &p.Data); err != nil {
|
||||
return err
|
||||
}
|
||||
return p
|
||||
}
|
||||
return errors.New("redigo: unknown pubsub notification")
|
||||
}
|
148
vendor/github.com/garyburd/redigo/redis/pubsub_test.go
generated
vendored
Normal file
148
vendor/github.com/garyburd/redigo/redis/pubsub_test.go
generated
vendored
Normal file
@@ -0,0 +1,148 @@
|
||||
// Copyright 2012 Gary Burd
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License"): you may
|
||||
// not use this file except in compliance with the License. You may obtain
|
||||
// a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
// License for the specific language governing permissions and limitations
|
||||
// under the License.
|
||||
|
||||
package redis_test
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"reflect"
|
||||
"sync"
|
||||
"testing"
|
||||
|
||||
"github.com/garyburd/redigo/redis"
|
||||
)
|
||||
|
||||
func publish(channel, value interface{}) {
|
||||
c, err := dial()
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
return
|
||||
}
|
||||
defer c.Close()
|
||||
c.Do("PUBLISH", channel, value)
|
||||
}
|
||||
|
||||
// Applications can receive pushed messages from one goroutine and manage subscriptions from another goroutine.
|
||||
func ExamplePubSubConn() {
|
||||
c, err := dial()
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
return
|
||||
}
|
||||
defer c.Close()
|
||||
var wg sync.WaitGroup
|
||||
wg.Add(2)
|
||||
|
||||
psc := redis.PubSubConn{Conn: c}
|
||||
|
||||
// This goroutine receives and prints pushed notifications from the server.
|
||||
// The goroutine exits when the connection is unsubscribed from all
|
||||
// channels or there is an error.
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
for {
|
||||
switch n := psc.Receive().(type) {
|
||||
case redis.Message:
|
||||
fmt.Printf("Message: %s %s\n", n.Channel, n.Data)
|
||||
case redis.PMessage:
|
||||
fmt.Printf("PMessage: %s %s %s\n", n.Pattern, n.Channel, n.Data)
|
||||
case redis.Subscription:
|
||||
fmt.Printf("Subscription: %s %s %d\n", n.Kind, n.Channel, n.Count)
|
||||
if n.Count == 0 {
|
||||
return
|
||||
}
|
||||
case error:
|
||||
fmt.Printf("error: %v\n", n)
|
||||
return
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
// This goroutine manages subscriptions for the connection.
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
|
||||
psc.Subscribe("example")
|
||||
psc.PSubscribe("p*")
|
||||
|
||||
// The following function calls publish a message using another
|
||||
// connection to the Redis server.
|
||||
publish("example", "hello")
|
||||
publish("example", "world")
|
||||
publish("pexample", "foo")
|
||||
publish("pexample", "bar")
|
||||
|
||||
// Unsubscribe from all connections. This will cause the receiving
|
||||
// goroutine to exit.
|
||||
psc.Unsubscribe()
|
||||
psc.PUnsubscribe()
|
||||
}()
|
||||
|
||||
wg.Wait()
|
||||
|
||||
// Output:
|
||||
// Subscription: subscribe example 1
|
||||
// Subscription: psubscribe p* 2
|
||||
// Message: example hello
|
||||
// Message: example world
|
||||
// PMessage: p* pexample foo
|
||||
// PMessage: p* pexample bar
|
||||
// Subscription: unsubscribe example 1
|
||||
// Subscription: punsubscribe p* 0
|
||||
}
|
||||
|
||||
func expectPushed(t *testing.T, c redis.PubSubConn, message string, expected interface{}) {
|
||||
actual := c.Receive()
|
||||
if !reflect.DeepEqual(actual, expected) {
|
||||
t.Errorf("%s = %v, want %v", message, actual, expected)
|
||||
}
|
||||
}
|
||||
|
||||
func TestPushed(t *testing.T) {
|
||||
pc, err := redis.DialDefaultServer()
|
||||
if err != nil {
|
||||
t.Fatalf("error connection to database, %v", err)
|
||||
}
|
||||
defer pc.Close()
|
||||
|
||||
sc, err := redis.DialDefaultServer()
|
||||
if err != nil {
|
||||
t.Fatalf("error connection to database, %v", err)
|
||||
}
|
||||
defer sc.Close()
|
||||
|
||||
c := redis.PubSubConn{Conn: sc}
|
||||
|
||||
c.Subscribe("c1")
|
||||
expectPushed(t, c, "Subscribe(c1)", redis.Subscription{Kind: "subscribe", Channel: "c1", Count: 1})
|
||||
c.Subscribe("c2")
|
||||
expectPushed(t, c, "Subscribe(c2)", redis.Subscription{Kind: "subscribe", Channel: "c2", Count: 2})
|
||||
c.PSubscribe("p1")
|
||||
expectPushed(t, c, "PSubscribe(p1)", redis.Subscription{Kind: "psubscribe", Channel: "p1", Count: 3})
|
||||
c.PSubscribe("p2")
|
||||
expectPushed(t, c, "PSubscribe(p2)", redis.Subscription{Kind: "psubscribe", Channel: "p2", Count: 4})
|
||||
c.PUnsubscribe()
|
||||
expectPushed(t, c, "Punsubscribe(p1)", redis.Subscription{Kind: "punsubscribe", Channel: "p1", Count: 3})
|
||||
expectPushed(t, c, "Punsubscribe()", redis.Subscription{Kind: "punsubscribe", Channel: "p2", Count: 2})
|
||||
|
||||
pc.Do("PUBLISH", "c1", "hello")
|
||||
expectPushed(t, c, "PUBLISH c1 hello", redis.Message{Channel: "c1", Data: []byte("hello")})
|
||||
|
||||
c.Ping("hello")
|
||||
expectPushed(t, c, `Ping("hello")`, redis.Pong{Data: "hello"})
|
||||
|
||||
c.Conn.Send("PING")
|
||||
c.Conn.Flush()
|
||||
expectPushed(t, c, `Send("PING")`, redis.Pong{})
|
||||
}
|
44
vendor/github.com/garyburd/redigo/redis/redis.go
generated
vendored
Normal file
44
vendor/github.com/garyburd/redigo/redis/redis.go
generated
vendored
Normal file
@@ -0,0 +1,44 @@
|
||||
// Copyright 2012 Gary Burd
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License"): you may
|
||||
// not use this file except in compliance with the License. You may obtain
|
||||
// a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
// License for the specific language governing permissions and limitations
|
||||
// under the License.
|
||||
|
||||
package redis
|
||||
|
||||
// Error represents an error returned in a command reply.
|
||||
type Error string
|
||||
|
||||
func (err Error) Error() string { return string(err) }
|
||||
|
||||
// Conn represents a connection to a Redis server.
|
||||
type Conn interface {
|
||||
// Close closes the connection.
|
||||
Close() error
|
||||
|
||||
// Err returns a non-nil value if the connection is broken. The returned
|
||||
// value is either the first non-nil value returned from the underlying
|
||||
// network connection or a protocol parsing error. Applications should
|
||||
// close broken connections.
|
||||
Err() error
|
||||
|
||||
// Do sends a command to the server and returns the received reply.
|
||||
Do(commandName string, args ...interface{}) (reply interface{}, err error)
|
||||
|
||||
// Send writes the command to the client's output buffer.
|
||||
Send(commandName string, args ...interface{}) error
|
||||
|
||||
// Flush flushes the output buffer to the Redis server.
|
||||
Flush() error
|
||||
|
||||
// Receive receives a single reply from the Redis server
|
||||
Receive() (reply interface{}, err error)
|
||||
}
|
393
vendor/github.com/garyburd/redigo/redis/reply.go
generated
vendored
Normal file
393
vendor/github.com/garyburd/redigo/redis/reply.go
generated
vendored
Normal file
@@ -0,0 +1,393 @@
|
||||
// Copyright 2012 Gary Burd
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License"): you may
|
||||
// not use this file except in compliance with the License. You may obtain
|
||||
// a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
// License for the specific language governing permissions and limitations
|
||||
// under the License.
|
||||
|
||||
package redis
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"strconv"
|
||||
)
|
||||
|
||||
// ErrNil indicates that a reply value is nil.
|
||||
var ErrNil = errors.New("redigo: nil returned")
|
||||
|
||||
// Int is a helper that converts a command reply to an integer. If err is not
|
||||
// equal to nil, then Int returns 0, err. Otherwise, Int converts the
|
||||
// reply to an int as follows:
|
||||
//
|
||||
// Reply type Result
|
||||
// integer int(reply), nil
|
||||
// bulk string parsed reply, nil
|
||||
// nil 0, ErrNil
|
||||
// other 0, error
|
||||
func Int(reply interface{}, err error) (int, error) {
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
switch reply := reply.(type) {
|
||||
case int64:
|
||||
x := int(reply)
|
||||
if int64(x) != reply {
|
||||
return 0, strconv.ErrRange
|
||||
}
|
||||
return x, nil
|
||||
case []byte:
|
||||
n, err := strconv.ParseInt(string(reply), 10, 0)
|
||||
return int(n), err
|
||||
case nil:
|
||||
return 0, ErrNil
|
||||
case Error:
|
||||
return 0, reply
|
||||
}
|
||||
return 0, fmt.Errorf("redigo: unexpected type for Int, got type %T", reply)
|
||||
}
|
||||
|
||||
// Int64 is a helper that converts a command reply to 64 bit integer. If err is
|
||||
// not equal to nil, then Int returns 0, err. Otherwise, Int64 converts the
|
||||
// reply to an int64 as follows:
|
||||
//
|
||||
// Reply type Result
|
||||
// integer reply, nil
|
||||
// bulk string parsed reply, nil
|
||||
// nil 0, ErrNil
|
||||
// other 0, error
|
||||
func Int64(reply interface{}, err error) (int64, error) {
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
switch reply := reply.(type) {
|
||||
case int64:
|
||||
return reply, nil
|
||||
case []byte:
|
||||
n, err := strconv.ParseInt(string(reply), 10, 64)
|
||||
return n, err
|
||||
case nil:
|
||||
return 0, ErrNil
|
||||
case Error:
|
||||
return 0, reply
|
||||
}
|
||||
return 0, fmt.Errorf("redigo: unexpected type for Int64, got type %T", reply)
|
||||
}
|
||||
|
||||
var errNegativeInt = errors.New("redigo: unexpected value for Uint64")
|
||||
|
||||
// Uint64 is a helper that converts a command reply to 64 bit integer. If err is
|
||||
// not equal to nil, then Int returns 0, err. Otherwise, Int64 converts the
|
||||
// reply to an int64 as follows:
|
||||
//
|
||||
// Reply type Result
|
||||
// integer reply, nil
|
||||
// bulk string parsed reply, nil
|
||||
// nil 0, ErrNil
|
||||
// other 0, error
|
||||
func Uint64(reply interface{}, err error) (uint64, error) {
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
switch reply := reply.(type) {
|
||||
case int64:
|
||||
if reply < 0 {
|
||||
return 0, errNegativeInt
|
||||
}
|
||||
return uint64(reply), nil
|
||||
case []byte:
|
||||
n, err := strconv.ParseUint(string(reply), 10, 64)
|
||||
return n, err
|
||||
case nil:
|
||||
return 0, ErrNil
|
||||
case Error:
|
||||
return 0, reply
|
||||
}
|
||||
return 0, fmt.Errorf("redigo: unexpected type for Uint64, got type %T", reply)
|
||||
}
|
||||
|
||||
// Float64 is a helper that converts a command reply to 64 bit float. If err is
|
||||
// not equal to nil, then Float64 returns 0, err. Otherwise, Float64 converts
|
||||
// the reply to an int as follows:
|
||||
//
|
||||
// Reply type Result
|
||||
// bulk string parsed reply, nil
|
||||
// nil 0, ErrNil
|
||||
// other 0, error
|
||||
func Float64(reply interface{}, err error) (float64, error) {
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
switch reply := reply.(type) {
|
||||
case []byte:
|
||||
n, err := strconv.ParseFloat(string(reply), 64)
|
||||
return n, err
|
||||
case nil:
|
||||
return 0, ErrNil
|
||||
case Error:
|
||||
return 0, reply
|
||||
}
|
||||
return 0, fmt.Errorf("redigo: unexpected type for Float64, got type %T", reply)
|
||||
}
|
||||
|
||||
// String is a helper that converts a command reply to a string. If err is not
|
||||
// equal to nil, then String returns "", err. Otherwise String converts the
|
||||
// reply to a string as follows:
|
||||
//
|
||||
// Reply type Result
|
||||
// bulk string string(reply), nil
|
||||
// simple string reply, nil
|
||||
// nil "", ErrNil
|
||||
// other "", error
|
||||
func String(reply interface{}, err error) (string, error) {
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
switch reply := reply.(type) {
|
||||
case []byte:
|
||||
return string(reply), nil
|
||||
case string:
|
||||
return reply, nil
|
||||
case nil:
|
||||
return "", ErrNil
|
||||
case Error:
|
||||
return "", reply
|
||||
}
|
||||
return "", fmt.Errorf("redigo: unexpected type for String, got type %T", reply)
|
||||
}
|
||||
|
||||
// Bytes is a helper that converts a command reply to a slice of bytes. If err
|
||||
// is not equal to nil, then Bytes returns nil, err. Otherwise Bytes converts
|
||||
// the reply to a slice of bytes as follows:
|
||||
//
|
||||
// Reply type Result
|
||||
// bulk string reply, nil
|
||||
// simple string []byte(reply), nil
|
||||
// nil nil, ErrNil
|
||||
// other nil, error
|
||||
func Bytes(reply interface{}, err error) ([]byte, error) {
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
switch reply := reply.(type) {
|
||||
case []byte:
|
||||
return reply, nil
|
||||
case string:
|
||||
return []byte(reply), nil
|
||||
case nil:
|
||||
return nil, ErrNil
|
||||
case Error:
|
||||
return nil, reply
|
||||
}
|
||||
return nil, fmt.Errorf("redigo: unexpected type for Bytes, got type %T", reply)
|
||||
}
|
||||
|
||||
// Bool is a helper that converts a command reply to a boolean. If err is not
|
||||
// equal to nil, then Bool returns false, err. Otherwise Bool converts the
|
||||
// reply to boolean as follows:
|
||||
//
|
||||
// Reply type Result
|
||||
// integer value != 0, nil
|
||||
// bulk string strconv.ParseBool(reply)
|
||||
// nil false, ErrNil
|
||||
// other false, error
|
||||
func Bool(reply interface{}, err error) (bool, error) {
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
switch reply := reply.(type) {
|
||||
case int64:
|
||||
return reply != 0, nil
|
||||
case []byte:
|
||||
return strconv.ParseBool(string(reply))
|
||||
case nil:
|
||||
return false, ErrNil
|
||||
case Error:
|
||||
return false, reply
|
||||
}
|
||||
return false, fmt.Errorf("redigo: unexpected type for Bool, got type %T", reply)
|
||||
}
|
||||
|
||||
// MultiBulk is a helper that converts an array command reply to a []interface{}.
|
||||
//
|
||||
// Deprecated: Use Values instead.
|
||||
func MultiBulk(reply interface{}, err error) ([]interface{}, error) { return Values(reply, err) }
|
||||
|
||||
// Values is a helper that converts an array command reply to a []interface{}.
|
||||
// If err is not equal to nil, then Values returns nil, err. Otherwise, Values
|
||||
// converts the reply as follows:
|
||||
//
|
||||
// Reply type Result
|
||||
// array reply, nil
|
||||
// nil nil, ErrNil
|
||||
// other nil, error
|
||||
func Values(reply interface{}, err error) ([]interface{}, error) {
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
switch reply := reply.(type) {
|
||||
case []interface{}:
|
||||
return reply, nil
|
||||
case nil:
|
||||
return nil, ErrNil
|
||||
case Error:
|
||||
return nil, reply
|
||||
}
|
||||
return nil, fmt.Errorf("redigo: unexpected type for Values, got type %T", reply)
|
||||
}
|
||||
|
||||
// Strings is a helper that converts an array command reply to a []string. If
|
||||
// err is not equal to nil, then Strings returns nil, err. Nil array items are
|
||||
// converted to "" in the output slice. Strings returns an error if an array
|
||||
// item is not a bulk string or nil.
|
||||
func Strings(reply interface{}, err error) ([]string, error) {
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
switch reply := reply.(type) {
|
||||
case []interface{}:
|
||||
result := make([]string, len(reply))
|
||||
for i := range reply {
|
||||
if reply[i] == nil {
|
||||
continue
|
||||
}
|
||||
p, ok := reply[i].([]byte)
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("redigo: unexpected element type for Strings, got type %T", reply[i])
|
||||
}
|
||||
result[i] = string(p)
|
||||
}
|
||||
return result, nil
|
||||
case nil:
|
||||
return nil, ErrNil
|
||||
case Error:
|
||||
return nil, reply
|
||||
}
|
||||
return nil, fmt.Errorf("redigo: unexpected type for Strings, got type %T", reply)
|
||||
}
|
||||
|
||||
// ByteSlices is a helper that converts an array command reply to a [][]byte.
|
||||
// If err is not equal to nil, then ByteSlices returns nil, err. Nil array
|
||||
// items are stay nil. ByteSlices returns an error if an array item is not a
|
||||
// bulk string or nil.
|
||||
func ByteSlices(reply interface{}, err error) ([][]byte, error) {
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
switch reply := reply.(type) {
|
||||
case []interface{}:
|
||||
result := make([][]byte, len(reply))
|
||||
for i := range reply {
|
||||
if reply[i] == nil {
|
||||
continue
|
||||
}
|
||||
p, ok := reply[i].([]byte)
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("redigo: unexpected element type for ByteSlices, got type %T", reply[i])
|
||||
}
|
||||
result[i] = p
|
||||
}
|
||||
return result, nil
|
||||
case nil:
|
||||
return nil, ErrNil
|
||||
case Error:
|
||||
return nil, reply
|
||||
}
|
||||
return nil, fmt.Errorf("redigo: unexpected type for ByteSlices, got type %T", reply)
|
||||
}
|
||||
|
||||
// Ints is a helper that converts an array command reply to a []int. If
|
||||
// err is not equal to nil, then Ints returns nil, err.
|
||||
func Ints(reply interface{}, err error) ([]int, error) {
|
||||
var ints []int
|
||||
values, err := Values(reply, err)
|
||||
if err != nil {
|
||||
return ints, err
|
||||
}
|
||||
if err := ScanSlice(values, &ints); err != nil {
|
||||
return ints, err
|
||||
}
|
||||
return ints, nil
|
||||
}
|
||||
|
||||
// StringMap is a helper that converts an array of strings (alternating key, value)
|
||||
// into a map[string]string. The HGETALL and CONFIG GET commands return replies in this format.
|
||||
// Requires an even number of values in result.
|
||||
func StringMap(result interface{}, err error) (map[string]string, error) {
|
||||
values, err := Values(result, err)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if len(values)%2 != 0 {
|
||||
return nil, errors.New("redigo: StringMap expects even number of values result")
|
||||
}
|
||||
m := make(map[string]string, len(values)/2)
|
||||
for i := 0; i < len(values); i += 2 {
|
||||
key, okKey := values[i].([]byte)
|
||||
value, okValue := values[i+1].([]byte)
|
||||
if !okKey || !okValue {
|
||||
return nil, errors.New("redigo: ScanMap key not a bulk string value")
|
||||
}
|
||||
m[string(key)] = string(value)
|
||||
}
|
||||
return m, nil
|
||||
}
|
||||
|
||||
// IntMap is a helper that converts an array of strings (alternating key, value)
|
||||
// into a map[string]int. The HGETALL commands return replies in this format.
|
||||
// Requires an even number of values in result.
|
||||
func IntMap(result interface{}, err error) (map[string]int, error) {
|
||||
values, err := Values(result, err)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if len(values)%2 != 0 {
|
||||
return nil, errors.New("redigo: IntMap expects even number of values result")
|
||||
}
|
||||
m := make(map[string]int, len(values)/2)
|
||||
for i := 0; i < len(values); i += 2 {
|
||||
key, ok := values[i].([]byte)
|
||||
if !ok {
|
||||
return nil, errors.New("redigo: ScanMap key not a bulk string value")
|
||||
}
|
||||
value, err := Int(values[i+1], nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
m[string(key)] = value
|
||||
}
|
||||
return m, nil
|
||||
}
|
||||
|
||||
// Int64Map is a helper that converts an array of strings (alternating key, value)
|
||||
// into a map[string]int64. The HGETALL commands return replies in this format.
|
||||
// Requires an even number of values in result.
|
||||
func Int64Map(result interface{}, err error) (map[string]int64, error) {
|
||||
values, err := Values(result, err)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if len(values)%2 != 0 {
|
||||
return nil, errors.New("redigo: Int64Map expects even number of values result")
|
||||
}
|
||||
m := make(map[string]int64, len(values)/2)
|
||||
for i := 0; i < len(values); i += 2 {
|
||||
key, ok := values[i].([]byte)
|
||||
if !ok {
|
||||
return nil, errors.New("redigo: ScanMap key not a bulk string value")
|
||||
}
|
||||
value, err := Int64(values[i+1], nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
m[string(key)] = value
|
||||
}
|
||||
return m, nil
|
||||
}
|
179
vendor/github.com/garyburd/redigo/redis/reply_test.go
generated
vendored
Normal file
179
vendor/github.com/garyburd/redigo/redis/reply_test.go
generated
vendored
Normal file
@@ -0,0 +1,179 @@
|
||||
// Copyright 2012 Gary Burd
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License"): you may
|
||||
// not use this file except in compliance with the License. You may obtain
|
||||
// a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
// License for the specific language governing permissions and limitations
|
||||
// under the License.
|
||||
|
||||
package redis_test
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"reflect"
|
||||
"testing"
|
||||
|
||||
"github.com/garyburd/redigo/redis"
|
||||
)
|
||||
|
||||
type valueError struct {
|
||||
v interface{}
|
||||
err error
|
||||
}
|
||||
|
||||
func ve(v interface{}, err error) valueError {
|
||||
return valueError{v, err}
|
||||
}
|
||||
|
||||
var replyTests = []struct {
|
||||
name interface{}
|
||||
actual valueError
|
||||
expected valueError
|
||||
}{
|
||||
{
|
||||
"ints([v1, v2])",
|
||||
ve(redis.Ints([]interface{}{[]byte("4"), []byte("5")}, nil)),
|
||||
ve([]int{4, 5}, nil),
|
||||
},
|
||||
{
|
||||
"ints(nil)",
|
||||
ve(redis.Ints(nil, nil)),
|
||||
ve([]int(nil), redis.ErrNil),
|
||||
},
|
||||
{
|
||||
"strings([v1, v2])",
|
||||
ve(redis.Strings([]interface{}{[]byte("v1"), []byte("v2")}, nil)),
|
||||
ve([]string{"v1", "v2"}, nil),
|
||||
},
|
||||
{
|
||||
"strings(nil)",
|
||||
ve(redis.Strings(nil, nil)),
|
||||
ve([]string(nil), redis.ErrNil),
|
||||
},
|
||||
{
|
||||
"byteslices([v1, v2])",
|
||||
ve(redis.ByteSlices([]interface{}{[]byte("v1"), []byte("v2")}, nil)),
|
||||
ve([][]byte{[]byte("v1"), []byte("v2")}, nil),
|
||||
},
|
||||
{
|
||||
"byteslices(nil)",
|
||||
ve(redis.ByteSlices(nil, nil)),
|
||||
ve([][]byte(nil), redis.ErrNil),
|
||||
},
|
||||
{
|
||||
"values([v1, v2])",
|
||||
ve(redis.Values([]interface{}{[]byte("v1"), []byte("v2")}, nil)),
|
||||
ve([]interface{}{[]byte("v1"), []byte("v2")}, nil),
|
||||
},
|
||||
{
|
||||
"values(nil)",
|
||||
ve(redis.Values(nil, nil)),
|
||||
ve([]interface{}(nil), redis.ErrNil),
|
||||
},
|
||||
{
|
||||
"float64(1.0)",
|
||||
ve(redis.Float64([]byte("1.0"), nil)),
|
||||
ve(float64(1.0), nil),
|
||||
},
|
||||
{
|
||||
"float64(nil)",
|
||||
ve(redis.Float64(nil, nil)),
|
||||
ve(float64(0.0), redis.ErrNil),
|
||||
},
|
||||
{
|
||||
"uint64(1)",
|
||||
ve(redis.Uint64(int64(1), nil)),
|
||||
ve(uint64(1), nil),
|
||||
},
|
||||
{
|
||||
"uint64(-1)",
|
||||
ve(redis.Uint64(int64(-1), nil)),
|
||||
ve(uint64(0), redis.ErrNegativeInt),
|
||||
},
|
||||
}
|
||||
|
||||
func TestReply(t *testing.T) {
|
||||
for _, rt := range replyTests {
|
||||
if rt.actual.err != rt.expected.err {
|
||||
t.Errorf("%s returned err %v, want %v", rt.name, rt.actual.err, rt.expected.err)
|
||||
continue
|
||||
}
|
||||
if !reflect.DeepEqual(rt.actual.v, rt.expected.v) {
|
||||
t.Errorf("%s=%+v, want %+v", rt.name, rt.actual.v, rt.expected.v)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// dial wraps DialDefaultServer() with a more suitable function name for examples.
|
||||
func dial() (redis.Conn, error) {
|
||||
return redis.DialDefaultServer()
|
||||
}
|
||||
|
||||
func ExampleBool() {
|
||||
c, err := dial()
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
return
|
||||
}
|
||||
defer c.Close()
|
||||
|
||||
c.Do("SET", "foo", 1)
|
||||
exists, _ := redis.Bool(c.Do("EXISTS", "foo"))
|
||||
fmt.Printf("%#v\n", exists)
|
||||
// Output:
|
||||
// true
|
||||
}
|
||||
|
||||
func ExampleInt() {
|
||||
c, err := dial()
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
return
|
||||
}
|
||||
defer c.Close()
|
||||
|
||||
c.Do("SET", "k1", 1)
|
||||
n, _ := redis.Int(c.Do("GET", "k1"))
|
||||
fmt.Printf("%#v\n", n)
|
||||
n, _ = redis.Int(c.Do("INCR", "k1"))
|
||||
fmt.Printf("%#v\n", n)
|
||||
// Output:
|
||||
// 1
|
||||
// 2
|
||||
}
|
||||
|
||||
func ExampleInts() {
|
||||
c, err := dial()
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
return
|
||||
}
|
||||
defer c.Close()
|
||||
|
||||
c.Do("SADD", "set_with_integers", 4, 5, 6)
|
||||
ints, _ := redis.Ints(c.Do("SMEMBERS", "set_with_integers"))
|
||||
fmt.Printf("%#v\n", ints)
|
||||
// Output:
|
||||
// []int{4, 5, 6}
|
||||
}
|
||||
|
||||
func ExampleString() {
|
||||
c, err := dial()
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
return
|
||||
}
|
||||
defer c.Close()
|
||||
|
||||
c.Do("SET", "hello", "world")
|
||||
s, err := redis.String(c.Do("GET", "hello"))
|
||||
fmt.Printf("%#v\n", s)
|
||||
// Output:
|
||||
// "world"
|
||||
}
|
555
vendor/github.com/garyburd/redigo/redis/scan.go
generated
vendored
Normal file
555
vendor/github.com/garyburd/redigo/redis/scan.go
generated
vendored
Normal file
@@ -0,0 +1,555 @@
|
||||
// Copyright 2012 Gary Burd
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License"): you may
|
||||
// not use this file except in compliance with the License. You may obtain
|
||||
// a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
// License for the specific language governing permissions and limitations
|
||||
// under the License.
|
||||
|
||||
package redis
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"reflect"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
)
|
||||
|
||||
func ensureLen(d reflect.Value, n int) {
|
||||
if n > d.Cap() {
|
||||
d.Set(reflect.MakeSlice(d.Type(), n, n))
|
||||
} else {
|
||||
d.SetLen(n)
|
||||
}
|
||||
}
|
||||
|
||||
func cannotConvert(d reflect.Value, s interface{}) error {
|
||||
var sname string
|
||||
switch s.(type) {
|
||||
case string:
|
||||
sname = "Redis simple string"
|
||||
case Error:
|
||||
sname = "Redis error"
|
||||
case int64:
|
||||
sname = "Redis integer"
|
||||
case []byte:
|
||||
sname = "Redis bulk string"
|
||||
case []interface{}:
|
||||
sname = "Redis array"
|
||||
default:
|
||||
sname = reflect.TypeOf(s).String()
|
||||
}
|
||||
return fmt.Errorf("cannot convert from %s to %s", sname, d.Type())
|
||||
}
|
||||
|
||||
func convertAssignBulkString(d reflect.Value, s []byte) (err error) {
|
||||
switch d.Type().Kind() {
|
||||
case reflect.Float32, reflect.Float64:
|
||||
var x float64
|
||||
x, err = strconv.ParseFloat(string(s), d.Type().Bits())
|
||||
d.SetFloat(x)
|
||||
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
|
||||
var x int64
|
||||
x, err = strconv.ParseInt(string(s), 10, d.Type().Bits())
|
||||
d.SetInt(x)
|
||||
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
|
||||
var x uint64
|
||||
x, err = strconv.ParseUint(string(s), 10, d.Type().Bits())
|
||||
d.SetUint(x)
|
||||
case reflect.Bool:
|
||||
var x bool
|
||||
x, err = strconv.ParseBool(string(s))
|
||||
d.SetBool(x)
|
||||
case reflect.String:
|
||||
d.SetString(string(s))
|
||||
case reflect.Slice:
|
||||
if d.Type().Elem().Kind() != reflect.Uint8 {
|
||||
err = cannotConvert(d, s)
|
||||
} else {
|
||||
d.SetBytes(s)
|
||||
}
|
||||
default:
|
||||
err = cannotConvert(d, s)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func convertAssignInt(d reflect.Value, s int64) (err error) {
|
||||
switch d.Type().Kind() {
|
||||
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
|
||||
d.SetInt(s)
|
||||
if d.Int() != s {
|
||||
err = strconv.ErrRange
|
||||
d.SetInt(0)
|
||||
}
|
||||
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
|
||||
if s < 0 {
|
||||
err = strconv.ErrRange
|
||||
} else {
|
||||
x := uint64(s)
|
||||
d.SetUint(x)
|
||||
if d.Uint() != x {
|
||||
err = strconv.ErrRange
|
||||
d.SetUint(0)
|
||||
}
|
||||
}
|
||||
case reflect.Bool:
|
||||
d.SetBool(s != 0)
|
||||
default:
|
||||
err = cannotConvert(d, s)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func convertAssignValue(d reflect.Value, s interface{}) (err error) {
|
||||
switch s := s.(type) {
|
||||
case []byte:
|
||||
err = convertAssignBulkString(d, s)
|
||||
case int64:
|
||||
err = convertAssignInt(d, s)
|
||||
default:
|
||||
err = cannotConvert(d, s)
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
func convertAssignArray(d reflect.Value, s []interface{}) error {
|
||||
if d.Type().Kind() != reflect.Slice {
|
||||
return cannotConvert(d, s)
|
||||
}
|
||||
ensureLen(d, len(s))
|
||||
for i := 0; i < len(s); i++ {
|
||||
if err := convertAssignValue(d.Index(i), s[i]); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func convertAssign(d interface{}, s interface{}) (err error) {
|
||||
// Handle the most common destination types using type switches and
|
||||
// fall back to reflection for all other types.
|
||||
switch s := s.(type) {
|
||||
case nil:
|
||||
// ingore
|
||||
case []byte:
|
||||
switch d := d.(type) {
|
||||
case *string:
|
||||
*d = string(s)
|
||||
case *int:
|
||||
*d, err = strconv.Atoi(string(s))
|
||||
case *bool:
|
||||
*d, err = strconv.ParseBool(string(s))
|
||||
case *[]byte:
|
||||
*d = s
|
||||
case *interface{}:
|
||||
*d = s
|
||||
case nil:
|
||||
// skip value
|
||||
default:
|
||||
if d := reflect.ValueOf(d); d.Type().Kind() != reflect.Ptr {
|
||||
err = cannotConvert(d, s)
|
||||
} else {
|
||||
err = convertAssignBulkString(d.Elem(), s)
|
||||
}
|
||||
}
|
||||
case int64:
|
||||
switch d := d.(type) {
|
||||
case *int:
|
||||
x := int(s)
|
||||
if int64(x) != s {
|
||||
err = strconv.ErrRange
|
||||
x = 0
|
||||
}
|
||||
*d = x
|
||||
case *bool:
|
||||
*d = s != 0
|
||||
case *interface{}:
|
||||
*d = s
|
||||
case nil:
|
||||
// skip value
|
||||
default:
|
||||
if d := reflect.ValueOf(d); d.Type().Kind() != reflect.Ptr {
|
||||
err = cannotConvert(d, s)
|
||||
} else {
|
||||
err = convertAssignInt(d.Elem(), s)
|
||||
}
|
||||
}
|
||||
case string:
|
||||
switch d := d.(type) {
|
||||
case *string:
|
||||
*d = string(s)
|
||||
default:
|
||||
err = cannotConvert(reflect.ValueOf(d), s)
|
||||
}
|
||||
case []interface{}:
|
||||
switch d := d.(type) {
|
||||
case *[]interface{}:
|
||||
*d = s
|
||||
case *interface{}:
|
||||
*d = s
|
||||
case nil:
|
||||
// skip value
|
||||
default:
|
||||
if d := reflect.ValueOf(d); d.Type().Kind() != reflect.Ptr {
|
||||
err = cannotConvert(d, s)
|
||||
} else {
|
||||
err = convertAssignArray(d.Elem(), s)
|
||||
}
|
||||
}
|
||||
case Error:
|
||||
err = s
|
||||
default:
|
||||
err = cannotConvert(reflect.ValueOf(d), s)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// Scan copies from src to the values pointed at by dest.
|
||||
//
|
||||
// The values pointed at by dest must be an integer, float, boolean, string,
|
||||
// []byte, interface{} or slices of these types. Scan uses the standard strconv
|
||||
// package to convert bulk strings to numeric and boolean types.
|
||||
//
|
||||
// If a dest value is nil, then the corresponding src value is skipped.
|
||||
//
|
||||
// If a src element is nil, then the corresponding dest value is not modified.
|
||||
//
|
||||
// To enable easy use of Scan in a loop, Scan returns the slice of src
|
||||
// following the copied values.
|
||||
func Scan(src []interface{}, dest ...interface{}) ([]interface{}, error) {
|
||||
if len(src) < len(dest) {
|
||||
return nil, errors.New("redigo.Scan: array short")
|
||||
}
|
||||
var err error
|
||||
for i, d := range dest {
|
||||
err = convertAssign(d, src[i])
|
||||
if err != nil {
|
||||
err = fmt.Errorf("redigo.Scan: cannot assign to dest %d: %v", i, err)
|
||||
break
|
||||
}
|
||||
}
|
||||
return src[len(dest):], err
|
||||
}
|
||||
|
||||
type fieldSpec struct {
|
||||
name string
|
||||
index []int
|
||||
omitEmpty bool
|
||||
}
|
||||
|
||||
type structSpec struct {
|
||||
m map[string]*fieldSpec
|
||||
l []*fieldSpec
|
||||
}
|
||||
|
||||
func (ss *structSpec) fieldSpec(name []byte) *fieldSpec {
|
||||
return ss.m[string(name)]
|
||||
}
|
||||
|
||||
func compileStructSpec(t reflect.Type, depth map[string]int, index []int, ss *structSpec) {
|
||||
for i := 0; i < t.NumField(); i++ {
|
||||
f := t.Field(i)
|
||||
switch {
|
||||
case f.PkgPath != "" && !f.Anonymous:
|
||||
// Ignore unexported fields.
|
||||
case f.Anonymous:
|
||||
// TODO: Handle pointers. Requires change to decoder and
|
||||
// protection against infinite recursion.
|
||||
if f.Type.Kind() == reflect.Struct {
|
||||
compileStructSpec(f.Type, depth, append(index, i), ss)
|
||||
}
|
||||
default:
|
||||
fs := &fieldSpec{name: f.Name}
|
||||
tag := f.Tag.Get("redis")
|
||||
p := strings.Split(tag, ",")
|
||||
if len(p) > 0 {
|
||||
if p[0] == "-" {
|
||||
continue
|
||||
}
|
||||
if len(p[0]) > 0 {
|
||||
fs.name = p[0]
|
||||
}
|
||||
for _, s := range p[1:] {
|
||||
switch s {
|
||||
case "omitempty":
|
||||
fs.omitEmpty = true
|
||||
default:
|
||||
panic(fmt.Errorf("redigo: unknown field tag %s for type %s", s, t.Name()))
|
||||
}
|
||||
}
|
||||
}
|
||||
d, found := depth[fs.name]
|
||||
if !found {
|
||||
d = 1 << 30
|
||||
}
|
||||
switch {
|
||||
case len(index) == d:
|
||||
// At same depth, remove from result.
|
||||
delete(ss.m, fs.name)
|
||||
j := 0
|
||||
for i := 0; i < len(ss.l); i++ {
|
||||
if fs.name != ss.l[i].name {
|
||||
ss.l[j] = ss.l[i]
|
||||
j += 1
|
||||
}
|
||||
}
|
||||
ss.l = ss.l[:j]
|
||||
case len(index) < d:
|
||||
fs.index = make([]int, len(index)+1)
|
||||
copy(fs.index, index)
|
||||
fs.index[len(index)] = i
|
||||
depth[fs.name] = len(index)
|
||||
ss.m[fs.name] = fs
|
||||
ss.l = append(ss.l, fs)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var (
|
||||
structSpecMutex sync.RWMutex
|
||||
structSpecCache = make(map[reflect.Type]*structSpec)
|
||||
defaultFieldSpec = &fieldSpec{}
|
||||
)
|
||||
|
||||
func structSpecForType(t reflect.Type) *structSpec {
|
||||
|
||||
structSpecMutex.RLock()
|
||||
ss, found := structSpecCache[t]
|
||||
structSpecMutex.RUnlock()
|
||||
if found {
|
||||
return ss
|
||||
}
|
||||
|
||||
structSpecMutex.Lock()
|
||||
defer structSpecMutex.Unlock()
|
||||
ss, found = structSpecCache[t]
|
||||
if found {
|
||||
return ss
|
||||
}
|
||||
|
||||
ss = &structSpec{m: make(map[string]*fieldSpec)}
|
||||
compileStructSpec(t, make(map[string]int), nil, ss)
|
||||
structSpecCache[t] = ss
|
||||
return ss
|
||||
}
|
||||
|
||||
var errScanStructValue = errors.New("redigo.ScanStruct: value must be non-nil pointer to a struct")
|
||||
|
||||
// ScanStruct scans alternating names and values from src to a struct. The
|
||||
// HGETALL and CONFIG GET commands return replies in this format.
|
||||
//
|
||||
// ScanStruct uses exported field names to match values in the response. Use
|
||||
// 'redis' field tag to override the name:
|
||||
//
|
||||
// Field int `redis:"myName"`
|
||||
//
|
||||
// Fields with the tag redis:"-" are ignored.
|
||||
//
|
||||
// Integer, float, boolean, string and []byte fields are supported. Scan uses the
|
||||
// standard strconv package to convert bulk string values to numeric and
|
||||
// boolean types.
|
||||
//
|
||||
// If a src element is nil, then the corresponding field is not modified.
|
||||
func ScanStruct(src []interface{}, dest interface{}) error {
|
||||
d := reflect.ValueOf(dest)
|
||||
if d.Kind() != reflect.Ptr || d.IsNil() {
|
||||
return errScanStructValue
|
||||
}
|
||||
d = d.Elem()
|
||||
if d.Kind() != reflect.Struct {
|
||||
return errScanStructValue
|
||||
}
|
||||
ss := structSpecForType(d.Type())
|
||||
|
||||
if len(src)%2 != 0 {
|
||||
return errors.New("redigo.ScanStruct: number of values not a multiple of 2")
|
||||
}
|
||||
|
||||
for i := 0; i < len(src); i += 2 {
|
||||
s := src[i+1]
|
||||
if s == nil {
|
||||
continue
|
||||
}
|
||||
name, ok := src[i].([]byte)
|
||||
if !ok {
|
||||
return fmt.Errorf("redigo.ScanStruct: key %d not a bulk string value", i)
|
||||
}
|
||||
fs := ss.fieldSpec(name)
|
||||
if fs == nil {
|
||||
continue
|
||||
}
|
||||
if err := convertAssignValue(d.FieldByIndex(fs.index), s); err != nil {
|
||||
return fmt.Errorf("redigo.ScanStruct: cannot assign field %s: %v", fs.name, err)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
var (
|
||||
errScanSliceValue = errors.New("redigo.ScanSlice: dest must be non-nil pointer to a struct")
|
||||
)
|
||||
|
||||
// ScanSlice scans src to the slice pointed to by dest. The elements the dest
|
||||
// slice must be integer, float, boolean, string, struct or pointer to struct
|
||||
// values.
|
||||
//
|
||||
// Struct fields must be integer, float, boolean or string values. All struct
|
||||
// fields are used unless a subset is specified using fieldNames.
|
||||
func ScanSlice(src []interface{}, dest interface{}, fieldNames ...string) error {
|
||||
d := reflect.ValueOf(dest)
|
||||
if d.Kind() != reflect.Ptr || d.IsNil() {
|
||||
return errScanSliceValue
|
||||
}
|
||||
d = d.Elem()
|
||||
if d.Kind() != reflect.Slice {
|
||||
return errScanSliceValue
|
||||
}
|
||||
|
||||
isPtr := false
|
||||
t := d.Type().Elem()
|
||||
if t.Kind() == reflect.Ptr && t.Elem().Kind() == reflect.Struct {
|
||||
isPtr = true
|
||||
t = t.Elem()
|
||||
}
|
||||
|
||||
if t.Kind() != reflect.Struct {
|
||||
ensureLen(d, len(src))
|
||||
for i, s := range src {
|
||||
if s == nil {
|
||||
continue
|
||||
}
|
||||
if err := convertAssignValue(d.Index(i), s); err != nil {
|
||||
return fmt.Errorf("redigo.ScanSlice: cannot assign element %d: %v", i, err)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
ss := structSpecForType(t)
|
||||
fss := ss.l
|
||||
if len(fieldNames) > 0 {
|
||||
fss = make([]*fieldSpec, len(fieldNames))
|
||||
for i, name := range fieldNames {
|
||||
fss[i] = ss.m[name]
|
||||
if fss[i] == nil {
|
||||
return fmt.Errorf("redigo.ScanSlice: ScanSlice bad field name %s", name)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if len(fss) == 0 {
|
||||
return errors.New("redigo.ScanSlice: no struct fields")
|
||||
}
|
||||
|
||||
n := len(src) / len(fss)
|
||||
if n*len(fss) != len(src) {
|
||||
return errors.New("redigo.ScanSlice: length not a multiple of struct field count")
|
||||
}
|
||||
|
||||
ensureLen(d, n)
|
||||
for i := 0; i < n; i++ {
|
||||
d := d.Index(i)
|
||||
if isPtr {
|
||||
if d.IsNil() {
|
||||
d.Set(reflect.New(t))
|
||||
}
|
||||
d = d.Elem()
|
||||
}
|
||||
for j, fs := range fss {
|
||||
s := src[i*len(fss)+j]
|
||||
if s == nil {
|
||||
continue
|
||||
}
|
||||
if err := convertAssignValue(d.FieldByIndex(fs.index), s); err != nil {
|
||||
return fmt.Errorf("redigo.ScanSlice: cannot assign element %d to field %s: %v", i*len(fss)+j, fs.name, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Args is a helper for constructing command arguments from structured values.
|
||||
type Args []interface{}
|
||||
|
||||
// Add returns the result of appending value to args.
|
||||
func (args Args) Add(value ...interface{}) Args {
|
||||
return append(args, value...)
|
||||
}
|
||||
|
||||
// AddFlat returns the result of appending the flattened value of v to args.
|
||||
//
|
||||
// Maps are flattened by appending the alternating keys and map values to args.
|
||||
//
|
||||
// Slices are flattened by appending the slice elements to args.
|
||||
//
|
||||
// Structs are flattened by appending the alternating names and values of
|
||||
// exported fields to args. If v is a nil struct pointer, then nothing is
|
||||
// appended. The 'redis' field tag overrides struct field names. See ScanStruct
|
||||
// for more information on the use of the 'redis' field tag.
|
||||
//
|
||||
// Other types are appended to args as is.
|
||||
func (args Args) AddFlat(v interface{}) Args {
|
||||
rv := reflect.ValueOf(v)
|
||||
switch rv.Kind() {
|
||||
case reflect.Struct:
|
||||
args = flattenStruct(args, rv)
|
||||
case reflect.Slice:
|
||||
for i := 0; i < rv.Len(); i++ {
|
||||
args = append(args, rv.Index(i).Interface())
|
||||
}
|
||||
case reflect.Map:
|
||||
for _, k := range rv.MapKeys() {
|
||||
args = append(args, k.Interface(), rv.MapIndex(k).Interface())
|
||||
}
|
||||
case reflect.Ptr:
|
||||
if rv.Type().Elem().Kind() == reflect.Struct {
|
||||
if !rv.IsNil() {
|
||||
args = flattenStruct(args, rv.Elem())
|
||||
}
|
||||
} else {
|
||||
args = append(args, v)
|
||||
}
|
||||
default:
|
||||
args = append(args, v)
|
||||
}
|
||||
return args
|
||||
}
|
||||
|
||||
func flattenStruct(args Args, v reflect.Value) Args {
|
||||
ss := structSpecForType(v.Type())
|
||||
for _, fs := range ss.l {
|
||||
fv := v.FieldByIndex(fs.index)
|
||||
if fs.omitEmpty {
|
||||
var empty = false
|
||||
switch fv.Kind() {
|
||||
case reflect.Array, reflect.Map, reflect.Slice, reflect.String:
|
||||
empty = fv.Len() == 0
|
||||
case reflect.Bool:
|
||||
empty = !fv.Bool()
|
||||
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
|
||||
empty = fv.Int() == 0
|
||||
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr:
|
||||
empty = fv.Uint() == 0
|
||||
case reflect.Float32, reflect.Float64:
|
||||
empty = fv.Float() == 0
|
||||
case reflect.Interface, reflect.Ptr:
|
||||
empty = fv.IsNil()
|
||||
}
|
||||
if empty {
|
||||
continue
|
||||
}
|
||||
}
|
||||
args = append(args, fs.name, fv.Interface())
|
||||
}
|
||||
return args
|
||||
}
|
440
vendor/github.com/garyburd/redigo/redis/scan_test.go
generated
vendored
Normal file
440
vendor/github.com/garyburd/redigo/redis/scan_test.go
generated
vendored
Normal file
@@ -0,0 +1,440 @@
|
||||
// Copyright 2012 Gary Burd
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License"): you may
|
||||
// not use this file except in compliance with the License. You may obtain
|
||||
// a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
// License for the specific language governing permissions and limitations
|
||||
// under the License.
|
||||
|
||||
package redis_test
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"math"
|
||||
"reflect"
|
||||
"testing"
|
||||
|
||||
"github.com/garyburd/redigo/redis"
|
||||
)
|
||||
|
||||
var scanConversionTests = []struct {
|
||||
src interface{}
|
||||
dest interface{}
|
||||
}{
|
||||
{[]byte("-inf"), math.Inf(-1)},
|
||||
{[]byte("+inf"), math.Inf(1)},
|
||||
{[]byte("0"), float64(0)},
|
||||
{[]byte("3.14159"), float64(3.14159)},
|
||||
{[]byte("3.14"), float32(3.14)},
|
||||
{[]byte("-100"), int(-100)},
|
||||
{[]byte("101"), int(101)},
|
||||
{int64(102), int(102)},
|
||||
{[]byte("103"), uint(103)},
|
||||
{int64(104), uint(104)},
|
||||
{[]byte("105"), int8(105)},
|
||||
{int64(106), int8(106)},
|
||||
{[]byte("107"), uint8(107)},
|
||||
{int64(108), uint8(108)},
|
||||
{[]byte("0"), false},
|
||||
{int64(0), false},
|
||||
{[]byte("f"), false},
|
||||
{[]byte("1"), true},
|
||||
{int64(1), true},
|
||||
{[]byte("t"), true},
|
||||
{"hello", "hello"},
|
||||
{[]byte("hello"), "hello"},
|
||||
{[]byte("world"), []byte("world")},
|
||||
{[]interface{}{[]byte("foo")}, []interface{}{[]byte("foo")}},
|
||||
{[]interface{}{[]byte("foo")}, []string{"foo"}},
|
||||
{[]interface{}{[]byte("hello"), []byte("world")}, []string{"hello", "world"}},
|
||||
{[]interface{}{[]byte("bar")}, [][]byte{[]byte("bar")}},
|
||||
{[]interface{}{[]byte("1")}, []int{1}},
|
||||
{[]interface{}{[]byte("1"), []byte("2")}, []int{1, 2}},
|
||||
{[]interface{}{[]byte("1"), []byte("2")}, []float64{1, 2}},
|
||||
{[]interface{}{[]byte("1")}, []byte{1}},
|
||||
{[]interface{}{[]byte("1")}, []bool{true}},
|
||||
}
|
||||
|
||||
func TestScanConversion(t *testing.T) {
|
||||
for _, tt := range scanConversionTests {
|
||||
values := []interface{}{tt.src}
|
||||
dest := reflect.New(reflect.TypeOf(tt.dest))
|
||||
values, err := redis.Scan(values, dest.Interface())
|
||||
if err != nil {
|
||||
t.Errorf("Scan(%v) returned error %v", tt, err)
|
||||
continue
|
||||
}
|
||||
if !reflect.DeepEqual(tt.dest, dest.Elem().Interface()) {
|
||||
t.Errorf("Scan(%v) returned %v, want %v", tt, dest.Elem().Interface(), tt.dest)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var scanConversionErrorTests = []struct {
|
||||
src interface{}
|
||||
dest interface{}
|
||||
}{
|
||||
{[]byte("1234"), byte(0)},
|
||||
{int64(1234), byte(0)},
|
||||
{[]byte("-1"), byte(0)},
|
||||
{int64(-1), byte(0)},
|
||||
{[]byte("junk"), false},
|
||||
{redis.Error("blah"), false},
|
||||
}
|
||||
|
||||
func TestScanConversionError(t *testing.T) {
|
||||
for _, tt := range scanConversionErrorTests {
|
||||
values := []interface{}{tt.src}
|
||||
dest := reflect.New(reflect.TypeOf(tt.dest))
|
||||
values, err := redis.Scan(values, dest.Interface())
|
||||
if err == nil {
|
||||
t.Errorf("Scan(%v) did not return error", tt)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func ExampleScan() {
|
||||
c, err := dial()
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
return
|
||||
}
|
||||
defer c.Close()
|
||||
|
||||
c.Send("HMSET", "album:1", "title", "Red", "rating", 5)
|
||||
c.Send("HMSET", "album:2", "title", "Earthbound", "rating", 1)
|
||||
c.Send("HMSET", "album:3", "title", "Beat")
|
||||
c.Send("LPUSH", "albums", "1")
|
||||
c.Send("LPUSH", "albums", "2")
|
||||
c.Send("LPUSH", "albums", "3")
|
||||
values, err := redis.Values(c.Do("SORT", "albums",
|
||||
"BY", "album:*->rating",
|
||||
"GET", "album:*->title",
|
||||
"GET", "album:*->rating"))
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
return
|
||||
}
|
||||
|
||||
for len(values) > 0 {
|
||||
var title string
|
||||
rating := -1 // initialize to illegal value to detect nil.
|
||||
values, err = redis.Scan(values, &title, &rating)
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
return
|
||||
}
|
||||
if rating == -1 {
|
||||
fmt.Println(title, "not-rated")
|
||||
} else {
|
||||
fmt.Println(title, rating)
|
||||
}
|
||||
}
|
||||
// Output:
|
||||
// Beat not-rated
|
||||
// Earthbound 1
|
||||
// Red 5
|
||||
}
|
||||
|
||||
type s0 struct {
|
||||
X int
|
||||
Y int `redis:"y"`
|
||||
Bt bool
|
||||
}
|
||||
|
||||
type s1 struct {
|
||||
X int `redis:"-"`
|
||||
I int `redis:"i"`
|
||||
U uint `redis:"u"`
|
||||
S string `redis:"s"`
|
||||
P []byte `redis:"p"`
|
||||
B bool `redis:"b"`
|
||||
Bt bool
|
||||
Bf bool
|
||||
s0
|
||||
}
|
||||
|
||||
var scanStructTests = []struct {
|
||||
title string
|
||||
reply []string
|
||||
value interface{}
|
||||
}{
|
||||
{"basic",
|
||||
[]string{"i", "-1234", "u", "5678", "s", "hello", "p", "world", "b", "t", "Bt", "1", "Bf", "0", "X", "123", "y", "456"},
|
||||
&s1{I: -1234, U: 5678, S: "hello", P: []byte("world"), B: true, Bt: true, Bf: false, s0: s0{X: 123, Y: 456}},
|
||||
},
|
||||
}
|
||||
|
||||
func TestScanStruct(t *testing.T) {
|
||||
for _, tt := range scanStructTests {
|
||||
|
||||
var reply []interface{}
|
||||
for _, v := range tt.reply {
|
||||
reply = append(reply, []byte(v))
|
||||
}
|
||||
|
||||
value := reflect.New(reflect.ValueOf(tt.value).Type().Elem())
|
||||
|
||||
if err := redis.ScanStruct(reply, value.Interface()); err != nil {
|
||||
t.Fatalf("ScanStruct(%s) returned error %v", tt.title, err)
|
||||
}
|
||||
|
||||
if !reflect.DeepEqual(value.Interface(), tt.value) {
|
||||
t.Fatalf("ScanStruct(%s) returned %v, want %v", tt.title, value.Interface(), tt.value)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestBadScanStructArgs(t *testing.T) {
|
||||
x := []interface{}{"A", "b"}
|
||||
test := func(v interface{}) {
|
||||
if err := redis.ScanStruct(x, v); err == nil {
|
||||
t.Errorf("Expect error for ScanStruct(%T, %T)", x, v)
|
||||
}
|
||||
}
|
||||
|
||||
test(nil)
|
||||
|
||||
var v0 *struct{}
|
||||
test(v0)
|
||||
|
||||
var v1 int
|
||||
test(&v1)
|
||||
|
||||
x = x[:1]
|
||||
v2 := struct{ A string }{}
|
||||
test(&v2)
|
||||
}
|
||||
|
||||
var scanSliceTests = []struct {
|
||||
src []interface{}
|
||||
fieldNames []string
|
||||
ok bool
|
||||
dest interface{}
|
||||
}{
|
||||
{
|
||||
[]interface{}{[]byte("1"), nil, []byte("-1")},
|
||||
nil,
|
||||
true,
|
||||
[]int{1, 0, -1},
|
||||
},
|
||||
{
|
||||
[]interface{}{[]byte("1"), nil, []byte("2")},
|
||||
nil,
|
||||
true,
|
||||
[]uint{1, 0, 2},
|
||||
},
|
||||
{
|
||||
[]interface{}{[]byte("-1")},
|
||||
nil,
|
||||
false,
|
||||
[]uint{1},
|
||||
},
|
||||
{
|
||||
[]interface{}{[]byte("hello"), nil, []byte("world")},
|
||||
nil,
|
||||
true,
|
||||
[][]byte{[]byte("hello"), nil, []byte("world")},
|
||||
},
|
||||
{
|
||||
[]interface{}{[]byte("hello"), nil, []byte("world")},
|
||||
nil,
|
||||
true,
|
||||
[]string{"hello", "", "world"},
|
||||
},
|
||||
{
|
||||
[]interface{}{[]byte("a1"), []byte("b1"), []byte("a2"), []byte("b2")},
|
||||
nil,
|
||||
true,
|
||||
[]struct{ A, B string }{{"a1", "b1"}, {"a2", "b2"}},
|
||||
},
|
||||
{
|
||||
[]interface{}{[]byte("a1"), []byte("b1")},
|
||||
nil,
|
||||
false,
|
||||
[]struct{ A, B, C string }{{"a1", "b1", ""}},
|
||||
},
|
||||
{
|
||||
[]interface{}{[]byte("a1"), []byte("b1"), []byte("a2"), []byte("b2")},
|
||||
nil,
|
||||
true,
|
||||
[]*struct{ A, B string }{{"a1", "b1"}, {"a2", "b2"}},
|
||||
},
|
||||
{
|
||||
[]interface{}{[]byte("a1"), []byte("b1"), []byte("a2"), []byte("b2")},
|
||||
[]string{"A", "B"},
|
||||
true,
|
||||
[]struct{ A, C, B string }{{"a1", "", "b1"}, {"a2", "", "b2"}},
|
||||
},
|
||||
{
|
||||
[]interface{}{[]byte("a1"), []byte("b1"), []byte("a2"), []byte("b2")},
|
||||
nil,
|
||||
false,
|
||||
[]struct{}{},
|
||||
},
|
||||
}
|
||||
|
||||
func TestScanSlice(t *testing.T) {
|
||||
for _, tt := range scanSliceTests {
|
||||
|
||||
typ := reflect.ValueOf(tt.dest).Type()
|
||||
dest := reflect.New(typ)
|
||||
|
||||
err := redis.ScanSlice(tt.src, dest.Interface(), tt.fieldNames...)
|
||||
if tt.ok != (err == nil) {
|
||||
t.Errorf("ScanSlice(%v, []%s, %v) returned error %v", tt.src, typ, tt.fieldNames, err)
|
||||
continue
|
||||
}
|
||||
if tt.ok && !reflect.DeepEqual(dest.Elem().Interface(), tt.dest) {
|
||||
t.Errorf("ScanSlice(src, []%s) returned %#v, want %#v", typ, dest.Elem().Interface(), tt.dest)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func ExampleScanSlice() {
|
||||
c, err := dial()
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
return
|
||||
}
|
||||
defer c.Close()
|
||||
|
||||
c.Send("HMSET", "album:1", "title", "Red", "rating", 5)
|
||||
c.Send("HMSET", "album:2", "title", "Earthbound", "rating", 1)
|
||||
c.Send("HMSET", "album:3", "title", "Beat", "rating", 4)
|
||||
c.Send("LPUSH", "albums", "1")
|
||||
c.Send("LPUSH", "albums", "2")
|
||||
c.Send("LPUSH", "albums", "3")
|
||||
values, err := redis.Values(c.Do("SORT", "albums",
|
||||
"BY", "album:*->rating",
|
||||
"GET", "album:*->title",
|
||||
"GET", "album:*->rating"))
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
return
|
||||
}
|
||||
|
||||
var albums []struct {
|
||||
Title string
|
||||
Rating int
|
||||
}
|
||||
if err := redis.ScanSlice(values, &albums); err != nil {
|
||||
fmt.Println(err)
|
||||
return
|
||||
}
|
||||
fmt.Printf("%v\n", albums)
|
||||
// Output:
|
||||
// [{Earthbound 1} {Beat 4} {Red 5}]
|
||||
}
|
||||
|
||||
var argsTests = []struct {
|
||||
title string
|
||||
actual redis.Args
|
||||
expected redis.Args
|
||||
}{
|
||||
{"struct ptr",
|
||||
redis.Args{}.AddFlat(&struct {
|
||||
I int `redis:"i"`
|
||||
U uint `redis:"u"`
|
||||
S string `redis:"s"`
|
||||
P []byte `redis:"p"`
|
||||
M map[string]string `redis:"m"`
|
||||
Bt bool
|
||||
Bf bool
|
||||
}{
|
||||
-1234, 5678, "hello", []byte("world"), map[string]string{"hello": "world"}, true, false,
|
||||
}),
|
||||
redis.Args{"i", int(-1234), "u", uint(5678), "s", "hello", "p", []byte("world"), "m", map[string]string{"hello": "world"}, "Bt", true, "Bf", false},
|
||||
},
|
||||
{"struct",
|
||||
redis.Args{}.AddFlat(struct{ I int }{123}),
|
||||
redis.Args{"I", 123},
|
||||
},
|
||||
{"slice",
|
||||
redis.Args{}.Add(1).AddFlat([]string{"a", "b", "c"}).Add(2),
|
||||
redis.Args{1, "a", "b", "c", 2},
|
||||
},
|
||||
{"struct omitempty",
|
||||
redis.Args{}.AddFlat(&struct {
|
||||
I int `redis:"i,omitempty"`
|
||||
U uint `redis:"u,omitempty"`
|
||||
S string `redis:"s,omitempty"`
|
||||
P []byte `redis:"p,omitempty"`
|
||||
M map[string]string `redis:"m,omitempty"`
|
||||
Bt bool `redis:"Bt,omitempty"`
|
||||
Bf bool `redis:"Bf,omitempty"`
|
||||
}{
|
||||
0, 0, "", []byte{}, map[string]string{}, true, false,
|
||||
}),
|
||||
redis.Args{"Bt", true},
|
||||
},
|
||||
}
|
||||
|
||||
func TestArgs(t *testing.T) {
|
||||
for _, tt := range argsTests {
|
||||
if !reflect.DeepEqual(tt.actual, tt.expected) {
|
||||
t.Fatalf("%s is %v, want %v", tt.title, tt.actual, tt.expected)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func ExampleArgs() {
|
||||
c, err := dial()
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
return
|
||||
}
|
||||
defer c.Close()
|
||||
|
||||
var p1, p2 struct {
|
||||
Title string `redis:"title"`
|
||||
Author string `redis:"author"`
|
||||
Body string `redis:"body"`
|
||||
}
|
||||
|
||||
p1.Title = "Example"
|
||||
p1.Author = "Gary"
|
||||
p1.Body = "Hello"
|
||||
|
||||
if _, err := c.Do("HMSET", redis.Args{}.Add("id1").AddFlat(&p1)...); err != nil {
|
||||
fmt.Println(err)
|
||||
return
|
||||
}
|
||||
|
||||
m := map[string]string{
|
||||
"title": "Example2",
|
||||
"author": "Steve",
|
||||
"body": "Map",
|
||||
}
|
||||
|
||||
if _, err := c.Do("HMSET", redis.Args{}.Add("id2").AddFlat(m)...); err != nil {
|
||||
fmt.Println(err)
|
||||
return
|
||||
}
|
||||
|
||||
for _, id := range []string{"id1", "id2"} {
|
||||
|
||||
v, err := redis.Values(c.Do("HGETALL", id))
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
return
|
||||
}
|
||||
|
||||
if err := redis.ScanStruct(v, &p2); err != nil {
|
||||
fmt.Println(err)
|
||||
return
|
||||
}
|
||||
|
||||
fmt.Printf("%+v\n", p2)
|
||||
}
|
||||
|
||||
// Output:
|
||||
// {Title:Example Author:Gary Body:Hello}
|
||||
// {Title:Example2 Author:Steve Body:Map}
|
||||
}
|
86
vendor/github.com/garyburd/redigo/redis/script.go
generated
vendored
Normal file
86
vendor/github.com/garyburd/redigo/redis/script.go
generated
vendored
Normal file
@@ -0,0 +1,86 @@
|
||||
// Copyright 2012 Gary Burd
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License"): you may
|
||||
// not use this file except in compliance with the License. You may obtain
|
||||
// a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
// License for the specific language governing permissions and limitations
|
||||
// under the License.
|
||||
|
||||
package redis
|
||||
|
||||
import (
|
||||
"crypto/sha1"
|
||||
"encoding/hex"
|
||||
"io"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// Script encapsulates the source, hash and key count for a Lua script. See
|
||||
// http://redis.io/commands/eval for information on scripts in Redis.
|
||||
type Script struct {
|
||||
keyCount int
|
||||
src string
|
||||
hash string
|
||||
}
|
||||
|
||||
// NewScript returns a new script object. If keyCount is greater than or equal
|
||||
// to zero, then the count is automatically inserted in the EVAL command
|
||||
// argument list. If keyCount is less than zero, then the application supplies
|
||||
// the count as the first value in the keysAndArgs argument to the Do, Send and
|
||||
// SendHash methods.
|
||||
func NewScript(keyCount int, src string) *Script {
|
||||
h := sha1.New()
|
||||
io.WriteString(h, src)
|
||||
return &Script{keyCount, src, hex.EncodeToString(h.Sum(nil))}
|
||||
}
|
||||
|
||||
func (s *Script) args(spec string, keysAndArgs []interface{}) []interface{} {
|
||||
var args []interface{}
|
||||
if s.keyCount < 0 {
|
||||
args = make([]interface{}, 1+len(keysAndArgs))
|
||||
args[0] = spec
|
||||
copy(args[1:], keysAndArgs)
|
||||
} else {
|
||||
args = make([]interface{}, 2+len(keysAndArgs))
|
||||
args[0] = spec
|
||||
args[1] = s.keyCount
|
||||
copy(args[2:], keysAndArgs)
|
||||
}
|
||||
return args
|
||||
}
|
||||
|
||||
// Do evaluates the script. Under the covers, Do optimistically evaluates the
|
||||
// script using the EVALSHA command. If the command fails because the script is
|
||||
// not loaded, then Do evaluates the script using the EVAL command (thus
|
||||
// causing the script to load).
|
||||
func (s *Script) Do(c Conn, keysAndArgs ...interface{}) (interface{}, error) {
|
||||
v, err := c.Do("EVALSHA", s.args(s.hash, keysAndArgs)...)
|
||||
if e, ok := err.(Error); ok && strings.HasPrefix(string(e), "NOSCRIPT ") {
|
||||
v, err = c.Do("EVAL", s.args(s.src, keysAndArgs)...)
|
||||
}
|
||||
return v, err
|
||||
}
|
||||
|
||||
// SendHash evaluates the script without waiting for the reply. The script is
|
||||
// evaluated with the EVALSHA command. The application must ensure that the
|
||||
// script is loaded by a previous call to Send, Do or Load methods.
|
||||
func (s *Script) SendHash(c Conn, keysAndArgs ...interface{}) error {
|
||||
return c.Send("EVALSHA", s.args(s.hash, keysAndArgs)...)
|
||||
}
|
||||
|
||||
// Send evaluates the script without waiting for the reply.
|
||||
func (s *Script) Send(c Conn, keysAndArgs ...interface{}) error {
|
||||
return c.Send("EVAL", s.args(s.src, keysAndArgs)...)
|
||||
}
|
||||
|
||||
// Load loads the script without evaluating it.
|
||||
func (s *Script) Load(c Conn) error {
|
||||
_, err := c.Do("SCRIPT", "LOAD", s.src)
|
||||
return err
|
||||
}
|
100
vendor/github.com/garyburd/redigo/redis/script_test.go
generated
vendored
Normal file
100
vendor/github.com/garyburd/redigo/redis/script_test.go
generated
vendored
Normal file
@@ -0,0 +1,100 @@
|
||||
// Copyright 2012 Gary Burd
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License"): you may
|
||||
// not use this file except in compliance with the License. You may obtain
|
||||
// a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
// License for the specific language governing permissions and limitations
|
||||
// under the License.
|
||||
|
||||
package redis_test
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"reflect"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/garyburd/redigo/redis"
|
||||
)
|
||||
|
||||
var (
|
||||
// These variables are declared at package level to remove distracting
|
||||
// details from the examples.
|
||||
c redis.Conn
|
||||
reply interface{}
|
||||
err error
|
||||
)
|
||||
|
||||
func ExampleScript() {
|
||||
// Initialize a package-level variable with a script.
|
||||
var getScript = redis.NewScript(1, `return redis.call('get', KEYS[1])`)
|
||||
|
||||
// In a function, use the script Do method to evaluate the script. The Do
|
||||
// method optimistically uses the EVALSHA command. If the script is not
|
||||
// loaded, then the Do method falls back to the EVAL command.
|
||||
reply, err = getScript.Do(c, "foo")
|
||||
}
|
||||
|
||||
func TestScript(t *testing.T) {
|
||||
c, err := redis.DialDefaultServer()
|
||||
if err != nil {
|
||||
t.Fatalf("error connection to database, %v", err)
|
||||
}
|
||||
defer c.Close()
|
||||
|
||||
// To test fall back in Do, we make script unique by adding comment with current time.
|
||||
script := fmt.Sprintf("--%d\nreturn {KEYS[1],KEYS[2],ARGV[1],ARGV[2]}", time.Now().UnixNano())
|
||||
s := redis.NewScript(2, script)
|
||||
reply := []interface{}{[]byte("key1"), []byte("key2"), []byte("arg1"), []byte("arg2")}
|
||||
|
||||
v, err := s.Do(c, "key1", "key2", "arg1", "arg2")
|
||||
if err != nil {
|
||||
t.Errorf("s.Do(c, ...) returned %v", err)
|
||||
}
|
||||
|
||||
if !reflect.DeepEqual(v, reply) {
|
||||
t.Errorf("s.Do(c, ..); = %v, want %v", v, reply)
|
||||
}
|
||||
|
||||
err = s.Load(c)
|
||||
if err != nil {
|
||||
t.Errorf("s.Load(c) returned %v", err)
|
||||
}
|
||||
|
||||
err = s.SendHash(c, "key1", "key2", "arg1", "arg2")
|
||||
if err != nil {
|
||||
t.Errorf("s.SendHash(c, ...) returned %v", err)
|
||||
}
|
||||
|
||||
err = c.Flush()
|
||||
if err != nil {
|
||||
t.Errorf("c.Flush() returned %v", err)
|
||||
}
|
||||
|
||||
v, err = c.Receive()
|
||||
if !reflect.DeepEqual(v, reply) {
|
||||
t.Errorf("s.SendHash(c, ..); c.Receive() = %v, want %v", v, reply)
|
||||
}
|
||||
|
||||
err = s.Send(c, "key1", "key2", "arg1", "arg2")
|
||||
if err != nil {
|
||||
t.Errorf("s.Send(c, ...) returned %v", err)
|
||||
}
|
||||
|
||||
err = c.Flush()
|
||||
if err != nil {
|
||||
t.Errorf("c.Flush() returned %v", err)
|
||||
}
|
||||
|
||||
v, err = c.Receive()
|
||||
if !reflect.DeepEqual(v, reply) {
|
||||
t.Errorf("s.Send(c, ..); c.Receive() = %v, want %v", v, reply)
|
||||
}
|
||||
|
||||
}
|
177
vendor/github.com/garyburd/redigo/redis/test_test.go
generated
vendored
Normal file
177
vendor/github.com/garyburd/redigo/redis/test_test.go
generated
vendored
Normal file
@@ -0,0 +1,177 @@
|
||||
// Copyright 2012 Gary Burd
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License"): you may
|
||||
// not use this file except in compliance with the License. You may obtain
|
||||
// a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
// License for the specific language governing permissions and limitations
|
||||
// under the License.
|
||||
|
||||
package redis
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"errors"
|
||||
"flag"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"os/exec"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
func SetNowFunc(f func() time.Time) {
|
||||
nowFunc = f
|
||||
}
|
||||
|
||||
var (
|
||||
ErrNegativeInt = errNegativeInt
|
||||
|
||||
serverPath = flag.String("redis-server", "redis-server", "Path to redis server binary")
|
||||
serverBasePort = flag.Int("redis-port", 16379, "Beginning of port range for test servers")
|
||||
serverLogName = flag.String("redis-log", "", "Write Redis server logs to `filename`")
|
||||
serverLog = ioutil.Discard
|
||||
|
||||
defaultServerMu sync.Mutex
|
||||
defaultServer *Server
|
||||
defaultServerErr error
|
||||
)
|
||||
|
||||
type Server struct {
|
||||
name string
|
||||
cmd *exec.Cmd
|
||||
done chan struct{}
|
||||
}
|
||||
|
||||
func NewServer(name string, args ...string) (*Server, error) {
|
||||
s := &Server{
|
||||
name: name,
|
||||
cmd: exec.Command(*serverPath, args...),
|
||||
done: make(chan struct{}),
|
||||
}
|
||||
|
||||
r, err := s.cmd.StdoutPipe()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
err = s.cmd.Start()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
ready := make(chan error, 1)
|
||||
go s.watch(r, ready)
|
||||
|
||||
select {
|
||||
case err = <-ready:
|
||||
case <-time.After(time.Second * 10):
|
||||
err = errors.New("timeout waiting for server to start")
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
s.Stop()
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return s, nil
|
||||
}
|
||||
|
||||
func (s *Server) watch(r io.Reader, ready chan error) {
|
||||
fmt.Fprintf(serverLog, "%d START %s \n", s.cmd.Process.Pid, s.name)
|
||||
var listening bool
|
||||
var text string
|
||||
scn := bufio.NewScanner(r)
|
||||
for scn.Scan() {
|
||||
text = scn.Text()
|
||||
fmt.Fprintf(serverLog, "%s\n", text)
|
||||
if !listening {
|
||||
if strings.Contains(text, "The server is now ready to accept connections on port") {
|
||||
listening = true
|
||||
ready <- nil
|
||||
}
|
||||
}
|
||||
}
|
||||
if !listening {
|
||||
ready <- fmt.Errorf("server exited: %s", text)
|
||||
}
|
||||
s.cmd.Wait()
|
||||
fmt.Fprintf(serverLog, "%d STOP %s \n", s.cmd.Process.Pid, s.name)
|
||||
close(s.done)
|
||||
}
|
||||
|
||||
func (s *Server) Stop() {
|
||||
s.cmd.Process.Signal(os.Interrupt)
|
||||
<-s.done
|
||||
}
|
||||
|
||||
// stopDefaultServer stops the server created by DialDefaultServer.
|
||||
func stopDefaultServer() {
|
||||
defaultServerMu.Lock()
|
||||
defer defaultServerMu.Unlock()
|
||||
if defaultServer != nil {
|
||||
defaultServer.Stop()
|
||||
defaultServer = nil
|
||||
}
|
||||
}
|
||||
|
||||
// startDefaultServer starts the default server if not already running.
|
||||
func startDefaultServer() error {
|
||||
defaultServerMu.Lock()
|
||||
defer defaultServerMu.Unlock()
|
||||
if defaultServer != nil || defaultServerErr != nil {
|
||||
return defaultServerErr
|
||||
}
|
||||
defaultServer, defaultServerErr = NewServer(
|
||||
"default",
|
||||
"--port", strconv.Itoa(*serverBasePort),
|
||||
"--save", "",
|
||||
"--appendonly", "no")
|
||||
return defaultServerErr
|
||||
}
|
||||
|
||||
// DialDefaultServer starts the test server if not already started and dials a
|
||||
// connection to the server.
|
||||
func DialDefaultServer() (Conn, error) {
|
||||
if err := startDefaultServer(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
c, err := Dial("tcp", fmt.Sprintf(":%d", *serverBasePort), DialReadTimeout(1*time.Second), DialWriteTimeout(1*time.Second))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
c.Do("FLUSHDB")
|
||||
return c, nil
|
||||
}
|
||||
|
||||
func TestMain(m *testing.M) {
|
||||
os.Exit(func() int {
|
||||
flag.Parse()
|
||||
|
||||
var f *os.File
|
||||
if *serverLogName != "" {
|
||||
var err error
|
||||
f, err = os.OpenFile(*serverLogName, os.O_WRONLY|os.O_CREATE|os.O_APPEND, 0600)
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "Error opening redis-log: %v\n", err)
|
||||
return 1
|
||||
}
|
||||
defer f.Close()
|
||||
serverLog = f
|
||||
}
|
||||
|
||||
defer stopDefaultServer()
|
||||
|
||||
return m.Run()
|
||||
}())
|
||||
}
|
113
vendor/github.com/garyburd/redigo/redis/zpop_example_test.go
generated
vendored
Normal file
113
vendor/github.com/garyburd/redigo/redis/zpop_example_test.go
generated
vendored
Normal file
@@ -0,0 +1,113 @@
|
||||
// Copyright 2013 Gary Burd
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License"): you may
|
||||
// not use this file except in compliance with the License. You may obtain
|
||||
// a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
// License for the specific language governing permissions and limitations
|
||||
// under the License.
|
||||
|
||||
package redis_test
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/garyburd/redigo/redis"
|
||||
)
|
||||
|
||||
// zpop pops a value from the ZSET key using WATCH/MULTI/EXEC commands.
|
||||
func zpop(c redis.Conn, key string) (result string, err error) {
|
||||
|
||||
defer func() {
|
||||
// Return connection to normal state on error.
|
||||
if err != nil {
|
||||
c.Do("DISCARD")
|
||||
}
|
||||
}()
|
||||
|
||||
// Loop until transaction is successful.
|
||||
for {
|
||||
if _, err := c.Do("WATCH", key); err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
members, err := redis.Strings(c.Do("ZRANGE", key, 0, 0))
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
if len(members) != 1 {
|
||||
return "", redis.ErrNil
|
||||
}
|
||||
|
||||
c.Send("MULTI")
|
||||
c.Send("ZREM", key, members[0])
|
||||
queued, err := c.Do("EXEC")
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
if queued != nil {
|
||||
result = members[0]
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
return result, nil
|
||||
}
|
||||
|
||||
// zpopScript pops a value from a ZSET.
|
||||
var zpopScript = redis.NewScript(1, `
|
||||
local r = redis.call('ZRANGE', KEYS[1], 0, 0)
|
||||
if r ~= nil then
|
||||
r = r[1]
|
||||
redis.call('ZREM', KEYS[1], r)
|
||||
end
|
||||
return r
|
||||
`)
|
||||
|
||||
// This example implements ZPOP as described at
|
||||
// http://redis.io/topics/transactions using WATCH/MULTI/EXEC and scripting.
|
||||
func Example_zpop() {
|
||||
c, err := dial()
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
return
|
||||
}
|
||||
defer c.Close()
|
||||
|
||||
// Add test data using a pipeline.
|
||||
|
||||
for i, member := range []string{"red", "blue", "green"} {
|
||||
c.Send("ZADD", "zset", i, member)
|
||||
}
|
||||
if _, err := c.Do(""); err != nil {
|
||||
fmt.Println(err)
|
||||
return
|
||||
}
|
||||
|
||||
// Pop using WATCH/MULTI/EXEC
|
||||
|
||||
v, err := zpop(c, "zset")
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
return
|
||||
}
|
||||
fmt.Println(v)
|
||||
|
||||
// Pop using a script.
|
||||
|
||||
v, err = redis.String(zpopScript.Do(c, "zset"))
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
return
|
||||
}
|
||||
fmt.Println(v)
|
||||
|
||||
// Output:
|
||||
// red
|
||||
// blue
|
||||
}
|
152
vendor/github.com/garyburd/redigo/redisx/connmux.go
generated
vendored
Normal file
152
vendor/github.com/garyburd/redigo/redisx/connmux.go
generated
vendored
Normal file
@@ -0,0 +1,152 @@
|
||||
// Copyright 2014 Gary Burd
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License"): you may
|
||||
// not use this file except in compliance with the License. You may obtain
|
||||
// a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
// License for the specific language governing permissions and limitations
|
||||
// under the License.
|
||||
|
||||
package redisx
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"sync"
|
||||
|
||||
"github.com/garyburd/redigo/internal"
|
||||
"github.com/garyburd/redigo/redis"
|
||||
)
|
||||
|
||||
// ConnMux multiplexes one or more connections to a single underlying
|
||||
// connection. The ConnMux connections do not support concurrency, commands
|
||||
// that associate server side state with the connection or commands that put
|
||||
// the connection in a special mode.
|
||||
type ConnMux struct {
|
||||
c redis.Conn
|
||||
|
||||
sendMu sync.Mutex
|
||||
sendID uint
|
||||
|
||||
recvMu sync.Mutex
|
||||
recvID uint
|
||||
recvWait map[uint]chan struct{}
|
||||
}
|
||||
|
||||
func NewConnMux(c redis.Conn) *ConnMux {
|
||||
return &ConnMux{c: c, recvWait: make(map[uint]chan struct{})}
|
||||
}
|
||||
|
||||
// Get gets a connection. The application must close the returned connection.
|
||||
func (p *ConnMux) Get() redis.Conn {
|
||||
c := &muxConn{p: p}
|
||||
c.ids = c.buf[:0]
|
||||
return c
|
||||
}
|
||||
|
||||
// Close closes the underlying connection.
|
||||
func (p *ConnMux) Close() error {
|
||||
return p.c.Close()
|
||||
}
|
||||
|
||||
type muxConn struct {
|
||||
p *ConnMux
|
||||
ids []uint
|
||||
buf [8]uint
|
||||
}
|
||||
|
||||
func (c *muxConn) send(flush bool, cmd string, args ...interface{}) error {
|
||||
if internal.LookupCommandInfo(cmd).Set != 0 {
|
||||
return errors.New("command not supported by mux pool")
|
||||
}
|
||||
p := c.p
|
||||
p.sendMu.Lock()
|
||||
id := p.sendID
|
||||
c.ids = append(c.ids, id)
|
||||
p.sendID++
|
||||
err := p.c.Send(cmd, args...)
|
||||
if flush {
|
||||
err = p.c.Flush()
|
||||
}
|
||||
p.sendMu.Unlock()
|
||||
return err
|
||||
}
|
||||
|
||||
func (c *muxConn) Send(cmd string, args ...interface{}) error {
|
||||
return c.send(false, cmd, args...)
|
||||
}
|
||||
|
||||
func (c *muxConn) Flush() error {
|
||||
p := c.p
|
||||
p.sendMu.Lock()
|
||||
err := p.c.Flush()
|
||||
p.sendMu.Unlock()
|
||||
return err
|
||||
}
|
||||
|
||||
func (c *muxConn) Receive() (interface{}, error) {
|
||||
if len(c.ids) == 0 {
|
||||
return nil, errors.New("mux pool underflow")
|
||||
}
|
||||
|
||||
id := c.ids[0]
|
||||
c.ids = c.ids[1:]
|
||||
if len(c.ids) == 0 {
|
||||
c.ids = c.buf[:0]
|
||||
}
|
||||
|
||||
p := c.p
|
||||
p.recvMu.Lock()
|
||||
if p.recvID != id {
|
||||
ch := make(chan struct{})
|
||||
p.recvWait[id] = ch
|
||||
p.recvMu.Unlock()
|
||||
<-ch
|
||||
p.recvMu.Lock()
|
||||
if p.recvID != id {
|
||||
panic("out of sync")
|
||||
}
|
||||
}
|
||||
|
||||
v, err := p.c.Receive()
|
||||
|
||||
id++
|
||||
p.recvID = id
|
||||
ch, ok := p.recvWait[id]
|
||||
if ok {
|
||||
delete(p.recvWait, id)
|
||||
}
|
||||
p.recvMu.Unlock()
|
||||
if ok {
|
||||
ch <- struct{}{}
|
||||
}
|
||||
|
||||
return v, err
|
||||
}
|
||||
|
||||
func (c *muxConn) Close() error {
|
||||
var err error
|
||||
if len(c.ids) == 0 {
|
||||
return nil
|
||||
}
|
||||
c.Flush()
|
||||
for _ = range c.ids {
|
||||
_, err = c.Receive()
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
func (c *muxConn) Do(cmd string, args ...interface{}) (interface{}, error) {
|
||||
if err := c.send(true, cmd, args...); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return c.Receive()
|
||||
}
|
||||
|
||||
func (c *muxConn) Err() error {
|
||||
return c.p.c.Err()
|
||||
}
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user