You've already forked openaccounting-server
mirror of
https://github.com/openaccounting/oa-server.git
synced 2025-12-09 00:50:59 +13:00
initial commit
This commit is contained in:
236
vendor/github.com/ant0ine/go-json-rest/rest/access_log_apache.go
generated
vendored
Normal file
236
vendor/github.com/ant0ine/go-json-rest/rest/access_log_apache.go
generated
vendored
Normal file
@@ -0,0 +1,236 @@
|
||||
package rest
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"log"
|
||||
"net"
|
||||
"os"
|
||||
"strings"
|
||||
"text/template"
|
||||
"time"
|
||||
)
|
||||
|
||||
// TODO Future improvements:
|
||||
// * support %{strftime}t ?
|
||||
// * support %{<header>}o to print headers
|
||||
|
||||
// AccessLogFormat defines the format of the access log record.
|
||||
// This implementation is a subset of Apache mod_log_config.
|
||||
// (See http://httpd.apache.org/docs/2.0/mod/mod_log_config.html)
|
||||
//
|
||||
// %b content length in bytes, - if 0
|
||||
// %B content length in bytes
|
||||
// %D response elapsed time in microseconds
|
||||
// %h remote address
|
||||
// %H server protocol
|
||||
// %l identd logname, not supported, -
|
||||
// %m http method
|
||||
// %P process id
|
||||
// %q query string
|
||||
// %r first line of the request
|
||||
// %s status code
|
||||
// %S status code preceeded by a terminal color
|
||||
// %t time of the request
|
||||
// %T response elapsed time in seconds, 3 decimals
|
||||
// %u remote user, - if missing
|
||||
// %{User-Agent}i user agent, - if missing
|
||||
// %{Referer}i referer, - is missing
|
||||
//
|
||||
// Some predefined formats are provided as contants.
|
||||
type AccessLogFormat string
|
||||
|
||||
const (
|
||||
// CommonLogFormat is the Common Log Format (CLF).
|
||||
CommonLogFormat = "%h %l %u %t \"%r\" %s %b"
|
||||
|
||||
// CombinedLogFormat is the NCSA extended/combined log format.
|
||||
CombinedLogFormat = "%h %l %u %t \"%r\" %s %b \"%{Referer}i\" \"%{User-Agent}i\""
|
||||
|
||||
// DefaultLogFormat is the default format, colored output and response time, convenient for development.
|
||||
DefaultLogFormat = "%t %S\033[0m \033[36;1m%Dμs\033[0m \"%r\" \033[1;30m%u \"%{User-Agent}i\"\033[0m"
|
||||
)
|
||||
|
||||
// AccessLogApacheMiddleware produces the access log following a format inspired by Apache
|
||||
// mod_log_config. It depends on TimerMiddleware and RecorderMiddleware that should be in the wrapped
|
||||
// middlewares. It also uses request.Env["REMOTE_USER"].(string) set by the auth middlewares.
|
||||
type AccessLogApacheMiddleware struct {
|
||||
|
||||
// Logger points to the logger object used by this middleware, it defaults to
|
||||
// log.New(os.Stderr, "", 0).
|
||||
Logger *log.Logger
|
||||
|
||||
// Format defines the format of the access log record. See AccessLogFormat for the details.
|
||||
// It defaults to DefaultLogFormat.
|
||||
Format AccessLogFormat
|
||||
|
||||
textTemplate *template.Template
|
||||
}
|
||||
|
||||
// MiddlewareFunc makes AccessLogApacheMiddleware implement the Middleware interface.
|
||||
func (mw *AccessLogApacheMiddleware) MiddlewareFunc(h HandlerFunc) HandlerFunc {
|
||||
|
||||
// set the default Logger
|
||||
if mw.Logger == nil {
|
||||
mw.Logger = log.New(os.Stderr, "", 0)
|
||||
}
|
||||
|
||||
// set default format
|
||||
if mw.Format == "" {
|
||||
mw.Format = DefaultLogFormat
|
||||
}
|
||||
|
||||
mw.convertFormat()
|
||||
|
||||
return func(w ResponseWriter, r *Request) {
|
||||
|
||||
// call the handler
|
||||
h(w, r)
|
||||
|
||||
util := &accessLogUtil{w, r}
|
||||
|
||||
mw.Logger.Print(mw.executeTextTemplate(util))
|
||||
}
|
||||
}
|
||||
|
||||
var apacheAdapter = strings.NewReplacer(
|
||||
"%b", "{{.BytesWritten | dashIf0}}",
|
||||
"%B", "{{.BytesWritten}}",
|
||||
"%D", "{{.ResponseTime | microseconds}}",
|
||||
"%h", "{{.ApacheRemoteAddr}}",
|
||||
"%H", "{{.R.Proto}}",
|
||||
"%l", "-",
|
||||
"%m", "{{.R.Method}}",
|
||||
"%P", "{{.Pid}}",
|
||||
"%q", "{{.ApacheQueryString}}",
|
||||
"%r", "{{.R.Method}} {{.R.URL.RequestURI}} {{.R.Proto}}",
|
||||
"%s", "{{.StatusCode}}",
|
||||
"%S", "\033[{{.StatusCode | statusCodeColor}}m{{.StatusCode}}",
|
||||
"%t", "{{if .StartTime}}{{.StartTime.Format \"02/Jan/2006:15:04:05 -0700\"}}{{end}}",
|
||||
"%T", "{{if .ResponseTime}}{{.ResponseTime.Seconds | printf \"%.3f\"}}{{end}}",
|
||||
"%u", "{{.RemoteUser | dashIfEmptyStr}}",
|
||||
"%{User-Agent}i", "{{.R.UserAgent | dashIfEmptyStr}}",
|
||||
"%{Referer}i", "{{.R.Referer | dashIfEmptyStr}}",
|
||||
)
|
||||
|
||||
// Convert the Apache access log format into a text/template
|
||||
func (mw *AccessLogApacheMiddleware) convertFormat() {
|
||||
|
||||
tmplText := apacheAdapter.Replace(string(mw.Format))
|
||||
|
||||
funcMap := template.FuncMap{
|
||||
"dashIfEmptyStr": func(value string) string {
|
||||
if value == "" {
|
||||
return "-"
|
||||
}
|
||||
return value
|
||||
},
|
||||
"dashIf0": func(value int64) string {
|
||||
if value == 0 {
|
||||
return "-"
|
||||
}
|
||||
return fmt.Sprintf("%d", value)
|
||||
},
|
||||
"microseconds": func(dur *time.Duration) string {
|
||||
if dur != nil {
|
||||
return fmt.Sprintf("%d", dur.Nanoseconds()/1000)
|
||||
}
|
||||
return ""
|
||||
},
|
||||
"statusCodeColor": func(statusCode int) string {
|
||||
if statusCode >= 400 && statusCode < 500 {
|
||||
return "1;33"
|
||||
} else if statusCode >= 500 {
|
||||
return "0;31"
|
||||
}
|
||||
return "0;32"
|
||||
},
|
||||
}
|
||||
|
||||
var err error
|
||||
mw.textTemplate, err = template.New("accessLog").Funcs(funcMap).Parse(tmplText)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
|
||||
// Execute the text template with the data derived from the request, and return a string.
|
||||
func (mw *AccessLogApacheMiddleware) executeTextTemplate(util *accessLogUtil) string {
|
||||
buf := bytes.NewBufferString("")
|
||||
err := mw.textTemplate.Execute(buf, util)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return buf.String()
|
||||
}
|
||||
|
||||
// accessLogUtil provides a collection of utility functions that devrive data from the Request object.
|
||||
// This object is used to provide data to the Apache Style template and the the JSON log record.
|
||||
type accessLogUtil struct {
|
||||
W ResponseWriter
|
||||
R *Request
|
||||
}
|
||||
|
||||
// As stored by the auth middlewares.
|
||||
func (u *accessLogUtil) RemoteUser() string {
|
||||
if u.R.Env["REMOTE_USER"] != nil {
|
||||
return u.R.Env["REMOTE_USER"].(string)
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
// If qs exists then return it with a leadin "?", apache log style.
|
||||
func (u *accessLogUtil) ApacheQueryString() string {
|
||||
if u.R.URL.RawQuery != "" {
|
||||
return "?" + u.R.URL.RawQuery
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
// When the request entered the timer middleware.
|
||||
func (u *accessLogUtil) StartTime() *time.Time {
|
||||
if u.R.Env["START_TIME"] != nil {
|
||||
return u.R.Env["START_TIME"].(*time.Time)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// If remoteAddr is set then return is without the port number, apache log style.
|
||||
func (u *accessLogUtil) ApacheRemoteAddr() string {
|
||||
remoteAddr := u.R.RemoteAddr
|
||||
if remoteAddr != "" {
|
||||
if ip, _, err := net.SplitHostPort(remoteAddr); err == nil {
|
||||
return ip
|
||||
}
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
// As recorded by the recorder middleware.
|
||||
func (u *accessLogUtil) StatusCode() int {
|
||||
if u.R.Env["STATUS_CODE"] != nil {
|
||||
return u.R.Env["STATUS_CODE"].(int)
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
// As mesured by the timer middleware.
|
||||
func (u *accessLogUtil) ResponseTime() *time.Duration {
|
||||
if u.R.Env["ELAPSED_TIME"] != nil {
|
||||
return u.R.Env["ELAPSED_TIME"].(*time.Duration)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Process id.
|
||||
func (u *accessLogUtil) Pid() int {
|
||||
return os.Getpid()
|
||||
}
|
||||
|
||||
// As recorded by the recorder middleware.
|
||||
func (u *accessLogUtil) BytesWritten() int64 {
|
||||
if u.R.Env["BYTES_WRITTEN"] != nil {
|
||||
return u.R.Env["BYTES_WRITTEN"].(int64)
|
||||
}
|
||||
return 0
|
||||
}
|
||||
88
vendor/github.com/ant0ine/go-json-rest/rest/access_log_json.go
generated
vendored
Normal file
88
vendor/github.com/ant0ine/go-json-rest/rest/access_log_json.go
generated
vendored
Normal file
@@ -0,0 +1,88 @@
|
||||
package rest
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"log"
|
||||
"os"
|
||||
"time"
|
||||
)
|
||||
|
||||
// AccessLogJsonMiddleware produces the access log with records written as JSON. This middleware
|
||||
// depends on TimerMiddleware and RecorderMiddleware that must be in the wrapped middlewares. It
|
||||
// also uses request.Env["REMOTE_USER"].(string) set by the auth middlewares.
|
||||
type AccessLogJsonMiddleware struct {
|
||||
|
||||
// Logger points to the logger object used by this middleware, it defaults to
|
||||
// log.New(os.Stderr, "", 0).
|
||||
Logger *log.Logger
|
||||
}
|
||||
|
||||
// MiddlewareFunc makes AccessLogJsonMiddleware implement the Middleware interface.
|
||||
func (mw *AccessLogJsonMiddleware) MiddlewareFunc(h HandlerFunc) HandlerFunc {
|
||||
|
||||
// set the default Logger
|
||||
if mw.Logger == nil {
|
||||
mw.Logger = log.New(os.Stderr, "", 0)
|
||||
}
|
||||
|
||||
return func(w ResponseWriter, r *Request) {
|
||||
|
||||
// call the handler
|
||||
h(w, r)
|
||||
|
||||
mw.Logger.Printf("%s", makeAccessLogJsonRecord(r).asJson())
|
||||
}
|
||||
}
|
||||
|
||||
// AccessLogJsonRecord is the data structure used by AccessLogJsonMiddleware to create the JSON
|
||||
// records. (Public for documentation only, no public method uses it)
|
||||
type AccessLogJsonRecord struct {
|
||||
Timestamp *time.Time
|
||||
StatusCode int
|
||||
ResponseTime *time.Duration
|
||||
HttpMethod string
|
||||
RequestURI string
|
||||
RemoteUser string
|
||||
UserAgent string
|
||||
}
|
||||
|
||||
func makeAccessLogJsonRecord(r *Request) *AccessLogJsonRecord {
|
||||
|
||||
var timestamp *time.Time
|
||||
if r.Env["START_TIME"] != nil {
|
||||
timestamp = r.Env["START_TIME"].(*time.Time)
|
||||
}
|
||||
|
||||
var statusCode int
|
||||
if r.Env["STATUS_CODE"] != nil {
|
||||
statusCode = r.Env["STATUS_CODE"].(int)
|
||||
}
|
||||
|
||||
var responseTime *time.Duration
|
||||
if r.Env["ELAPSED_TIME"] != nil {
|
||||
responseTime = r.Env["ELAPSED_TIME"].(*time.Duration)
|
||||
}
|
||||
|
||||
var remoteUser string
|
||||
if r.Env["REMOTE_USER"] != nil {
|
||||
remoteUser = r.Env["REMOTE_USER"].(string)
|
||||
}
|
||||
|
||||
return &AccessLogJsonRecord{
|
||||
Timestamp: timestamp,
|
||||
StatusCode: statusCode,
|
||||
ResponseTime: responseTime,
|
||||
HttpMethod: r.Method,
|
||||
RequestURI: r.URL.RequestURI(),
|
||||
RemoteUser: remoteUser,
|
||||
UserAgent: r.UserAgent(),
|
||||
}
|
||||
}
|
||||
|
||||
func (r *AccessLogJsonRecord) asJson() []byte {
|
||||
b, err := json.Marshal(r)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return b
|
||||
}
|
||||
83
vendor/github.com/ant0ine/go-json-rest/rest/api.go
generated
vendored
Normal file
83
vendor/github.com/ant0ine/go-json-rest/rest/api.go
generated
vendored
Normal file
@@ -0,0 +1,83 @@
|
||||
package rest
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
)
|
||||
|
||||
// Api defines a stack of Middlewares and an App.
|
||||
type Api struct {
|
||||
stack []Middleware
|
||||
app App
|
||||
}
|
||||
|
||||
// NewApi makes a new Api object. The Middleware stack is empty, and the App is nil.
|
||||
func NewApi() *Api {
|
||||
return &Api{
|
||||
stack: []Middleware{},
|
||||
app: nil,
|
||||
}
|
||||
}
|
||||
|
||||
// Use pushes one or multiple middlewares to the stack for middlewares
|
||||
// maintained in the Api object.
|
||||
func (api *Api) Use(middlewares ...Middleware) {
|
||||
api.stack = append(api.stack, middlewares...)
|
||||
}
|
||||
|
||||
// SetApp sets the App in the Api object.
|
||||
func (api *Api) SetApp(app App) {
|
||||
api.app = app
|
||||
}
|
||||
|
||||
// MakeHandler wraps all the Middlewares of the stack and the App together, and returns an
|
||||
// http.Handler ready to be used. If the Middleware stack is empty the App is used directly. If the
|
||||
// App is nil, a HandlerFunc that does nothing is used instead.
|
||||
func (api *Api) MakeHandler() http.Handler {
|
||||
var appFunc HandlerFunc
|
||||
if api.app != nil {
|
||||
appFunc = api.app.AppFunc()
|
||||
} else {
|
||||
appFunc = func(w ResponseWriter, r *Request) {}
|
||||
}
|
||||
return http.HandlerFunc(
|
||||
adapterFunc(
|
||||
WrapMiddlewares(api.stack, appFunc),
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
// Defines a stack of middlewares convenient for development. Among other things:
|
||||
// console friendly logging, JSON indentation, error stack strace in the response.
|
||||
var DefaultDevStack = []Middleware{
|
||||
&AccessLogApacheMiddleware{},
|
||||
&TimerMiddleware{},
|
||||
&RecorderMiddleware{},
|
||||
&PoweredByMiddleware{},
|
||||
&RecoverMiddleware{
|
||||
EnableResponseStackTrace: true,
|
||||
},
|
||||
&JsonIndentMiddleware{},
|
||||
&ContentTypeCheckerMiddleware{},
|
||||
}
|
||||
|
||||
// Defines a stack of middlewares convenient for production. Among other things:
|
||||
// Apache CombinedLogFormat logging, gzip compression.
|
||||
var DefaultProdStack = []Middleware{
|
||||
&AccessLogApacheMiddleware{
|
||||
Format: CombinedLogFormat,
|
||||
},
|
||||
&TimerMiddleware{},
|
||||
&RecorderMiddleware{},
|
||||
&PoweredByMiddleware{},
|
||||
&RecoverMiddleware{},
|
||||
&GzipMiddleware{},
|
||||
&ContentTypeCheckerMiddleware{},
|
||||
}
|
||||
|
||||
// Defines a stack of middlewares that should be common to most of the middleware stacks.
|
||||
var DefaultCommonStack = []Middleware{
|
||||
&TimerMiddleware{},
|
||||
&RecorderMiddleware{},
|
||||
&PoweredByMiddleware{},
|
||||
&RecoverMiddleware{},
|
||||
}
|
||||
100
vendor/github.com/ant0ine/go-json-rest/rest/auth_basic.go
generated
vendored
Normal file
100
vendor/github.com/ant0ine/go-json-rest/rest/auth_basic.go
generated
vendored
Normal file
@@ -0,0 +1,100 @@
|
||||
package rest
|
||||
|
||||
import (
|
||||
"encoding/base64"
|
||||
"errors"
|
||||
"log"
|
||||
"net/http"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// AuthBasicMiddleware provides a simple AuthBasic implementation. On failure, a 401 HTTP response
|
||||
//is returned. On success, the wrapped middleware is called, and the userId is made available as
|
||||
// request.Env["REMOTE_USER"].(string)
|
||||
type AuthBasicMiddleware struct {
|
||||
|
||||
// Realm name to display to the user. Required.
|
||||
Realm string
|
||||
|
||||
// Callback function that should perform the authentication of the user based on userId and
|
||||
// password. Must return true on success, false on failure. Required.
|
||||
Authenticator func(userId string, password string) bool
|
||||
|
||||
// Callback function that should perform the authorization of the authenticated user. Called
|
||||
// only after an authentication success. Must return true on success, false on failure.
|
||||
// Optional, default to success.
|
||||
Authorizator func(userId string, request *Request) bool
|
||||
}
|
||||
|
||||
// MiddlewareFunc makes AuthBasicMiddleware implement the Middleware interface.
|
||||
func (mw *AuthBasicMiddleware) MiddlewareFunc(handler HandlerFunc) HandlerFunc {
|
||||
|
||||
if mw.Realm == "" {
|
||||
log.Fatal("Realm is required")
|
||||
}
|
||||
|
||||
if mw.Authenticator == nil {
|
||||
log.Fatal("Authenticator is required")
|
||||
}
|
||||
|
||||
if mw.Authorizator == nil {
|
||||
mw.Authorizator = func(userId string, request *Request) bool {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
return func(writer ResponseWriter, request *Request) {
|
||||
|
||||
authHeader := request.Header.Get("Authorization")
|
||||
if authHeader == "" {
|
||||
mw.unauthorized(writer)
|
||||
return
|
||||
}
|
||||
|
||||
providedUserId, providedPassword, err := mw.decodeBasicAuthHeader(authHeader)
|
||||
|
||||
if err != nil {
|
||||
Error(writer, "Invalid authentication", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
if !mw.Authenticator(providedUserId, providedPassword) {
|
||||
mw.unauthorized(writer)
|
||||
return
|
||||
}
|
||||
|
||||
if !mw.Authorizator(providedUserId, request) {
|
||||
mw.unauthorized(writer)
|
||||
return
|
||||
}
|
||||
|
||||
request.Env["REMOTE_USER"] = providedUserId
|
||||
|
||||
handler(writer, request)
|
||||
}
|
||||
}
|
||||
|
||||
func (mw *AuthBasicMiddleware) unauthorized(writer ResponseWriter) {
|
||||
writer.Header().Set("WWW-Authenticate", "Basic realm="+mw.Realm)
|
||||
Error(writer, "Not Authorized", http.StatusUnauthorized)
|
||||
}
|
||||
|
||||
func (mw *AuthBasicMiddleware) decodeBasicAuthHeader(header string) (user string, password string, err error) {
|
||||
|
||||
parts := strings.SplitN(header, " ", 2)
|
||||
if !(len(parts) == 2 && parts[0] == "Basic") {
|
||||
return "", "", errors.New("Invalid authentication")
|
||||
}
|
||||
|
||||
decoded, err := base64.StdEncoding.DecodeString(parts[1])
|
||||
if err != nil {
|
||||
return "", "", errors.New("Invalid base64")
|
||||
}
|
||||
|
||||
creds := strings.SplitN(string(decoded), ":", 2)
|
||||
if len(creds) != 2 {
|
||||
return "", "", errors.New("Invalid authentication")
|
||||
}
|
||||
|
||||
return creds[0], creds[1], nil
|
||||
}
|
||||
40
vendor/github.com/ant0ine/go-json-rest/rest/content_type_checker.go
generated
vendored
Normal file
40
vendor/github.com/ant0ine/go-json-rest/rest/content_type_checker.go
generated
vendored
Normal file
@@ -0,0 +1,40 @@
|
||||
package rest
|
||||
|
||||
import (
|
||||
"mime"
|
||||
"net/http"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// ContentTypeCheckerMiddleware verifies the request Content-Type header and returns a
|
||||
// StatusUnsupportedMediaType (415) HTTP error response if it's incorrect. The expected
|
||||
// Content-Type is 'application/json' if the content is non-null. Note: If a charset parameter
|
||||
// exists, it MUST be UTF-8.
|
||||
type ContentTypeCheckerMiddleware struct{}
|
||||
|
||||
// MiddlewareFunc makes ContentTypeCheckerMiddleware implement the Middleware interface.
|
||||
func (mw *ContentTypeCheckerMiddleware) MiddlewareFunc(handler HandlerFunc) HandlerFunc {
|
||||
|
||||
return func(w ResponseWriter, r *Request) {
|
||||
|
||||
mediatype, params, _ := mime.ParseMediaType(r.Header.Get("Content-Type"))
|
||||
charset, ok := params["charset"]
|
||||
if !ok {
|
||||
charset = "UTF-8"
|
||||
}
|
||||
|
||||
// per net/http doc, means that the length is known and non-null
|
||||
if r.ContentLength > 0 &&
|
||||
!(mediatype == "application/json" && strings.ToUpper(charset) == "UTF-8") {
|
||||
|
||||
Error(w,
|
||||
"Bad Content-Type or charset, expected 'application/json'",
|
||||
http.StatusUnsupportedMediaType,
|
||||
)
|
||||
return
|
||||
}
|
||||
|
||||
// call the wrapped handler
|
||||
handler(w, r)
|
||||
}
|
||||
}
|
||||
135
vendor/github.com/ant0ine/go-json-rest/rest/cors.go
generated
vendored
Normal file
135
vendor/github.com/ant0ine/go-json-rest/rest/cors.go
generated
vendored
Normal file
@@ -0,0 +1,135 @@
|
||||
package rest
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// Possible improvements:
|
||||
// If AllowedMethods["*"] then Access-Control-Allow-Methods is set to the requested methods
|
||||
// If AllowedHeaderss["*"] then Access-Control-Allow-Headers is set to the requested headers
|
||||
// Put some presets in AllowedHeaders
|
||||
// Put some presets in AccessControlExposeHeaders
|
||||
|
||||
// CorsMiddleware provides a configurable CORS implementation.
|
||||
type CorsMiddleware struct {
|
||||
allowedMethods map[string]bool
|
||||
allowedMethodsCsv string
|
||||
allowedHeaders map[string]bool
|
||||
allowedHeadersCsv string
|
||||
|
||||
// Reject non CORS requests if true. See CorsInfo.IsCors.
|
||||
RejectNonCorsRequests bool
|
||||
|
||||
// Function excecuted for every CORS requests to validate the Origin. (Required)
|
||||
// Must return true if valid, false if invalid.
|
||||
// For instance: simple equality, regexp, DB lookup, ...
|
||||
OriginValidator func(origin string, request *Request) bool
|
||||
|
||||
// List of allowed HTTP methods. Note that the comparison will be made in
|
||||
// uppercase to avoid common mistakes. And that the
|
||||
// Access-Control-Allow-Methods response header also uses uppercase.
|
||||
// (see CorsInfo.AccessControlRequestMethod)
|
||||
AllowedMethods []string
|
||||
|
||||
// List of allowed HTTP Headers. Note that the comparison will be made with
|
||||
// noarmalized names (http.CanonicalHeaderKey). And that the response header
|
||||
// also uses normalized names.
|
||||
// (see CorsInfo.AccessControlRequestHeaders)
|
||||
AllowedHeaders []string
|
||||
|
||||
// List of headers used to set the Access-Control-Expose-Headers header.
|
||||
AccessControlExposeHeaders []string
|
||||
|
||||
// User to se the Access-Control-Allow-Credentials response header.
|
||||
AccessControlAllowCredentials bool
|
||||
|
||||
// Used to set the Access-Control-Max-Age response header, in seconds.
|
||||
AccessControlMaxAge int
|
||||
}
|
||||
|
||||
// MiddlewareFunc makes CorsMiddleware implement the Middleware interface.
|
||||
func (mw *CorsMiddleware) MiddlewareFunc(handler HandlerFunc) HandlerFunc {
|
||||
|
||||
// precompute as much as possible at init time
|
||||
|
||||
mw.allowedMethods = map[string]bool{}
|
||||
normedMethods := []string{}
|
||||
for _, allowedMethod := range mw.AllowedMethods {
|
||||
normed := strings.ToUpper(allowedMethod)
|
||||
mw.allowedMethods[normed] = true
|
||||
normedMethods = append(normedMethods, normed)
|
||||
}
|
||||
mw.allowedMethodsCsv = strings.Join(normedMethods, ",")
|
||||
|
||||
mw.allowedHeaders = map[string]bool{}
|
||||
normedHeaders := []string{}
|
||||
for _, allowedHeader := range mw.AllowedHeaders {
|
||||
normed := http.CanonicalHeaderKey(allowedHeader)
|
||||
mw.allowedHeaders[normed] = true
|
||||
normedHeaders = append(normedHeaders, normed)
|
||||
}
|
||||
mw.allowedHeadersCsv = strings.Join(normedHeaders, ",")
|
||||
|
||||
return func(writer ResponseWriter, request *Request) {
|
||||
|
||||
corsInfo := request.GetCorsInfo()
|
||||
|
||||
// non CORS requests
|
||||
if !corsInfo.IsCors {
|
||||
if mw.RejectNonCorsRequests {
|
||||
Error(writer, "Non CORS request", http.StatusForbidden)
|
||||
return
|
||||
}
|
||||
// continue, execute the wrapped middleware
|
||||
handler(writer, request)
|
||||
return
|
||||
}
|
||||
|
||||
// Validate the Origin
|
||||
if mw.OriginValidator(corsInfo.Origin, request) == false {
|
||||
Error(writer, "Invalid Origin", http.StatusForbidden)
|
||||
return
|
||||
}
|
||||
|
||||
if corsInfo.IsPreflight {
|
||||
|
||||
// check the request methods
|
||||
if mw.allowedMethods[corsInfo.AccessControlRequestMethod] == false {
|
||||
Error(writer, "Invalid Preflight Request", http.StatusForbidden)
|
||||
return
|
||||
}
|
||||
|
||||
// check the request headers
|
||||
for _, requestedHeader := range corsInfo.AccessControlRequestHeaders {
|
||||
if mw.allowedHeaders[requestedHeader] == false {
|
||||
Error(writer, "Invalid Preflight Request", http.StatusForbidden)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
writer.Header().Set("Access-Control-Allow-Methods", mw.allowedMethodsCsv)
|
||||
writer.Header().Set("Access-Control-Allow-Headers", mw.allowedHeadersCsv)
|
||||
writer.Header().Set("Access-Control-Allow-Origin", corsInfo.Origin)
|
||||
if mw.AccessControlAllowCredentials == true {
|
||||
writer.Header().Set("Access-Control-Allow-Credentials", "true")
|
||||
}
|
||||
writer.Header().Set("Access-Control-Max-Age", strconv.Itoa(mw.AccessControlMaxAge))
|
||||
writer.WriteHeader(http.StatusOK)
|
||||
return
|
||||
}
|
||||
|
||||
// Non-preflight requests
|
||||
for _, exposed := range mw.AccessControlExposeHeaders {
|
||||
writer.Header().Add("Access-Control-Expose-Headers", exposed)
|
||||
}
|
||||
writer.Header().Set("Access-Control-Allow-Origin", corsInfo.Origin)
|
||||
if mw.AccessControlAllowCredentials == true {
|
||||
writer.Header().Set("Access-Control-Allow-Credentials", "true")
|
||||
}
|
||||
// continure, execute the wrapped middleware
|
||||
handler(writer, request)
|
||||
return
|
||||
}
|
||||
}
|
||||
47
vendor/github.com/ant0ine/go-json-rest/rest/doc.go
generated
vendored
Normal file
47
vendor/github.com/ant0ine/go-json-rest/rest/doc.go
generated
vendored
Normal file
@@ -0,0 +1,47 @@
|
||||
// A quick and easy way to setup a RESTful JSON API
|
||||
//
|
||||
// http://ant0ine.github.io/go-json-rest/
|
||||
//
|
||||
// Go-Json-Rest is a thin layer on top of net/http that helps building RESTful JSON APIs easily.
|
||||
// It provides fast and scalable request routing using a Trie based implementation, helpers to deal
|
||||
// with JSON requests and responses, and middlewares for functionalities like CORS, Auth, Gzip,
|
||||
// Status, ...
|
||||
//
|
||||
// Example:
|
||||
//
|
||||
// package main
|
||||
//
|
||||
// import (
|
||||
// "github.com/ant0ine/go-json-rest/rest"
|
||||
// "log"
|
||||
// "net/http"
|
||||
// )
|
||||
//
|
||||
// type User struct {
|
||||
// Id string
|
||||
// Name string
|
||||
// }
|
||||
//
|
||||
// func GetUser(w rest.ResponseWriter, req *rest.Request) {
|
||||
// user := User{
|
||||
// Id: req.PathParam("id"),
|
||||
// Name: "Antoine",
|
||||
// }
|
||||
// w.WriteJson(&user)
|
||||
// }
|
||||
//
|
||||
// func main() {
|
||||
// api := rest.NewApi()
|
||||
// api.Use(rest.DefaultDevStack...)
|
||||
// router, err := rest.MakeRouter(
|
||||
// rest.Get("/users/:id", GetUser),
|
||||
// )
|
||||
// if err != nil {
|
||||
// log.Fatal(err)
|
||||
// }
|
||||
// api.SetApp(router)
|
||||
// log.Fatal(http.ListenAndServe(":8080", api.MakeHandler()))
|
||||
// }
|
||||
//
|
||||
//
|
||||
package rest
|
||||
132
vendor/github.com/ant0ine/go-json-rest/rest/gzip.go
generated
vendored
Normal file
132
vendor/github.com/ant0ine/go-json-rest/rest/gzip.go
generated
vendored
Normal file
@@ -0,0 +1,132 @@
|
||||
package rest
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"compress/gzip"
|
||||
"net"
|
||||
"net/http"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// GzipMiddleware is responsible for compressing the payload with gzip and setting the proper
|
||||
// headers when supported by the client. It must be wrapped by TimerMiddleware for the
|
||||
// compression time to be captured. And It must be wrapped by RecorderMiddleware for the
|
||||
// compressed BYTES_WRITTEN to be captured.
|
||||
type GzipMiddleware struct{}
|
||||
|
||||
// MiddlewareFunc makes GzipMiddleware implement the Middleware interface.
|
||||
func (mw *GzipMiddleware) MiddlewareFunc(h HandlerFunc) HandlerFunc {
|
||||
return func(w ResponseWriter, r *Request) {
|
||||
// gzip support enabled
|
||||
canGzip := strings.Contains(r.Header.Get("Accept-Encoding"), "gzip")
|
||||
// client accepts gzip ?
|
||||
writer := &gzipResponseWriter{w, false, canGzip, nil}
|
||||
defer func() {
|
||||
// need to close gzip writer
|
||||
if writer.gzipWriter != nil {
|
||||
writer.gzipWriter.Close()
|
||||
}
|
||||
}()
|
||||
// call the handler with the wrapped writer
|
||||
h(writer, r)
|
||||
}
|
||||
}
|
||||
|
||||
// Private responseWriter intantiated by the gzip middleware.
|
||||
// It encodes the payload with gzip and set the proper headers.
|
||||
// It implements the following interfaces:
|
||||
// ResponseWriter
|
||||
// http.ResponseWriter
|
||||
// http.Flusher
|
||||
// http.CloseNotifier
|
||||
// http.Hijacker
|
||||
type gzipResponseWriter struct {
|
||||
ResponseWriter
|
||||
wroteHeader bool
|
||||
canGzip bool
|
||||
gzipWriter *gzip.Writer
|
||||
}
|
||||
|
||||
// Set the right headers for gzip encoded responses.
|
||||
func (w *gzipResponseWriter) WriteHeader(code int) {
|
||||
|
||||
// Always set the Vary header, even if this particular request
|
||||
// is not gzipped.
|
||||
w.Header().Add("Vary", "Accept-Encoding")
|
||||
|
||||
if w.canGzip {
|
||||
w.Header().Set("Content-Encoding", "gzip")
|
||||
}
|
||||
|
||||
w.ResponseWriter.WriteHeader(code)
|
||||
w.wroteHeader = true
|
||||
}
|
||||
|
||||
// Make sure the local Write is called.
|
||||
func (w *gzipResponseWriter) WriteJson(v interface{}) error {
|
||||
b, err := w.EncodeJson(v)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
_, err = w.Write(b)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Make sure the local WriteHeader is called, and call the parent Flush.
|
||||
// Provided in order to implement the http.Flusher interface.
|
||||
func (w *gzipResponseWriter) Flush() {
|
||||
if !w.wroteHeader {
|
||||
w.WriteHeader(http.StatusOK)
|
||||
}
|
||||
flusher := w.ResponseWriter.(http.Flusher)
|
||||
flusher.Flush()
|
||||
}
|
||||
|
||||
// Call the parent CloseNotify.
|
||||
// Provided in order to implement the http.CloseNotifier interface.
|
||||
func (w *gzipResponseWriter) CloseNotify() <-chan bool {
|
||||
notifier := w.ResponseWriter.(http.CloseNotifier)
|
||||
return notifier.CloseNotify()
|
||||
}
|
||||
|
||||
// Provided in order to implement the http.Hijacker interface.
|
||||
func (w *gzipResponseWriter) Hijack() (net.Conn, *bufio.ReadWriter, error) {
|
||||
hijacker := w.ResponseWriter.(http.Hijacker)
|
||||
return hijacker.Hijack()
|
||||
}
|
||||
|
||||
// Make sure the local WriteHeader is called, and encode the payload if necessary.
|
||||
// Provided in order to implement the http.ResponseWriter interface.
|
||||
func (w *gzipResponseWriter) Write(b []byte) (int, error) {
|
||||
|
||||
if !w.wroteHeader {
|
||||
w.WriteHeader(http.StatusOK)
|
||||
}
|
||||
|
||||
writer := w.ResponseWriter.(http.ResponseWriter)
|
||||
|
||||
if w.canGzip {
|
||||
// Write can be called multiple times for a given response.
|
||||
// (see the streaming example:
|
||||
// https://github.com/ant0ine/go-json-rest-examples/tree/master/streaming)
|
||||
// The gzipWriter is instantiated only once, and flushed after
|
||||
// each write.
|
||||
if w.gzipWriter == nil {
|
||||
w.gzipWriter = gzip.NewWriter(writer)
|
||||
}
|
||||
count, errW := w.gzipWriter.Write(b)
|
||||
errF := w.gzipWriter.Flush()
|
||||
if errW != nil {
|
||||
return count, errW
|
||||
}
|
||||
if errF != nil {
|
||||
return count, errF
|
||||
}
|
||||
return count, nil
|
||||
}
|
||||
|
||||
return writer.Write(b)
|
||||
}
|
||||
53
vendor/github.com/ant0ine/go-json-rest/rest/if.go
generated
vendored
Normal file
53
vendor/github.com/ant0ine/go-json-rest/rest/if.go
generated
vendored
Normal file
@@ -0,0 +1,53 @@
|
||||
package rest
|
||||
|
||||
import (
|
||||
"log"
|
||||
)
|
||||
|
||||
// IfMiddleware evaluates at runtime a condition based on the current request, and decides to
|
||||
// execute one of the other Middleware based on this boolean.
|
||||
type IfMiddleware struct {
|
||||
|
||||
// Runtime condition that decides of the execution of IfTrue of IfFalse.
|
||||
Condition func(r *Request) bool
|
||||
|
||||
// Middleware to run when the condition is true. Note that the middleware is initialized
|
||||
// weather if will be used or not. (Optional, pass-through if not set)
|
||||
IfTrue Middleware
|
||||
|
||||
// Middleware to run when the condition is false. Note that the middleware is initialized
|
||||
// weather if will be used or not. (Optional, pass-through if not set)
|
||||
IfFalse Middleware
|
||||
}
|
||||
|
||||
// MiddlewareFunc makes TimerMiddleware implement the Middleware interface.
|
||||
func (mw *IfMiddleware) MiddlewareFunc(h HandlerFunc) HandlerFunc {
|
||||
|
||||
if mw.Condition == nil {
|
||||
log.Fatal("IfMiddleware Condition is required")
|
||||
}
|
||||
|
||||
var ifTrueHandler HandlerFunc
|
||||
if mw.IfTrue != nil {
|
||||
ifTrueHandler = mw.IfTrue.MiddlewareFunc(h)
|
||||
} else {
|
||||
ifTrueHandler = h
|
||||
}
|
||||
|
||||
var ifFalseHandler HandlerFunc
|
||||
if mw.IfFalse != nil {
|
||||
ifFalseHandler = mw.IfFalse.MiddlewareFunc(h)
|
||||
} else {
|
||||
ifFalseHandler = h
|
||||
}
|
||||
|
||||
return func(w ResponseWriter, r *Request) {
|
||||
|
||||
if mw.Condition(r) {
|
||||
ifTrueHandler(w, r)
|
||||
} else {
|
||||
ifFalseHandler(w, r)
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
113
vendor/github.com/ant0ine/go-json-rest/rest/json_indent.go
generated
vendored
Normal file
113
vendor/github.com/ant0ine/go-json-rest/rest/json_indent.go
generated
vendored
Normal file
@@ -0,0 +1,113 @@
|
||||
package rest
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"encoding/json"
|
||||
"net"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
// JsonIndentMiddleware provides JSON encoding with indentation.
|
||||
// It could be convenient to use it during development.
|
||||
// It works by "subclassing" the responseWriter provided by the wrapping middleware,
|
||||
// replacing the writer.EncodeJson and writer.WriteJson implementations,
|
||||
// and making the parent implementations ignored.
|
||||
type JsonIndentMiddleware struct {
|
||||
|
||||
// prefix string, as in json.MarshalIndent
|
||||
Prefix string
|
||||
|
||||
// indentation string, as in json.MarshalIndent
|
||||
Indent string
|
||||
}
|
||||
|
||||
// MiddlewareFunc makes JsonIndentMiddleware implement the Middleware interface.
|
||||
func (mw *JsonIndentMiddleware) MiddlewareFunc(handler HandlerFunc) HandlerFunc {
|
||||
|
||||
if mw.Indent == "" {
|
||||
mw.Indent = " "
|
||||
}
|
||||
|
||||
return func(w ResponseWriter, r *Request) {
|
||||
|
||||
writer := &jsonIndentResponseWriter{w, false, mw.Prefix, mw.Indent}
|
||||
// call the wrapped handler
|
||||
handler(writer, r)
|
||||
}
|
||||
}
|
||||
|
||||
// Private responseWriter intantiated by the middleware.
|
||||
// It implements the following interfaces:
|
||||
// ResponseWriter
|
||||
// http.ResponseWriter
|
||||
// http.Flusher
|
||||
// http.CloseNotifier
|
||||
// http.Hijacker
|
||||
type jsonIndentResponseWriter struct {
|
||||
ResponseWriter
|
||||
wroteHeader bool
|
||||
prefix string
|
||||
indent string
|
||||
}
|
||||
|
||||
// Replace the parent EncodeJson to provide indentation.
|
||||
func (w *jsonIndentResponseWriter) EncodeJson(v interface{}) ([]byte, error) {
|
||||
b, err := json.MarshalIndent(v, w.prefix, w.indent)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return b, nil
|
||||
}
|
||||
|
||||
// Make sure the local EncodeJson and local Write are called.
|
||||
// Does not call the parent WriteJson.
|
||||
func (w *jsonIndentResponseWriter) WriteJson(v interface{}) error {
|
||||
b, err := w.EncodeJson(v)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
_, err = w.Write(b)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Call the parent WriteHeader.
|
||||
func (w *jsonIndentResponseWriter) WriteHeader(code int) {
|
||||
w.ResponseWriter.WriteHeader(code)
|
||||
w.wroteHeader = true
|
||||
}
|
||||
|
||||
// Make sure the local WriteHeader is called, and call the parent Flush.
|
||||
// Provided in order to implement the http.Flusher interface.
|
||||
func (w *jsonIndentResponseWriter) Flush() {
|
||||
if !w.wroteHeader {
|
||||
w.WriteHeader(http.StatusOK)
|
||||
}
|
||||
flusher := w.ResponseWriter.(http.Flusher)
|
||||
flusher.Flush()
|
||||
}
|
||||
|
||||
// Call the parent CloseNotify.
|
||||
// Provided in order to implement the http.CloseNotifier interface.
|
||||
func (w *jsonIndentResponseWriter) CloseNotify() <-chan bool {
|
||||
notifier := w.ResponseWriter.(http.CloseNotifier)
|
||||
return notifier.CloseNotify()
|
||||
}
|
||||
|
||||
// Provided in order to implement the http.Hijacker interface.
|
||||
func (w *jsonIndentResponseWriter) Hijack() (net.Conn, *bufio.ReadWriter, error) {
|
||||
hijacker := w.ResponseWriter.(http.Hijacker)
|
||||
return hijacker.Hijack()
|
||||
}
|
||||
|
||||
// Make sure the local WriteHeader is called, and call the parent Write.
|
||||
// Provided in order to implement the http.ResponseWriter interface.
|
||||
func (w *jsonIndentResponseWriter) Write(b []byte) (int, error) {
|
||||
if !w.wroteHeader {
|
||||
w.WriteHeader(http.StatusOK)
|
||||
}
|
||||
writer := w.ResponseWriter.(http.ResponseWriter)
|
||||
return writer.Write(b)
|
||||
}
|
||||
116
vendor/github.com/ant0ine/go-json-rest/rest/jsonp.go
generated
vendored
Normal file
116
vendor/github.com/ant0ine/go-json-rest/rest/jsonp.go
generated
vendored
Normal file
@@ -0,0 +1,116 @@
|
||||
package rest
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"net"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
// JsonpMiddleware provides JSONP responses on demand, based on the presence
|
||||
// of a query string argument specifying the callback name.
|
||||
type JsonpMiddleware struct {
|
||||
|
||||
// Name of the query string parameter used to specify the
|
||||
// the name of the JS callback used for the padding.
|
||||
// Defaults to "callback".
|
||||
CallbackNameKey string
|
||||
}
|
||||
|
||||
// MiddlewareFunc returns a HandlerFunc that implements the middleware.
|
||||
func (mw *JsonpMiddleware) MiddlewareFunc(h HandlerFunc) HandlerFunc {
|
||||
|
||||
if mw.CallbackNameKey == "" {
|
||||
mw.CallbackNameKey = "callback"
|
||||
}
|
||||
|
||||
return func(w ResponseWriter, r *Request) {
|
||||
|
||||
callbackName := r.URL.Query().Get(mw.CallbackNameKey)
|
||||
// TODO validate the callbackName ?
|
||||
|
||||
if callbackName != "" {
|
||||
// the client request JSONP, instantiate JsonpMiddleware.
|
||||
writer := &jsonpResponseWriter{w, false, callbackName}
|
||||
// call the handler with the wrapped writer
|
||||
h(writer, r)
|
||||
} else {
|
||||
// do nothing special
|
||||
h(w, r)
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
// Private responseWriter intantiated by the JSONP middleware.
|
||||
// It adds the padding to the payload and set the proper headers.
|
||||
// It implements the following interfaces:
|
||||
// ResponseWriter
|
||||
// http.ResponseWriter
|
||||
// http.Flusher
|
||||
// http.CloseNotifier
|
||||
// http.Hijacker
|
||||
type jsonpResponseWriter struct {
|
||||
ResponseWriter
|
||||
wroteHeader bool
|
||||
callbackName string
|
||||
}
|
||||
|
||||
// Overwrite the Content-Type to be text/javascript
|
||||
func (w *jsonpResponseWriter) WriteHeader(code int) {
|
||||
|
||||
w.Header().Set("Content-Type", "text/javascript")
|
||||
|
||||
w.ResponseWriter.WriteHeader(code)
|
||||
w.wroteHeader = true
|
||||
}
|
||||
|
||||
// Make sure the local Write is called.
|
||||
func (w *jsonpResponseWriter) WriteJson(v interface{}) error {
|
||||
b, err := w.EncodeJson(v)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
// JSONP security fix (http://miki.it/blog/2014/7/8/abusing-jsonp-with-rosetta-flash/)
|
||||
w.Header().Set("Content-Disposition", "filename=f.txt")
|
||||
w.Header().Set("X-Content-Type-Options", "nosniff")
|
||||
w.Write([]byte("/**/" + w.callbackName + "("))
|
||||
w.Write(b)
|
||||
w.Write([]byte(")"))
|
||||
return nil
|
||||
}
|
||||
|
||||
// Make sure the local WriteHeader is called, and call the parent Flush.
|
||||
// Provided in order to implement the http.Flusher interface.
|
||||
func (w *jsonpResponseWriter) Flush() {
|
||||
if !w.wroteHeader {
|
||||
w.WriteHeader(http.StatusOK)
|
||||
}
|
||||
flusher := w.ResponseWriter.(http.Flusher)
|
||||
flusher.Flush()
|
||||
}
|
||||
|
||||
// Call the parent CloseNotify.
|
||||
// Provided in order to implement the http.CloseNotifier interface.
|
||||
func (w *jsonpResponseWriter) CloseNotify() <-chan bool {
|
||||
notifier := w.ResponseWriter.(http.CloseNotifier)
|
||||
return notifier.CloseNotify()
|
||||
}
|
||||
|
||||
// Provided in order to implement the http.Hijacker interface.
|
||||
func (w *jsonpResponseWriter) Hijack() (net.Conn, *bufio.ReadWriter, error) {
|
||||
hijacker := w.ResponseWriter.(http.Hijacker)
|
||||
return hijacker.Hijack()
|
||||
}
|
||||
|
||||
// Make sure the local WriteHeader is called.
|
||||
// Provided in order to implement the http.ResponseWriter interface.
|
||||
func (w *jsonpResponseWriter) Write(b []byte) (int, error) {
|
||||
|
||||
if !w.wroteHeader {
|
||||
w.WriteHeader(http.StatusOK)
|
||||
}
|
||||
|
||||
writer := w.ResponseWriter.(http.ResponseWriter)
|
||||
|
||||
return writer.Write(b)
|
||||
}
|
||||
72
vendor/github.com/ant0ine/go-json-rest/rest/middleware.go
generated
vendored
Normal file
72
vendor/github.com/ant0ine/go-json-rest/rest/middleware.go
generated
vendored
Normal file
@@ -0,0 +1,72 @@
|
||||
package rest
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
)
|
||||
|
||||
// HandlerFunc defines the handler function. It is the go-json-rest equivalent of http.HandlerFunc.
|
||||
type HandlerFunc func(ResponseWriter, *Request)
|
||||
|
||||
// App defines the interface that an object should implement to be used as an app in this framework
|
||||
// stack. The App is the top element of the stack, the other elements being middlewares.
|
||||
type App interface {
|
||||
AppFunc() HandlerFunc
|
||||
}
|
||||
|
||||
// AppSimple is an adapter type that makes it easy to write an App with a simple function.
|
||||
// eg: rest.NewApi(rest.AppSimple(func(w rest.ResponseWriter, r *rest.Request) { ... }))
|
||||
type AppSimple HandlerFunc
|
||||
|
||||
// AppFunc makes AppSimple implement the App interface.
|
||||
func (as AppSimple) AppFunc() HandlerFunc {
|
||||
return HandlerFunc(as)
|
||||
}
|
||||
|
||||
// Middleware defines the interface that objects must implement in order to wrap a HandlerFunc and
|
||||
// be used in the middleware stack.
|
||||
type Middleware interface {
|
||||
MiddlewareFunc(handler HandlerFunc) HandlerFunc
|
||||
}
|
||||
|
||||
// MiddlewareSimple is an adapter type that makes it easy to write a Middleware with a simple
|
||||
// function. eg: api.Use(rest.MiddlewareSimple(func(h HandlerFunc) Handlerfunc { ... }))
|
||||
type MiddlewareSimple func(handler HandlerFunc) HandlerFunc
|
||||
|
||||
// MiddlewareFunc makes MiddlewareSimple implement the Middleware interface.
|
||||
func (ms MiddlewareSimple) MiddlewareFunc(handler HandlerFunc) HandlerFunc {
|
||||
return ms(handler)
|
||||
}
|
||||
|
||||
// WrapMiddlewares calls the MiddlewareFunc methods in the reverse order and returns an HandlerFunc
|
||||
// ready to be executed. This can be used to wrap a set of middlewares, post routing, on a per Route
|
||||
// basis.
|
||||
func WrapMiddlewares(middlewares []Middleware, handler HandlerFunc) HandlerFunc {
|
||||
wrapped := handler
|
||||
for i := len(middlewares) - 1; i >= 0; i-- {
|
||||
wrapped = middlewares[i].MiddlewareFunc(wrapped)
|
||||
}
|
||||
return wrapped
|
||||
}
|
||||
|
||||
// Handle the transition between net/http and go-json-rest objects.
|
||||
// It intanciates the rest.Request and rest.ResponseWriter, ...
|
||||
func adapterFunc(handler HandlerFunc) http.HandlerFunc {
|
||||
|
||||
return func(origWriter http.ResponseWriter, origRequest *http.Request) {
|
||||
|
||||
// instantiate the rest objects
|
||||
request := &Request{
|
||||
origRequest,
|
||||
nil,
|
||||
map[string]interface{}{},
|
||||
}
|
||||
|
||||
writer := &responseWriter{
|
||||
origWriter,
|
||||
false,
|
||||
}
|
||||
|
||||
// call the wrapped handler
|
||||
handler(writer, request)
|
||||
}
|
||||
}
|
||||
29
vendor/github.com/ant0ine/go-json-rest/rest/powered_by.go
generated
vendored
Normal file
29
vendor/github.com/ant0ine/go-json-rest/rest/powered_by.go
generated
vendored
Normal file
@@ -0,0 +1,29 @@
|
||||
package rest
|
||||
|
||||
const xPoweredByDefault = "go-json-rest"
|
||||
|
||||
// PoweredByMiddleware adds the "X-Powered-By" header to the HTTP response.
|
||||
type PoweredByMiddleware struct {
|
||||
|
||||
// If specified, used as the value for the "X-Powered-By" response header.
|
||||
// Defaults to "go-json-rest".
|
||||
XPoweredBy string
|
||||
}
|
||||
|
||||
// MiddlewareFunc makes PoweredByMiddleware implement the Middleware interface.
|
||||
func (mw *PoweredByMiddleware) MiddlewareFunc(h HandlerFunc) HandlerFunc {
|
||||
|
||||
poweredBy := xPoweredByDefault
|
||||
if mw.XPoweredBy != "" {
|
||||
poweredBy = mw.XPoweredBy
|
||||
}
|
||||
|
||||
return func(w ResponseWriter, r *Request) {
|
||||
|
||||
w.Header().Add("X-Powered-By", poweredBy)
|
||||
|
||||
// call the handler
|
||||
h(w, r)
|
||||
|
||||
}
|
||||
}
|
||||
100
vendor/github.com/ant0ine/go-json-rest/rest/recorder.go
generated
vendored
Normal file
100
vendor/github.com/ant0ine/go-json-rest/rest/recorder.go
generated
vendored
Normal file
@@ -0,0 +1,100 @@
|
||||
package rest
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"net"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
// RecorderMiddleware keeps a record of the HTTP status code of the response,
|
||||
// and the number of bytes written.
|
||||
// The result is available to the wrapping handlers as request.Env["STATUS_CODE"].(int),
|
||||
// and as request.Env["BYTES_WRITTEN"].(int64)
|
||||
type RecorderMiddleware struct{}
|
||||
|
||||
// MiddlewareFunc makes RecorderMiddleware implement the Middleware interface.
|
||||
func (mw *RecorderMiddleware) MiddlewareFunc(h HandlerFunc) HandlerFunc {
|
||||
return func(w ResponseWriter, r *Request) {
|
||||
|
||||
writer := &recorderResponseWriter{w, 0, false, 0}
|
||||
|
||||
// call the handler
|
||||
h(writer, r)
|
||||
|
||||
r.Env["STATUS_CODE"] = writer.statusCode
|
||||
r.Env["BYTES_WRITTEN"] = writer.bytesWritten
|
||||
}
|
||||
}
|
||||
|
||||
// Private responseWriter intantiated by the recorder middleware.
|
||||
// It keeps a record of the HTTP status code of the response.
|
||||
// It implements the following interfaces:
|
||||
// ResponseWriter
|
||||
// http.ResponseWriter
|
||||
// http.Flusher
|
||||
// http.CloseNotifier
|
||||
// http.Hijacker
|
||||
type recorderResponseWriter struct {
|
||||
ResponseWriter
|
||||
statusCode int
|
||||
wroteHeader bool
|
||||
bytesWritten int64
|
||||
}
|
||||
|
||||
// Record the status code.
|
||||
func (w *recorderResponseWriter) WriteHeader(code int) {
|
||||
w.ResponseWriter.WriteHeader(code)
|
||||
if w.wroteHeader {
|
||||
return
|
||||
}
|
||||
w.statusCode = code
|
||||
w.wroteHeader = true
|
||||
}
|
||||
|
||||
// Make sure the local Write is called.
|
||||
func (w *recorderResponseWriter) WriteJson(v interface{}) error {
|
||||
b, err := w.EncodeJson(v)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
_, err = w.Write(b)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Make sure the local WriteHeader is called, and call the parent Flush.
|
||||
// Provided in order to implement the http.Flusher interface.
|
||||
func (w *recorderResponseWriter) Flush() {
|
||||
if !w.wroteHeader {
|
||||
w.WriteHeader(http.StatusOK)
|
||||
}
|
||||
flusher := w.ResponseWriter.(http.Flusher)
|
||||
flusher.Flush()
|
||||
}
|
||||
|
||||
// Call the parent CloseNotify.
|
||||
// Provided in order to implement the http.CloseNotifier interface.
|
||||
func (w *recorderResponseWriter) CloseNotify() <-chan bool {
|
||||
notifier := w.ResponseWriter.(http.CloseNotifier)
|
||||
return notifier.CloseNotify()
|
||||
}
|
||||
|
||||
// Provided in order to implement the http.Hijacker interface.
|
||||
func (w *recorderResponseWriter) Hijack() (net.Conn, *bufio.ReadWriter, error) {
|
||||
hijacker := w.ResponseWriter.(http.Hijacker)
|
||||
return hijacker.Hijack()
|
||||
}
|
||||
|
||||
// Make sure the local WriteHeader is called, and call the parent Write.
|
||||
// Provided in order to implement the http.ResponseWriter interface.
|
||||
func (w *recorderResponseWriter) Write(b []byte) (int, error) {
|
||||
if !w.wroteHeader {
|
||||
w.WriteHeader(http.StatusOK)
|
||||
}
|
||||
writer := w.ResponseWriter.(http.ResponseWriter)
|
||||
written, err := writer.Write(b)
|
||||
w.bytesWritten += int64(written)
|
||||
return written, err
|
||||
}
|
||||
74
vendor/github.com/ant0ine/go-json-rest/rest/recover.go
generated
vendored
Normal file
74
vendor/github.com/ant0ine/go-json-rest/rest/recover.go
generated
vendored
Normal file
@@ -0,0 +1,74 @@
|
||||
package rest
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"log"
|
||||
"net/http"
|
||||
"os"
|
||||
"runtime/debug"
|
||||
)
|
||||
|
||||
// RecoverMiddleware catches the panic errors that occur in the wrapped HandleFunc,
|
||||
// and convert them to 500 responses.
|
||||
type RecoverMiddleware struct {
|
||||
|
||||
// Custom logger used for logging the panic errors,
|
||||
// optional, defaults to log.New(os.Stderr, "", 0)
|
||||
Logger *log.Logger
|
||||
|
||||
// If true, the log records will be printed as JSON. Convenient for log parsing.
|
||||
EnableLogAsJson bool
|
||||
|
||||
// If true, when a "panic" happens, the error string and the stack trace will be
|
||||
// printed in the 500 response body.
|
||||
EnableResponseStackTrace bool
|
||||
}
|
||||
|
||||
// MiddlewareFunc makes RecoverMiddleware implement the Middleware interface.
|
||||
func (mw *RecoverMiddleware) MiddlewareFunc(h HandlerFunc) HandlerFunc {
|
||||
|
||||
// set the default Logger
|
||||
if mw.Logger == nil {
|
||||
mw.Logger = log.New(os.Stderr, "", 0)
|
||||
}
|
||||
|
||||
return func(w ResponseWriter, r *Request) {
|
||||
|
||||
// catch user code's panic, and convert to http response
|
||||
defer func() {
|
||||
if reco := recover(); reco != nil {
|
||||
trace := debug.Stack()
|
||||
|
||||
// log the trace
|
||||
message := fmt.Sprintf("%s\n%s", reco, trace)
|
||||
mw.logError(message)
|
||||
|
||||
// write error response
|
||||
if mw.EnableResponseStackTrace {
|
||||
Error(w, message, http.StatusInternalServerError)
|
||||
} else {
|
||||
Error(w, "Internal Server Error", http.StatusInternalServerError)
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
// call the handler
|
||||
h(w, r)
|
||||
}
|
||||
}
|
||||
|
||||
func (mw *RecoverMiddleware) logError(message string) {
|
||||
if mw.EnableLogAsJson {
|
||||
record := map[string]string{
|
||||
"error": message,
|
||||
}
|
||||
b, err := json.Marshal(&record)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
mw.Logger.Printf("%s", b)
|
||||
} else {
|
||||
mw.Logger.Print(message)
|
||||
}
|
||||
}
|
||||
148
vendor/github.com/ant0ine/go-json-rest/rest/request.go
generated
vendored
Normal file
148
vendor/github.com/ant0ine/go-json-rest/rest/request.go
generated
vendored
Normal file
@@ -0,0 +1,148 @@
|
||||
package rest
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"strings"
|
||||
)
|
||||
|
||||
var (
|
||||
// ErrJsonPayloadEmpty is returned when the JSON payload is empty.
|
||||
ErrJsonPayloadEmpty = errors.New("JSON payload is empty")
|
||||
)
|
||||
|
||||
// Request inherits from http.Request, and provides additional methods.
|
||||
type Request struct {
|
||||
*http.Request
|
||||
|
||||
// Map of parameters that have been matched in the URL Path.
|
||||
PathParams map[string]string
|
||||
|
||||
// Environment used by middlewares to communicate.
|
||||
Env map[string]interface{}
|
||||
}
|
||||
|
||||
// PathParam provides a convenient access to the PathParams map.
|
||||
func (r *Request) PathParam(name string) string {
|
||||
return r.PathParams[name]
|
||||
}
|
||||
|
||||
// DecodeJsonPayload reads the request body and decodes the JSON using json.Unmarshal.
|
||||
func (r *Request) DecodeJsonPayload(v interface{}) error {
|
||||
content, err := ioutil.ReadAll(r.Body)
|
||||
r.Body.Close()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if len(content) == 0 {
|
||||
return ErrJsonPayloadEmpty
|
||||
}
|
||||
err = json.Unmarshal(content, v)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// BaseUrl returns a new URL object with the Host and Scheme taken from the request.
|
||||
// (without the trailing slash in the host)
|
||||
func (r *Request) BaseUrl() *url.URL {
|
||||
scheme := r.URL.Scheme
|
||||
if scheme == "" {
|
||||
scheme = "http"
|
||||
}
|
||||
|
||||
// HTTP sometimes gives the default scheme as HTTP even when used with TLS
|
||||
// Check if TLS is not nil and given back https scheme
|
||||
if scheme == "http" && r.TLS != nil {
|
||||
scheme = "https"
|
||||
}
|
||||
|
||||
host := r.Host
|
||||
if len(host) > 0 && host[len(host)-1] == '/' {
|
||||
host = host[:len(host)-1]
|
||||
}
|
||||
|
||||
return &url.URL{
|
||||
Scheme: scheme,
|
||||
Host: host,
|
||||
}
|
||||
}
|
||||
|
||||
// UrlFor returns the URL object from UriBase with the Path set to path, and the query
|
||||
// string built with queryParams.
|
||||
func (r *Request) UrlFor(path string, queryParams map[string][]string) *url.URL {
|
||||
baseUrl := r.BaseUrl()
|
||||
baseUrl.Path = path
|
||||
if queryParams != nil {
|
||||
query := url.Values{}
|
||||
for k, v := range queryParams {
|
||||
for _, vv := range v {
|
||||
query.Add(k, vv)
|
||||
}
|
||||
}
|
||||
baseUrl.RawQuery = query.Encode()
|
||||
}
|
||||
return baseUrl
|
||||
}
|
||||
|
||||
// CorsInfo contains the CORS request info derived from a rest.Request.
|
||||
type CorsInfo struct {
|
||||
IsCors bool
|
||||
IsPreflight bool
|
||||
Origin string
|
||||
OriginUrl *url.URL
|
||||
|
||||
// The header value is converted to uppercase to avoid common mistakes.
|
||||
AccessControlRequestMethod string
|
||||
|
||||
// The header values are normalized with http.CanonicalHeaderKey.
|
||||
AccessControlRequestHeaders []string
|
||||
}
|
||||
|
||||
// GetCorsInfo derives CorsInfo from Request.
|
||||
func (r *Request) GetCorsInfo() *CorsInfo {
|
||||
|
||||
origin := r.Header.Get("Origin")
|
||||
|
||||
var originUrl *url.URL
|
||||
var isCors bool
|
||||
|
||||
if origin == "" {
|
||||
isCors = false
|
||||
} else if origin == "null" {
|
||||
isCors = true
|
||||
} else {
|
||||
var err error
|
||||
originUrl, err = url.ParseRequestURI(origin)
|
||||
isCors = err == nil && r.Host != originUrl.Host
|
||||
}
|
||||
|
||||
reqMethod := r.Header.Get("Access-Control-Request-Method")
|
||||
|
||||
reqHeaders := []string{}
|
||||
rawReqHeaders := r.Header[http.CanonicalHeaderKey("Access-Control-Request-Headers")]
|
||||
for _, rawReqHeader := range rawReqHeaders {
|
||||
if len(rawReqHeader) == 0 {
|
||||
continue
|
||||
}
|
||||
// net/http does not handle comma delimited headers for us
|
||||
for _, reqHeader := range strings.Split(rawReqHeader, ",") {
|
||||
reqHeaders = append(reqHeaders, http.CanonicalHeaderKey(strings.TrimSpace(reqHeader)))
|
||||
}
|
||||
}
|
||||
|
||||
isPreflight := isCors && r.Method == "OPTIONS" && reqMethod != ""
|
||||
|
||||
return &CorsInfo{
|
||||
IsCors: isCors,
|
||||
IsPreflight: isPreflight,
|
||||
Origin: origin,
|
||||
OriginUrl: originUrl,
|
||||
AccessControlRequestMethod: strings.ToUpper(reqMethod),
|
||||
AccessControlRequestHeaders: reqHeaders,
|
||||
}
|
||||
}
|
||||
127
vendor/github.com/ant0ine/go-json-rest/rest/response.go
generated
vendored
Normal file
127
vendor/github.com/ant0ine/go-json-rest/rest/response.go
generated
vendored
Normal file
@@ -0,0 +1,127 @@
|
||||
package rest
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"encoding/json"
|
||||
"net"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
// A ResponseWriter interface dedicated to JSON HTTP response.
|
||||
// Note, the responseWriter object instantiated by the framework also implements many other interfaces
|
||||
// accessible by type assertion: http.ResponseWriter, http.Flusher, http.CloseNotifier, http.Hijacker.
|
||||
type ResponseWriter interface {
|
||||
|
||||
// Identical to the http.ResponseWriter interface
|
||||
Header() http.Header
|
||||
|
||||
// Use EncodeJson to generate the payload, write the headers with http.StatusOK if
|
||||
// they are not already written, then write the payload.
|
||||
// The Content-Type header is set to "application/json", unless already specified.
|
||||
WriteJson(v interface{}) error
|
||||
|
||||
// Encode the data structure to JSON, mainly used to wrap ResponseWriter in
|
||||
// middlewares.
|
||||
EncodeJson(v interface{}) ([]byte, error)
|
||||
|
||||
// Similar to the http.ResponseWriter interface, with additional JSON related
|
||||
// headers set.
|
||||
WriteHeader(int)
|
||||
}
|
||||
|
||||
// This allows to customize the field name used in the error response payload.
|
||||
// It defaults to "Error" for compatibility reason, but can be changed before starting the server.
|
||||
// eg: rest.ErrorFieldName = "errorMessage"
|
||||
var ErrorFieldName = "Error"
|
||||
|
||||
// Error produces an error response in JSON with the following structure, '{"Error":"My error message"}'
|
||||
// The standard plain text net/http Error helper can still be called like this:
|
||||
// http.Error(w, "error message", code)
|
||||
func Error(w ResponseWriter, error string, code int) {
|
||||
w.WriteHeader(code)
|
||||
err := w.WriteJson(map[string]string{ErrorFieldName: error})
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
|
||||
// NotFound produces a 404 response with the following JSON, '{"Error":"Resource not found"}'
|
||||
// The standard plain text net/http NotFound helper can still be called like this:
|
||||
// http.NotFound(w, r.Request)
|
||||
func NotFound(w ResponseWriter, r *Request) {
|
||||
Error(w, "Resource not found", http.StatusNotFound)
|
||||
}
|
||||
|
||||
// Private responseWriter intantiated by the resource handler.
|
||||
// It implements the following interfaces:
|
||||
// ResponseWriter
|
||||
// http.ResponseWriter
|
||||
// http.Flusher
|
||||
// http.CloseNotifier
|
||||
// http.Hijacker
|
||||
type responseWriter struct {
|
||||
http.ResponseWriter
|
||||
wroteHeader bool
|
||||
}
|
||||
|
||||
func (w *responseWriter) WriteHeader(code int) {
|
||||
if w.Header().Get("Content-Type") == "" {
|
||||
// Per spec, UTF-8 is the default, and the charset parameter should not
|
||||
// be necessary. But some clients (eg: Chrome) think otherwise.
|
||||
// Since json.Marshal produces UTF-8, setting the charset parameter is a
|
||||
// safe option.
|
||||
w.Header().Set("Content-Type", "application/json; charset=utf-8")
|
||||
}
|
||||
w.ResponseWriter.WriteHeader(code)
|
||||
w.wroteHeader = true
|
||||
}
|
||||
|
||||
func (w *responseWriter) EncodeJson(v interface{}) ([]byte, error) {
|
||||
b, err := json.Marshal(v)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return b, nil
|
||||
}
|
||||
|
||||
// Encode the object in JSON and call Write.
|
||||
func (w *responseWriter) WriteJson(v interface{}) error {
|
||||
b, err := w.EncodeJson(v)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
_, err = w.Write(b)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Provided in order to implement the http.ResponseWriter interface.
|
||||
func (w *responseWriter) Write(b []byte) (int, error) {
|
||||
if !w.wroteHeader {
|
||||
w.WriteHeader(http.StatusOK)
|
||||
}
|
||||
return w.ResponseWriter.Write(b)
|
||||
}
|
||||
|
||||
// Provided in order to implement the http.Flusher interface.
|
||||
func (w *responseWriter) Flush() {
|
||||
if !w.wroteHeader {
|
||||
w.WriteHeader(http.StatusOK)
|
||||
}
|
||||
flusher := w.ResponseWriter.(http.Flusher)
|
||||
flusher.Flush()
|
||||
}
|
||||
|
||||
// Provided in order to implement the http.CloseNotifier interface.
|
||||
func (w *responseWriter) CloseNotify() <-chan bool {
|
||||
notifier := w.ResponseWriter.(http.CloseNotifier)
|
||||
return notifier.CloseNotify()
|
||||
}
|
||||
|
||||
// Provided in order to implement the http.Hijacker interface.
|
||||
func (w *responseWriter) Hijack() (net.Conn, *bufio.ReadWriter, error) {
|
||||
hijacker := w.ResponseWriter.(http.Hijacker)
|
||||
return hijacker.Hijack()
|
||||
}
|
||||
107
vendor/github.com/ant0ine/go-json-rest/rest/route.go
generated
vendored
Normal file
107
vendor/github.com/ant0ine/go-json-rest/rest/route.go
generated
vendored
Normal file
@@ -0,0 +1,107 @@
|
||||
package rest
|
||||
|
||||
import (
|
||||
"strings"
|
||||
)
|
||||
|
||||
// Route defines a route as consumed by the router. It can be instantiated directly, or using one
|
||||
// of the shortcut methods: rest.Get, rest.Post, rest.Put, rest.Patch and rest.Delete.
|
||||
type Route struct {
|
||||
|
||||
// Any HTTP method. It will be used as uppercase to avoid common mistakes.
|
||||
HttpMethod string
|
||||
|
||||
// A string like "/resource/:id.json".
|
||||
// Placeholders supported are:
|
||||
// :paramName that matches any char to the first '/' or '.'
|
||||
// #paramName that matches any char to the first '/'
|
||||
// *paramName that matches everything to the end of the string
|
||||
// (placeholder names must be unique per PathExp)
|
||||
PathExp string
|
||||
|
||||
// Code that will be executed when this route is taken.
|
||||
Func HandlerFunc
|
||||
}
|
||||
|
||||
// MakePath generates the path corresponding to this Route and the provided path parameters.
|
||||
// This is used for reverse route resolution.
|
||||
func (route *Route) MakePath(pathParams map[string]string) string {
|
||||
path := route.PathExp
|
||||
for paramName, paramValue := range pathParams {
|
||||
paramPlaceholder := ":" + paramName
|
||||
relaxedPlaceholder := "#" + paramName
|
||||
splatPlaceholder := "*" + paramName
|
||||
r := strings.NewReplacer(paramPlaceholder, paramValue, splatPlaceholder, paramValue, relaxedPlaceholder, paramValue)
|
||||
path = r.Replace(path)
|
||||
}
|
||||
return path
|
||||
}
|
||||
|
||||
// Head is a shortcut method that instantiates a HEAD route. See the Route object the parameters definitions.
|
||||
// Equivalent to &Route{"HEAD", pathExp, handlerFunc}
|
||||
func Head(pathExp string, handlerFunc HandlerFunc) *Route {
|
||||
return &Route{
|
||||
HttpMethod: "HEAD",
|
||||
PathExp: pathExp,
|
||||
Func: handlerFunc,
|
||||
}
|
||||
}
|
||||
|
||||
// Get is a shortcut method that instantiates a GET route. See the Route object the parameters definitions.
|
||||
// Equivalent to &Route{"GET", pathExp, handlerFunc}
|
||||
func Get(pathExp string, handlerFunc HandlerFunc) *Route {
|
||||
return &Route{
|
||||
HttpMethod: "GET",
|
||||
PathExp: pathExp,
|
||||
Func: handlerFunc,
|
||||
}
|
||||
}
|
||||
|
||||
// Post is a shortcut method that instantiates a POST route. See the Route object the parameters definitions.
|
||||
// Equivalent to &Route{"POST", pathExp, handlerFunc}
|
||||
func Post(pathExp string, handlerFunc HandlerFunc) *Route {
|
||||
return &Route{
|
||||
HttpMethod: "POST",
|
||||
PathExp: pathExp,
|
||||
Func: handlerFunc,
|
||||
}
|
||||
}
|
||||
|
||||
// Put is a shortcut method that instantiates a PUT route. See the Route object the parameters definitions.
|
||||
// Equivalent to &Route{"PUT", pathExp, handlerFunc}
|
||||
func Put(pathExp string, handlerFunc HandlerFunc) *Route {
|
||||
return &Route{
|
||||
HttpMethod: "PUT",
|
||||
PathExp: pathExp,
|
||||
Func: handlerFunc,
|
||||
}
|
||||
}
|
||||
|
||||
// Patch is a shortcut method that instantiates a PATCH route. See the Route object the parameters definitions.
|
||||
// Equivalent to &Route{"PATCH", pathExp, handlerFunc}
|
||||
func Patch(pathExp string, handlerFunc HandlerFunc) *Route {
|
||||
return &Route{
|
||||
HttpMethod: "PATCH",
|
||||
PathExp: pathExp,
|
||||
Func: handlerFunc,
|
||||
}
|
||||
}
|
||||
|
||||
// Delete is a shortcut method that instantiates a DELETE route. Equivalent to &Route{"DELETE", pathExp, handlerFunc}
|
||||
func Delete(pathExp string, handlerFunc HandlerFunc) *Route {
|
||||
return &Route{
|
||||
HttpMethod: "DELETE",
|
||||
PathExp: pathExp,
|
||||
Func: handlerFunc,
|
||||
}
|
||||
}
|
||||
|
||||
// Options is a shortcut method that instantiates an OPTIONS route. See the Route object the parameters definitions.
|
||||
// Equivalent to &Route{"OPTIONS", pathExp, handlerFunc}
|
||||
func Options(pathExp string, handlerFunc HandlerFunc) *Route {
|
||||
return &Route{
|
||||
HttpMethod: "OPTIONS",
|
||||
PathExp: pathExp,
|
||||
Func: handlerFunc,
|
||||
}
|
||||
}
|
||||
194
vendor/github.com/ant0ine/go-json-rest/rest/router.go
generated
vendored
Normal file
194
vendor/github.com/ant0ine/go-json-rest/rest/router.go
generated
vendored
Normal file
@@ -0,0 +1,194 @@
|
||||
package rest
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"github.com/ant0ine/go-json-rest/rest/trie"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type router struct {
|
||||
Routes []*Route
|
||||
|
||||
disableTrieCompression bool
|
||||
index map[*Route]int
|
||||
trie *trie.Trie
|
||||
}
|
||||
|
||||
// MakeRouter returns the router app. Given a set of Routes, it dispatches the request to the
|
||||
// HandlerFunc of the first route that matches. The order of the Routes matters.
|
||||
func MakeRouter(routes ...*Route) (App, error) {
|
||||
r := &router{
|
||||
Routes: routes,
|
||||
}
|
||||
err := r.start()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return r, nil
|
||||
}
|
||||
|
||||
// Handle the REST routing and run the user code.
|
||||
func (rt *router) AppFunc() HandlerFunc {
|
||||
return func(writer ResponseWriter, request *Request) {
|
||||
|
||||
// find the route
|
||||
route, params, pathMatched := rt.findRouteFromURL(request.Method, request.URL)
|
||||
if route == nil {
|
||||
|
||||
if pathMatched {
|
||||
// no route found, but path was matched: 405 Method Not Allowed
|
||||
Error(writer, "Method not allowed", http.StatusMethodNotAllowed)
|
||||
return
|
||||
}
|
||||
|
||||
// no route found, the path was not matched: 404 Not Found
|
||||
NotFound(writer, request)
|
||||
return
|
||||
}
|
||||
|
||||
// a route was found, set the PathParams
|
||||
request.PathParams = params
|
||||
|
||||
// run the user code
|
||||
handler := route.Func
|
||||
handler(writer, request)
|
||||
}
|
||||
}
|
||||
|
||||
// This is run for each new request, perf is important.
|
||||
func escapedPath(urlObj *url.URL) string {
|
||||
// the escape method of url.URL should be public
|
||||
// that would avoid this split.
|
||||
parts := strings.SplitN(urlObj.RequestURI(), "?", 2)
|
||||
return parts[0]
|
||||
}
|
||||
|
||||
var preEscape = strings.NewReplacer("*", "__SPLAT_PLACEHOLDER__", "#", "__RELAXED_PLACEHOLDER__")
|
||||
|
||||
var postEscape = strings.NewReplacer("__SPLAT_PLACEHOLDER__", "*", "__RELAXED_PLACEHOLDER__", "#")
|
||||
|
||||
// This is run at init time only.
|
||||
func escapedPathExp(pathExp string) (string, error) {
|
||||
|
||||
// PathExp validation
|
||||
if pathExp == "" {
|
||||
return "", errors.New("empty PathExp")
|
||||
}
|
||||
if pathExp[0] != '/' {
|
||||
return "", errors.New("PathExp must start with /")
|
||||
}
|
||||
if strings.Contains(pathExp, "?") {
|
||||
return "", errors.New("PathExp must not contain the query string")
|
||||
}
|
||||
|
||||
// Get the right escaping
|
||||
// XXX a bit hacky
|
||||
|
||||
pathExp = preEscape.Replace(pathExp)
|
||||
|
||||
urlObj, err := url.Parse(pathExp)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
// get the same escaping as find requests
|
||||
pathExp = urlObj.RequestURI()
|
||||
|
||||
pathExp = postEscape.Replace(pathExp)
|
||||
|
||||
return pathExp, nil
|
||||
}
|
||||
|
||||
// This validates the Routes and prepares the Trie data structure.
|
||||
// It must be called once the Routes are defined and before trying to find Routes.
|
||||
// The order matters, if multiple Routes match, the first defined will be used.
|
||||
func (rt *router) start() error {
|
||||
|
||||
rt.trie = trie.New()
|
||||
rt.index = map[*Route]int{}
|
||||
|
||||
for i, route := range rt.Routes {
|
||||
|
||||
// work with the PathExp urlencoded.
|
||||
pathExp, err := escapedPathExp(route.PathExp)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// insert in the Trie
|
||||
err = rt.trie.AddRoute(
|
||||
strings.ToUpper(route.HttpMethod), // work with the HttpMethod in uppercase
|
||||
pathExp,
|
||||
route,
|
||||
)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// index
|
||||
rt.index[route] = i
|
||||
}
|
||||
|
||||
if rt.disableTrieCompression == false {
|
||||
rt.trie.Compress()
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// return the result that has the route defined the earliest
|
||||
func (rt *router) ofFirstDefinedRoute(matches []*trie.Match) *trie.Match {
|
||||
minIndex := -1
|
||||
var bestMatch *trie.Match
|
||||
|
||||
for _, result := range matches {
|
||||
route := result.Route.(*Route)
|
||||
routeIndex := rt.index[route]
|
||||
if minIndex == -1 || routeIndex < minIndex {
|
||||
minIndex = routeIndex
|
||||
bestMatch = result
|
||||
}
|
||||
}
|
||||
|
||||
return bestMatch
|
||||
}
|
||||
|
||||
// Return the first matching Route and the corresponding parameters for a given URL object.
|
||||
func (rt *router) findRouteFromURL(httpMethod string, urlObj *url.URL) (*Route, map[string]string, bool) {
|
||||
|
||||
// lookup the routes in the Trie
|
||||
matches, pathMatched := rt.trie.FindRoutesAndPathMatched(
|
||||
strings.ToUpper(httpMethod), // work with the httpMethod in uppercase
|
||||
escapedPath(urlObj), // work with the path urlencoded
|
||||
)
|
||||
|
||||
// short cuts
|
||||
if len(matches) == 0 {
|
||||
// no route found
|
||||
return nil, nil, pathMatched
|
||||
}
|
||||
|
||||
if len(matches) == 1 {
|
||||
// one route found
|
||||
return matches[0].Route.(*Route), matches[0].Params, pathMatched
|
||||
}
|
||||
|
||||
// multiple routes found, pick the first defined
|
||||
result := rt.ofFirstDefinedRoute(matches)
|
||||
return result.Route.(*Route), result.Params, pathMatched
|
||||
}
|
||||
|
||||
// Parse the url string (complete or just the path) and return the first matching Route and the corresponding parameters.
|
||||
func (rt *router) findRoute(httpMethod, urlStr string) (*Route, map[string]string, bool, error) {
|
||||
|
||||
// parse the url
|
||||
urlObj, err := url.Parse(urlStr)
|
||||
if err != nil {
|
||||
return nil, nil, false, err
|
||||
}
|
||||
|
||||
route, params, pathMatched := rt.findRouteFromURL(httpMethod, urlObj)
|
||||
return route, params, pathMatched, nil
|
||||
}
|
||||
129
vendor/github.com/ant0ine/go-json-rest/rest/status.go
generated
vendored
Normal file
129
vendor/github.com/ant0ine/go-json-rest/rest/status.go
generated
vendored
Normal file
@@ -0,0 +1,129 @@
|
||||
package rest
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
"os"
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
|
||||
// StatusMiddleware keeps track of various stats about the processed requests.
|
||||
// It depends on request.Env["STATUS_CODE"] and request.Env["ELAPSED_TIME"],
|
||||
// recorderMiddleware and timerMiddleware must be in the wrapped middlewares.
|
||||
type StatusMiddleware struct {
|
||||
lock sync.RWMutex
|
||||
start time.Time
|
||||
pid int
|
||||
responseCounts map[string]int
|
||||
totalResponseTime time.Time
|
||||
}
|
||||
|
||||
// MiddlewareFunc makes StatusMiddleware implement the Middleware interface.
|
||||
func (mw *StatusMiddleware) MiddlewareFunc(h HandlerFunc) HandlerFunc {
|
||||
|
||||
mw.start = time.Now()
|
||||
mw.pid = os.Getpid()
|
||||
mw.responseCounts = map[string]int{}
|
||||
mw.totalResponseTime = time.Time{}
|
||||
|
||||
return func(w ResponseWriter, r *Request) {
|
||||
|
||||
// call the handler
|
||||
h(w, r)
|
||||
|
||||
if r.Env["STATUS_CODE"] == nil {
|
||||
log.Fatal("StatusMiddleware: Env[\"STATUS_CODE\"] is nil, " +
|
||||
"RecorderMiddleware may not be in the wrapped Middlewares.")
|
||||
}
|
||||
statusCode := r.Env["STATUS_CODE"].(int)
|
||||
|
||||
if r.Env["ELAPSED_TIME"] == nil {
|
||||
log.Fatal("StatusMiddleware: Env[\"ELAPSED_TIME\"] is nil, " +
|
||||
"TimerMiddleware may not be in the wrapped Middlewares.")
|
||||
}
|
||||
responseTime := r.Env["ELAPSED_TIME"].(*time.Duration)
|
||||
|
||||
mw.lock.Lock()
|
||||
mw.responseCounts[fmt.Sprintf("%d", statusCode)]++
|
||||
mw.totalResponseTime = mw.totalResponseTime.Add(*responseTime)
|
||||
mw.lock.Unlock()
|
||||
}
|
||||
}
|
||||
|
||||
// Status contains stats and status information. It is returned by GetStatus.
|
||||
// These information can be made available as an API endpoint, see the "status"
|
||||
// example to install the following status route.
|
||||
// GET /.status returns something like:
|
||||
//
|
||||
// {
|
||||
// "Pid": 21732,
|
||||
// "UpTime": "1m15.926272s",
|
||||
// "UpTimeSec": 75.926272,
|
||||
// "Time": "2013-03-04 08:00:27.152986 +0000 UTC",
|
||||
// "TimeUnix": 1362384027,
|
||||
// "StatusCodeCount": {
|
||||
// "200": 53,
|
||||
// "404": 11
|
||||
// },
|
||||
// "TotalCount": 64,
|
||||
// "TotalResponseTime": "16.777ms",
|
||||
// "TotalResponseTimeSec": 0.016777,
|
||||
// "AverageResponseTime": "262.14us",
|
||||
// "AverageResponseTimeSec": 0.00026214
|
||||
// }
|
||||
type Status struct {
|
||||
Pid int
|
||||
UpTime string
|
||||
UpTimeSec float64
|
||||
Time string
|
||||
TimeUnix int64
|
||||
StatusCodeCount map[string]int
|
||||
TotalCount int
|
||||
TotalResponseTime string
|
||||
TotalResponseTimeSec float64
|
||||
AverageResponseTime string
|
||||
AverageResponseTimeSec float64
|
||||
}
|
||||
|
||||
// GetStatus computes and returns a Status object based on the request informations accumulated
|
||||
// since the start of the process.
|
||||
func (mw *StatusMiddleware) GetStatus() *Status {
|
||||
|
||||
mw.lock.RLock()
|
||||
|
||||
now := time.Now()
|
||||
|
||||
uptime := now.Sub(mw.start)
|
||||
|
||||
totalCount := 0
|
||||
for _, count := range mw.responseCounts {
|
||||
totalCount += count
|
||||
}
|
||||
|
||||
totalResponseTime := mw.totalResponseTime.Sub(time.Time{})
|
||||
|
||||
averageResponseTime := time.Duration(0)
|
||||
if totalCount > 0 {
|
||||
avgNs := int64(totalResponseTime) / int64(totalCount)
|
||||
averageResponseTime = time.Duration(avgNs)
|
||||
}
|
||||
|
||||
status := &Status{
|
||||
Pid: mw.pid,
|
||||
UpTime: uptime.String(),
|
||||
UpTimeSec: uptime.Seconds(),
|
||||
Time: now.String(),
|
||||
TimeUnix: now.Unix(),
|
||||
StatusCodeCount: mw.responseCounts,
|
||||
TotalCount: totalCount,
|
||||
TotalResponseTime: totalResponseTime.String(),
|
||||
TotalResponseTimeSec: totalResponseTime.Seconds(),
|
||||
AverageResponseTime: averageResponseTime.String(),
|
||||
AverageResponseTimeSec: averageResponseTime.Seconds(),
|
||||
}
|
||||
|
||||
mw.lock.RUnlock()
|
||||
|
||||
return status
|
||||
}
|
||||
26
vendor/github.com/ant0ine/go-json-rest/rest/timer.go
generated
vendored
Normal file
26
vendor/github.com/ant0ine/go-json-rest/rest/timer.go
generated
vendored
Normal file
@@ -0,0 +1,26 @@
|
||||
package rest
|
||||
|
||||
import (
|
||||
"time"
|
||||
)
|
||||
|
||||
// TimerMiddleware computes the elapsed time spent during the execution of the wrapped handler.
|
||||
// The result is available to the wrapping handlers as request.Env["ELAPSED_TIME"].(*time.Duration),
|
||||
// and as request.Env["START_TIME"].(*time.Time)
|
||||
type TimerMiddleware struct{}
|
||||
|
||||
// MiddlewareFunc makes TimerMiddleware implement the Middleware interface.
|
||||
func (mw *TimerMiddleware) MiddlewareFunc(h HandlerFunc) HandlerFunc {
|
||||
return func(w ResponseWriter, r *Request) {
|
||||
|
||||
start := time.Now()
|
||||
r.Env["START_TIME"] = &start
|
||||
|
||||
// call the handler
|
||||
h(w, r)
|
||||
|
||||
end := time.Now()
|
||||
elapsed := end.Sub(start)
|
||||
r.Env["ELAPSED_TIME"] = &elapsed
|
||||
}
|
||||
}
|
||||
426
vendor/github.com/ant0ine/go-json-rest/rest/trie/impl.go
generated
vendored
Normal file
426
vendor/github.com/ant0ine/go-json-rest/rest/trie/impl.go
generated
vendored
Normal file
@@ -0,0 +1,426 @@
|
||||
// Special Trie implementation for HTTP routing.
|
||||
//
|
||||
// This Trie implementation is designed to support strings that includes
|
||||
// :param and *splat parameters. Strings that are commonly used to represent
|
||||
// the Path in HTTP routing. This implementation also maintain for each Path
|
||||
// a map of HTTP Methods associated with the Route.
|
||||
//
|
||||
// You probably don't need to use this package directly.
|
||||
//
|
||||
package trie
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
)
|
||||
|
||||
func splitParam(remaining string) (string, string) {
|
||||
i := 0
|
||||
for len(remaining) > i && remaining[i] != '/' && remaining[i] != '.' {
|
||||
i++
|
||||
}
|
||||
return remaining[:i], remaining[i:]
|
||||
}
|
||||
|
||||
func splitRelaxed(remaining string) (string, string) {
|
||||
i := 0
|
||||
for len(remaining) > i && remaining[i] != '/' {
|
||||
i++
|
||||
}
|
||||
return remaining[:i], remaining[i:]
|
||||
}
|
||||
|
||||
type node struct {
|
||||
HttpMethodToRoute map[string]interface{}
|
||||
|
||||
Children map[string]*node
|
||||
ChildrenKeyLen int
|
||||
|
||||
ParamChild *node
|
||||
ParamName string
|
||||
|
||||
RelaxedChild *node
|
||||
RelaxedName string
|
||||
|
||||
SplatChild *node
|
||||
SplatName string
|
||||
}
|
||||
|
||||
func (n *node) addRoute(httpMethod, pathExp string, route interface{}, usedParams []string) error {
|
||||
|
||||
if len(pathExp) == 0 {
|
||||
// end of the path, leaf node, update the map
|
||||
if n.HttpMethodToRoute == nil {
|
||||
n.HttpMethodToRoute = map[string]interface{}{
|
||||
httpMethod: route,
|
||||
}
|
||||
return nil
|
||||
} else {
|
||||
if n.HttpMethodToRoute[httpMethod] != nil {
|
||||
return errors.New("node.Route already set, duplicated path and method")
|
||||
}
|
||||
n.HttpMethodToRoute[httpMethod] = route
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
token := pathExp[0:1]
|
||||
remaining := pathExp[1:]
|
||||
var nextNode *node
|
||||
|
||||
if token[0] == ':' {
|
||||
// :param case
|
||||
var name string
|
||||
name, remaining = splitParam(remaining)
|
||||
|
||||
// Check param name is unique
|
||||
for _, e := range usedParams {
|
||||
if e == name {
|
||||
return errors.New(
|
||||
fmt.Sprintf("A route can't have two placeholders with the same name: %s", name),
|
||||
)
|
||||
}
|
||||
}
|
||||
usedParams = append(usedParams, name)
|
||||
|
||||
if n.ParamChild == nil {
|
||||
n.ParamChild = &node{}
|
||||
n.ParamName = name
|
||||
} else {
|
||||
if n.ParamName != name {
|
||||
return errors.New(
|
||||
fmt.Sprintf(
|
||||
"Routes sharing a common placeholder MUST name it consistently: %s != %s",
|
||||
n.ParamName,
|
||||
name,
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
nextNode = n.ParamChild
|
||||
} else if token[0] == '#' {
|
||||
// #param case
|
||||
var name string
|
||||
name, remaining = splitRelaxed(remaining)
|
||||
|
||||
// Check param name is unique
|
||||
for _, e := range usedParams {
|
||||
if e == name {
|
||||
return errors.New(
|
||||
fmt.Sprintf("A route can't have two placeholders with the same name: %s", name),
|
||||
)
|
||||
}
|
||||
}
|
||||
usedParams = append(usedParams, name)
|
||||
|
||||
if n.RelaxedChild == nil {
|
||||
n.RelaxedChild = &node{}
|
||||
n.RelaxedName = name
|
||||
} else {
|
||||
if n.RelaxedName != name {
|
||||
return errors.New(
|
||||
fmt.Sprintf(
|
||||
"Routes sharing a common placeholder MUST name it consistently: %s != %s",
|
||||
n.RelaxedName,
|
||||
name,
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
nextNode = n.RelaxedChild
|
||||
} else if token[0] == '*' {
|
||||
// *splat case
|
||||
name := remaining
|
||||
remaining = ""
|
||||
|
||||
// Check param name is unique
|
||||
for _, e := range usedParams {
|
||||
if e == name {
|
||||
return errors.New(
|
||||
fmt.Sprintf("A route can't have two placeholders with the same name: %s", name),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
if n.SplatChild == nil {
|
||||
n.SplatChild = &node{}
|
||||
n.SplatName = name
|
||||
}
|
||||
nextNode = n.SplatChild
|
||||
} else {
|
||||
// general case
|
||||
if n.Children == nil {
|
||||
n.Children = map[string]*node{}
|
||||
n.ChildrenKeyLen = 1
|
||||
}
|
||||
if n.Children[token] == nil {
|
||||
n.Children[token] = &node{}
|
||||
}
|
||||
nextNode = n.Children[token]
|
||||
}
|
||||
|
||||
return nextNode.addRoute(httpMethod, remaining, route, usedParams)
|
||||
}
|
||||
|
||||
func (n *node) compress() {
|
||||
// *splat branch
|
||||
if n.SplatChild != nil {
|
||||
n.SplatChild.compress()
|
||||
}
|
||||
// :param branch
|
||||
if n.ParamChild != nil {
|
||||
n.ParamChild.compress()
|
||||
}
|
||||
// #param branch
|
||||
if n.RelaxedChild != nil {
|
||||
n.RelaxedChild.compress()
|
||||
}
|
||||
// main branch
|
||||
if len(n.Children) == 0 {
|
||||
return
|
||||
}
|
||||
// compressable ?
|
||||
canCompress := true
|
||||
for _, node := range n.Children {
|
||||
if node.HttpMethodToRoute != nil || node.SplatChild != nil || node.ParamChild != nil || node.RelaxedChild != nil {
|
||||
canCompress = false
|
||||
}
|
||||
}
|
||||
// compress
|
||||
if canCompress {
|
||||
merged := map[string]*node{}
|
||||
for key, node := range n.Children {
|
||||
for gdKey, gdNode := range node.Children {
|
||||
mergedKey := key + gdKey
|
||||
merged[mergedKey] = gdNode
|
||||
}
|
||||
}
|
||||
n.Children = merged
|
||||
n.ChildrenKeyLen++
|
||||
n.compress()
|
||||
// continue
|
||||
} else {
|
||||
for _, node := range n.Children {
|
||||
node.compress()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func printFPadding(padding int, format string, a ...interface{}) {
|
||||
for i := 0; i < padding; i++ {
|
||||
fmt.Print(" ")
|
||||
}
|
||||
fmt.Printf(format, a...)
|
||||
}
|
||||
|
||||
// Private function for now
|
||||
func (n *node) printDebug(level int) {
|
||||
level++
|
||||
// *splat branch
|
||||
if n.SplatChild != nil {
|
||||
printFPadding(level, "*splat\n")
|
||||
n.SplatChild.printDebug(level)
|
||||
}
|
||||
// :param branch
|
||||
if n.ParamChild != nil {
|
||||
printFPadding(level, ":param\n")
|
||||
n.ParamChild.printDebug(level)
|
||||
}
|
||||
// #param branch
|
||||
if n.RelaxedChild != nil {
|
||||
printFPadding(level, "#relaxed\n")
|
||||
n.RelaxedChild.printDebug(level)
|
||||
}
|
||||
// main branch
|
||||
for key, node := range n.Children {
|
||||
printFPadding(level, "\"%s\"\n", key)
|
||||
node.printDebug(level)
|
||||
}
|
||||
}
|
||||
|
||||
// utility for the node.findRoutes recursive method
|
||||
|
||||
type paramMatch struct {
|
||||
name string
|
||||
value string
|
||||
}
|
||||
|
||||
type findContext struct {
|
||||
paramStack []paramMatch
|
||||
matchFunc func(httpMethod, path string, node *node)
|
||||
}
|
||||
|
||||
func newFindContext() *findContext {
|
||||
return &findContext{
|
||||
paramStack: []paramMatch{},
|
||||
}
|
||||
}
|
||||
|
||||
func (fc *findContext) pushParams(name, value string) {
|
||||
fc.paramStack = append(
|
||||
fc.paramStack,
|
||||
paramMatch{name, value},
|
||||
)
|
||||
}
|
||||
|
||||
func (fc *findContext) popParams() {
|
||||
fc.paramStack = fc.paramStack[:len(fc.paramStack)-1]
|
||||
}
|
||||
|
||||
func (fc *findContext) paramsAsMap() map[string]string {
|
||||
r := map[string]string{}
|
||||
for _, param := range fc.paramStack {
|
||||
if r[param.name] != "" {
|
||||
// this is checked at addRoute time, and should never happen.
|
||||
panic(fmt.Sprintf(
|
||||
"placeholder %s already found, placeholder names should be unique per route",
|
||||
param.name,
|
||||
))
|
||||
}
|
||||
r[param.name] = param.value
|
||||
}
|
||||
return r
|
||||
}
|
||||
|
||||
type Match struct {
|
||||
// Same Route as in AddRoute
|
||||
Route interface{}
|
||||
// map of params matched for this result
|
||||
Params map[string]string
|
||||
}
|
||||
|
||||
func (n *node) find(httpMethod, path string, context *findContext) {
|
||||
|
||||
if n.HttpMethodToRoute != nil && path == "" {
|
||||
context.matchFunc(httpMethod, path, n)
|
||||
}
|
||||
|
||||
if len(path) == 0 {
|
||||
return
|
||||
}
|
||||
|
||||
// *splat branch
|
||||
if n.SplatChild != nil {
|
||||
context.pushParams(n.SplatName, path)
|
||||
n.SplatChild.find(httpMethod, "", context)
|
||||
context.popParams()
|
||||
}
|
||||
|
||||
// :param branch
|
||||
if n.ParamChild != nil {
|
||||
value, remaining := splitParam(path)
|
||||
context.pushParams(n.ParamName, value)
|
||||
n.ParamChild.find(httpMethod, remaining, context)
|
||||
context.popParams()
|
||||
}
|
||||
|
||||
// #param branch
|
||||
if n.RelaxedChild != nil {
|
||||
value, remaining := splitRelaxed(path)
|
||||
context.pushParams(n.RelaxedName, value)
|
||||
n.RelaxedChild.find(httpMethod, remaining, context)
|
||||
context.popParams()
|
||||
}
|
||||
|
||||
// main branch
|
||||
length := n.ChildrenKeyLen
|
||||
if len(path) < length {
|
||||
return
|
||||
}
|
||||
token := path[0:length]
|
||||
remaining := path[length:]
|
||||
if n.Children[token] != nil {
|
||||
n.Children[token].find(httpMethod, remaining, context)
|
||||
}
|
||||
}
|
||||
|
||||
type Trie struct {
|
||||
root *node
|
||||
}
|
||||
|
||||
// Instanciate a Trie with an empty node as the root.
|
||||
func New() *Trie {
|
||||
return &Trie{
|
||||
root: &node{},
|
||||
}
|
||||
}
|
||||
|
||||
// Insert the route in the Trie following or creating the nodes corresponding to the path.
|
||||
func (t *Trie) AddRoute(httpMethod, pathExp string, route interface{}) error {
|
||||
return t.root.addRoute(httpMethod, pathExp, route, []string{})
|
||||
}
|
||||
|
||||
// Reduce the size of the tree, must be done after the last AddRoute.
|
||||
func (t *Trie) Compress() {
|
||||
t.root.compress()
|
||||
}
|
||||
|
||||
// Private function for now.
|
||||
func (t *Trie) printDebug() {
|
||||
fmt.Print("<trie>\n")
|
||||
t.root.printDebug(0)
|
||||
fmt.Print("</trie>\n")
|
||||
}
|
||||
|
||||
// Given a path and an http method, return all the matching routes.
|
||||
func (t *Trie) FindRoutes(httpMethod, path string) []*Match {
|
||||
context := newFindContext()
|
||||
matches := []*Match{}
|
||||
context.matchFunc = func(httpMethod, path string, node *node) {
|
||||
if node.HttpMethodToRoute[httpMethod] != nil {
|
||||
// path and method match, found a route !
|
||||
matches = append(
|
||||
matches,
|
||||
&Match{
|
||||
Route: node.HttpMethodToRoute[httpMethod],
|
||||
Params: context.paramsAsMap(),
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
t.root.find(httpMethod, path, context)
|
||||
return matches
|
||||
}
|
||||
|
||||
// Same as FindRoutes, but return in addition a boolean indicating if the path was matched.
|
||||
// Useful to return 405
|
||||
func (t *Trie) FindRoutesAndPathMatched(httpMethod, path string) ([]*Match, bool) {
|
||||
context := newFindContext()
|
||||
pathMatched := false
|
||||
matches := []*Match{}
|
||||
context.matchFunc = func(httpMethod, path string, node *node) {
|
||||
pathMatched = true
|
||||
if node.HttpMethodToRoute[httpMethod] != nil {
|
||||
// path and method match, found a route !
|
||||
matches = append(
|
||||
matches,
|
||||
&Match{
|
||||
Route: node.HttpMethodToRoute[httpMethod],
|
||||
Params: context.paramsAsMap(),
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
t.root.find(httpMethod, path, context)
|
||||
return matches, pathMatched
|
||||
}
|
||||
|
||||
// Given a path, and whatever the http method, return all the matching routes.
|
||||
func (t *Trie) FindRoutesForPath(path string) []*Match {
|
||||
context := newFindContext()
|
||||
matches := []*Match{}
|
||||
context.matchFunc = func(httpMethod, path string, node *node) {
|
||||
params := context.paramsAsMap()
|
||||
for _, route := range node.HttpMethodToRoute {
|
||||
matches = append(
|
||||
matches,
|
||||
&Match{
|
||||
Route: route,
|
||||
Params: params,
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
t.root.find("", path, context)
|
||||
return matches
|
||||
}
|
||||
Reference in New Issue
Block a user