initial commit

This commit is contained in:
Patrick Nagurny
2018-10-19 15:31:41 -04:00
commit e2dd29259f
203 changed files with 44839 additions and 0 deletions

View 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
}

View 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
View 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{},
}

View 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
}

View 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
View 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
View 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
View 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
View 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)
}
}
}

View 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
View 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)
}

View 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)
}
}

View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
}
}

View 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
}