Introduce go chi web framework as frontend of macaron, so that we can move routes from macaron to chi step by step (#7420)
* When route cannot be found on chi, go to macaron * Stick chi version to 1.5.0 * Follow router log setting
This commit is contained in:
parent
0ae35c66f2
commit
c296f4fed6
49 changed files with 4796 additions and 249 deletions
32
vendor/github.com/go-chi/chi/middleware/basic_auth.go
generated
vendored
Normal file
32
vendor/github.com/go-chi/chi/middleware/basic_auth.go
generated
vendored
Normal file
|
@ -0,0 +1,32 @@
|
|||
package middleware
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
// BasicAuth implements a simple middleware handler for adding basic http auth to a route.
|
||||
func BasicAuth(realm string, creds map[string]string) func(next http.Handler) http.Handler {
|
||||
return func(next http.Handler) http.Handler {
|
||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
user, pass, ok := r.BasicAuth()
|
||||
if !ok {
|
||||
basicAuthFailed(w, realm)
|
||||
return
|
||||
}
|
||||
|
||||
credPass, credUserOk := creds[user]
|
||||
if !credUserOk || pass != credPass {
|
||||
basicAuthFailed(w, realm)
|
||||
return
|
||||
}
|
||||
|
||||
next.ServeHTTP(w, r)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func basicAuthFailed(w http.ResponseWriter, realm string) {
|
||||
w.Header().Add("WWW-Authenticate", fmt.Sprintf(`Basic realm="%s"`, realm))
|
||||
w.WriteHeader(http.StatusUnauthorized)
|
||||
}
|
399
vendor/github.com/go-chi/chi/middleware/compress.go
generated
vendored
Normal file
399
vendor/github.com/go-chi/chi/middleware/compress.go
generated
vendored
Normal file
|
@ -0,0 +1,399 @@
|
|||
package middleware
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"compress/flate"
|
||||
"compress/gzip"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"net"
|
||||
"net/http"
|
||||
"strings"
|
||||
"sync"
|
||||
)
|
||||
|
||||
var defaultCompressibleContentTypes = []string{
|
||||
"text/html",
|
||||
"text/css",
|
||||
"text/plain",
|
||||
"text/javascript",
|
||||
"application/javascript",
|
||||
"application/x-javascript",
|
||||
"application/json",
|
||||
"application/atom+xml",
|
||||
"application/rss+xml",
|
||||
"image/svg+xml",
|
||||
}
|
||||
|
||||
// Compress is a middleware that compresses response
|
||||
// body of a given content types to a data format based
|
||||
// on Accept-Encoding request header. It uses a given
|
||||
// compression level.
|
||||
//
|
||||
// NOTE: make sure to set the Content-Type header on your response
|
||||
// otherwise this middleware will not compress the response body. For ex, in
|
||||
// your handler you should set w.Header().Set("Content-Type", http.DetectContentType(yourBody))
|
||||
// or set it manually.
|
||||
//
|
||||
// Passing a compression level of 5 is sensible value
|
||||
func Compress(level int, types ...string) func(next http.Handler) http.Handler {
|
||||
compressor := NewCompressor(level, types...)
|
||||
return compressor.Handler
|
||||
}
|
||||
|
||||
// Compressor represents a set of encoding configurations.
|
||||
type Compressor struct {
|
||||
level int // The compression level.
|
||||
// The mapping of encoder names to encoder functions.
|
||||
encoders map[string]EncoderFunc
|
||||
// The mapping of pooled encoders to pools.
|
||||
pooledEncoders map[string]*sync.Pool
|
||||
// The set of content types allowed to be compressed.
|
||||
allowedTypes map[string]struct{}
|
||||
allowedWildcards map[string]struct{}
|
||||
// The list of encoders in order of decreasing precedence.
|
||||
encodingPrecedence []string
|
||||
}
|
||||
|
||||
// NewCompressor creates a new Compressor that will handle encoding responses.
|
||||
//
|
||||
// The level should be one of the ones defined in the flate package.
|
||||
// The types are the content types that are allowed to be compressed.
|
||||
func NewCompressor(level int, types ...string) *Compressor {
|
||||
// If types are provided, set those as the allowed types. If none are
|
||||
// provided, use the default list.
|
||||
allowedTypes := make(map[string]struct{})
|
||||
allowedWildcards := make(map[string]struct{})
|
||||
if len(types) > 0 {
|
||||
for _, t := range types {
|
||||
if strings.Contains(strings.TrimSuffix(t, "/*"), "*") {
|
||||
panic(fmt.Sprintf("middleware/compress: Unsupported content-type wildcard pattern '%s'. Only '/*' supported", t))
|
||||
}
|
||||
if strings.HasSuffix(t, "/*") {
|
||||
allowedWildcards[strings.TrimSuffix(t, "/*")] = struct{}{}
|
||||
} else {
|
||||
allowedTypes[t] = struct{}{}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
for _, t := range defaultCompressibleContentTypes {
|
||||
allowedTypes[t] = struct{}{}
|
||||
}
|
||||
}
|
||||
|
||||
c := &Compressor{
|
||||
level: level,
|
||||
encoders: make(map[string]EncoderFunc),
|
||||
pooledEncoders: make(map[string]*sync.Pool),
|
||||
allowedTypes: allowedTypes,
|
||||
allowedWildcards: allowedWildcards,
|
||||
}
|
||||
|
||||
// Set the default encoders. The precedence order uses the reverse
|
||||
// ordering that the encoders were added. This means adding new encoders
|
||||
// will move them to the front of the order.
|
||||
//
|
||||
// TODO:
|
||||
// lzma: Opera.
|
||||
// sdch: Chrome, Android. Gzip output + dictionary header.
|
||||
// br: Brotli, see https://github.com/go-chi/chi/pull/326
|
||||
|
||||
// HTTP 1.1 "deflate" (RFC 2616) stands for DEFLATE data (RFC 1951)
|
||||
// wrapped with zlib (RFC 1950). The zlib wrapper uses Adler-32
|
||||
// checksum compared to CRC-32 used in "gzip" and thus is faster.
|
||||
//
|
||||
// But.. some old browsers (MSIE, Safari 5.1) incorrectly expect
|
||||
// raw DEFLATE data only, without the mentioned zlib wrapper.
|
||||
// Because of this major confusion, most modern browsers try it
|
||||
// both ways, first looking for zlib headers.
|
||||
// Quote by Mark Adler: http://stackoverflow.com/a/9186091/385548
|
||||
//
|
||||
// The list of browsers having problems is quite big, see:
|
||||
// http://zoompf.com/blog/2012/02/lose-the-wait-http-compression
|
||||
// https://web.archive.org/web/20120321182910/http://www.vervestudios.co/projects/compression-tests/results
|
||||
//
|
||||
// That's why we prefer gzip over deflate. It's just more reliable
|
||||
// and not significantly slower than gzip.
|
||||
c.SetEncoder("deflate", encoderDeflate)
|
||||
|
||||
// TODO: Exception for old MSIE browsers that can't handle non-HTML?
|
||||
// https://zoompf.com/blog/2012/02/lose-the-wait-http-compression
|
||||
c.SetEncoder("gzip", encoderGzip)
|
||||
|
||||
// NOTE: Not implemented, intentionally:
|
||||
// case "compress": // LZW. Deprecated.
|
||||
// case "bzip2": // Too slow on-the-fly.
|
||||
// case "zopfli": // Too slow on-the-fly.
|
||||
// case "xz": // Too slow on-the-fly.
|
||||
return c
|
||||
}
|
||||
|
||||
// SetEncoder can be used to set the implementation of a compression algorithm.
|
||||
//
|
||||
// The encoding should be a standardised identifier. See:
|
||||
// https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Accept-Encoding
|
||||
//
|
||||
// For example, add the Brotli algortithm:
|
||||
//
|
||||
// import brotli_enc "gopkg.in/kothar/brotli-go.v0/enc"
|
||||
//
|
||||
// compressor := middleware.NewCompressor(5, "text/html")
|
||||
// compressor.SetEncoder("br", func(w http.ResponseWriter, level int) io.Writer {
|
||||
// params := brotli_enc.NewBrotliParams()
|
||||
// params.SetQuality(level)
|
||||
// return brotli_enc.NewBrotliWriter(params, w)
|
||||
// })
|
||||
func (c *Compressor) SetEncoder(encoding string, fn EncoderFunc) {
|
||||
encoding = strings.ToLower(encoding)
|
||||
if encoding == "" {
|
||||
panic("the encoding can not be empty")
|
||||
}
|
||||
if fn == nil {
|
||||
panic("attempted to set a nil encoder function")
|
||||
}
|
||||
|
||||
// If we are adding a new encoder that is already registered, we have to
|
||||
// clear that one out first.
|
||||
if _, ok := c.pooledEncoders[encoding]; ok {
|
||||
delete(c.pooledEncoders, encoding)
|
||||
}
|
||||
if _, ok := c.encoders[encoding]; ok {
|
||||
delete(c.encoders, encoding)
|
||||
}
|
||||
|
||||
// If the encoder supports Resetting (IoReseterWriter), then it can be pooled.
|
||||
encoder := fn(ioutil.Discard, c.level)
|
||||
if encoder != nil {
|
||||
if _, ok := encoder.(ioResetterWriter); ok {
|
||||
pool := &sync.Pool{
|
||||
New: func() interface{} {
|
||||
return fn(ioutil.Discard, c.level)
|
||||
},
|
||||
}
|
||||
c.pooledEncoders[encoding] = pool
|
||||
}
|
||||
}
|
||||
// If the encoder is not in the pooledEncoders, add it to the normal encoders.
|
||||
if _, ok := c.pooledEncoders[encoding]; !ok {
|
||||
c.encoders[encoding] = fn
|
||||
}
|
||||
|
||||
for i, v := range c.encodingPrecedence {
|
||||
if v == encoding {
|
||||
c.encodingPrecedence = append(c.encodingPrecedence[:i], c.encodingPrecedence[i+1:]...)
|
||||
}
|
||||
}
|
||||
|
||||
c.encodingPrecedence = append([]string{encoding}, c.encodingPrecedence...)
|
||||
}
|
||||
|
||||
// Handler returns a new middleware that will compress the response based on the
|
||||
// current Compressor.
|
||||
func (c *Compressor) Handler(next http.Handler) http.Handler {
|
||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
encoder, encoding, cleanup := c.selectEncoder(r.Header, w)
|
||||
|
||||
cw := &compressResponseWriter{
|
||||
ResponseWriter: w,
|
||||
w: w,
|
||||
contentTypes: c.allowedTypes,
|
||||
contentWildcards: c.allowedWildcards,
|
||||
encoding: encoding,
|
||||
compressable: false, // determined in post-handler
|
||||
}
|
||||
if encoder != nil {
|
||||
cw.w = encoder
|
||||
}
|
||||
// Re-add the encoder to the pool if applicable.
|
||||
defer cleanup()
|
||||
defer cw.Close()
|
||||
|
||||
next.ServeHTTP(cw, r)
|
||||
})
|
||||
}
|
||||
|
||||
// selectEncoder returns the encoder, the name of the encoder, and a closer function.
|
||||
func (c *Compressor) selectEncoder(h http.Header, w io.Writer) (io.Writer, string, func()) {
|
||||
header := h.Get("Accept-Encoding")
|
||||
|
||||
// Parse the names of all accepted algorithms from the header.
|
||||
accepted := strings.Split(strings.ToLower(header), ",")
|
||||
|
||||
// Find supported encoder by accepted list by precedence
|
||||
for _, name := range c.encodingPrecedence {
|
||||
if matchAcceptEncoding(accepted, name) {
|
||||
if pool, ok := c.pooledEncoders[name]; ok {
|
||||
encoder := pool.Get().(ioResetterWriter)
|
||||
cleanup := func() {
|
||||
pool.Put(encoder)
|
||||
}
|
||||
encoder.Reset(w)
|
||||
return encoder, name, cleanup
|
||||
|
||||
}
|
||||
if fn, ok := c.encoders[name]; ok {
|
||||
return fn(w, c.level), name, func() {}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// No encoder found to match the accepted encoding
|
||||
return nil, "", func() {}
|
||||
}
|
||||
|
||||
func matchAcceptEncoding(accepted []string, encoding string) bool {
|
||||
for _, v := range accepted {
|
||||
if strings.Contains(v, encoding) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// An EncoderFunc is a function that wraps the provided io.Writer with a
|
||||
// streaming compression algorithm and returns it.
|
||||
//
|
||||
// In case of failure, the function should return nil.
|
||||
type EncoderFunc func(w io.Writer, level int) io.Writer
|
||||
|
||||
// Interface for types that allow resetting io.Writers.
|
||||
type ioResetterWriter interface {
|
||||
io.Writer
|
||||
Reset(w io.Writer)
|
||||
}
|
||||
|
||||
type compressResponseWriter struct {
|
||||
http.ResponseWriter
|
||||
|
||||
// The streaming encoder writer to be used if there is one. Otherwise,
|
||||
// this is just the normal writer.
|
||||
w io.Writer
|
||||
encoding string
|
||||
contentTypes map[string]struct{}
|
||||
contentWildcards map[string]struct{}
|
||||
wroteHeader bool
|
||||
compressable bool
|
||||
}
|
||||
|
||||
func (cw *compressResponseWriter) isCompressable() bool {
|
||||
// Parse the first part of the Content-Type response header.
|
||||
contentType := cw.Header().Get("Content-Type")
|
||||
if idx := strings.Index(contentType, ";"); idx >= 0 {
|
||||
contentType = contentType[0:idx]
|
||||
}
|
||||
|
||||
// Is the content type compressable?
|
||||
if _, ok := cw.contentTypes[contentType]; ok {
|
||||
return true
|
||||
}
|
||||
if idx := strings.Index(contentType, "/"); idx > 0 {
|
||||
contentType = contentType[0:idx]
|
||||
_, ok := cw.contentWildcards[contentType]
|
||||
return ok
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (cw *compressResponseWriter) WriteHeader(code int) {
|
||||
if cw.wroteHeader {
|
||||
cw.ResponseWriter.WriteHeader(code) // Allow multiple calls to propagate.
|
||||
return
|
||||
}
|
||||
cw.wroteHeader = true
|
||||
defer cw.ResponseWriter.WriteHeader(code)
|
||||
|
||||
// Already compressed data?
|
||||
if cw.Header().Get("Content-Encoding") != "" {
|
||||
return
|
||||
}
|
||||
|
||||
if !cw.isCompressable() {
|
||||
cw.compressable = false
|
||||
return
|
||||
}
|
||||
|
||||
if cw.encoding != "" {
|
||||
cw.compressable = true
|
||||
cw.Header().Set("Content-Encoding", cw.encoding)
|
||||
cw.Header().Set("Vary", "Accept-Encoding")
|
||||
|
||||
// The content-length after compression is unknown
|
||||
cw.Header().Del("Content-Length")
|
||||
}
|
||||
}
|
||||
|
||||
func (cw *compressResponseWriter) Write(p []byte) (int, error) {
|
||||
if !cw.wroteHeader {
|
||||
cw.WriteHeader(http.StatusOK)
|
||||
}
|
||||
|
||||
return cw.writer().Write(p)
|
||||
}
|
||||
|
||||
func (cw *compressResponseWriter) writer() io.Writer {
|
||||
if cw.compressable {
|
||||
return cw.w
|
||||
} else {
|
||||
return cw.ResponseWriter
|
||||
}
|
||||
}
|
||||
|
||||
type compressFlusher interface {
|
||||
Flush() error
|
||||
}
|
||||
|
||||
func (cw *compressResponseWriter) Flush() {
|
||||
if f, ok := cw.writer().(http.Flusher); ok {
|
||||
f.Flush()
|
||||
}
|
||||
// If the underlying writer has a compression flush signature,
|
||||
// call this Flush() method instead
|
||||
if f, ok := cw.writer().(compressFlusher); ok {
|
||||
f.Flush()
|
||||
|
||||
// Also flush the underlying response writer
|
||||
if f, ok := cw.ResponseWriter.(http.Flusher); ok {
|
||||
f.Flush()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (cw *compressResponseWriter) Hijack() (net.Conn, *bufio.ReadWriter, error) {
|
||||
if hj, ok := cw.writer().(http.Hijacker); ok {
|
||||
return hj.Hijack()
|
||||
}
|
||||
return nil, nil, errors.New("chi/middleware: http.Hijacker is unavailable on the writer")
|
||||
}
|
||||
|
||||
func (cw *compressResponseWriter) Push(target string, opts *http.PushOptions) error {
|
||||
if ps, ok := cw.writer().(http.Pusher); ok {
|
||||
return ps.Push(target, opts)
|
||||
}
|
||||
return errors.New("chi/middleware: http.Pusher is unavailable on the writer")
|
||||
}
|
||||
|
||||
func (cw *compressResponseWriter) Close() error {
|
||||
if c, ok := cw.writer().(io.WriteCloser); ok {
|
||||
return c.Close()
|
||||
}
|
||||
return errors.New("chi/middleware: io.WriteCloser is unavailable on the writer")
|
||||
}
|
||||
|
||||
func encoderGzip(w io.Writer, level int) io.Writer {
|
||||
gw, err := gzip.NewWriterLevel(w, level)
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
return gw
|
||||
}
|
||||
|
||||
func encoderDeflate(w io.Writer, level int) io.Writer {
|
||||
dw, err := flate.NewWriter(w, level)
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
return dw
|
||||
}
|
51
vendor/github.com/go-chi/chi/middleware/content_charset.go
generated
vendored
Normal file
51
vendor/github.com/go-chi/chi/middleware/content_charset.go
generated
vendored
Normal file
|
@ -0,0 +1,51 @@
|
|||
package middleware
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// ContentCharset generates a handler that writes a 415 Unsupported Media Type response if none of the charsets match.
|
||||
// An empty charset will allow requests with no Content-Type header or no specified charset.
|
||||
func ContentCharset(charsets ...string) func(next http.Handler) http.Handler {
|
||||
for i, c := range charsets {
|
||||
charsets[i] = strings.ToLower(c)
|
||||
}
|
||||
|
||||
return func(next http.Handler) http.Handler {
|
||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
if !contentEncoding(r.Header.Get("Content-Type"), charsets...) {
|
||||
w.WriteHeader(http.StatusUnsupportedMediaType)
|
||||
return
|
||||
}
|
||||
|
||||
next.ServeHTTP(w, r)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// Check the content encoding against a list of acceptable values.
|
||||
func contentEncoding(ce string, charsets ...string) bool {
|
||||
_, ce = split(strings.ToLower(ce), ";")
|
||||
_, ce = split(ce, "charset=")
|
||||
ce, _ = split(ce, ";")
|
||||
for _, c := range charsets {
|
||||
if ce == c {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
// Split a string in two parts, cleaning any whitespace.
|
||||
func split(str, sep string) (string, string) {
|
||||
var a, b string
|
||||
var parts = strings.SplitN(str, sep, 2)
|
||||
a = strings.TrimSpace(parts[0])
|
||||
if len(parts) == 2 {
|
||||
b = strings.TrimSpace(parts[1])
|
||||
}
|
||||
|
||||
return a, b
|
||||
}
|
34
vendor/github.com/go-chi/chi/middleware/content_encoding.go
generated
vendored
Normal file
34
vendor/github.com/go-chi/chi/middleware/content_encoding.go
generated
vendored
Normal file
|
@ -0,0 +1,34 @@
|
|||
package middleware
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// AllowContentEncoding enforces a whitelist of request Content-Encoding otherwise responds
|
||||
// with a 415 Unsupported Media Type status.
|
||||
func AllowContentEncoding(contentEncoding ...string) func(next http.Handler) http.Handler {
|
||||
allowedEncodings := make(map[string]struct{}, len(contentEncoding))
|
||||
for _, encoding := range contentEncoding {
|
||||
allowedEncodings[strings.TrimSpace(strings.ToLower(encoding))] = struct{}{}
|
||||
}
|
||||
return func(next http.Handler) http.Handler {
|
||||
fn := func(w http.ResponseWriter, r *http.Request) {
|
||||
requestEncodings := r.Header["Content-Encoding"]
|
||||
// skip check for empty content body or no Content-Encoding
|
||||
if r.ContentLength == 0 {
|
||||
next.ServeHTTP(w, r)
|
||||
return
|
||||
}
|
||||
// All encodings in the request must be allowed
|
||||
for _, encoding := range requestEncodings {
|
||||
if _, ok := allowedEncodings[strings.TrimSpace(strings.ToLower(encoding))]; !ok {
|
||||
w.WriteHeader(http.StatusUnsupportedMediaType)
|
||||
return
|
||||
}
|
||||
}
|
||||
next.ServeHTTP(w, r)
|
||||
}
|
||||
return http.HandlerFunc(fn)
|
||||
}
|
||||
}
|
51
vendor/github.com/go-chi/chi/middleware/content_type.go
generated
vendored
Normal file
51
vendor/github.com/go-chi/chi/middleware/content_type.go
generated
vendored
Normal file
|
@ -0,0 +1,51 @@
|
|||
package middleware
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// SetHeader is a convenience handler to set a response header key/value
|
||||
func SetHeader(key, value string) func(next http.Handler) http.Handler {
|
||||
return func(next http.Handler) http.Handler {
|
||||
fn := func(w http.ResponseWriter, r *http.Request) {
|
||||
w.Header().Set(key, value)
|
||||
next.ServeHTTP(w, r)
|
||||
}
|
||||
return http.HandlerFunc(fn)
|
||||
}
|
||||
}
|
||||
|
||||
// AllowContentType enforces a whitelist of request Content-Types otherwise responds
|
||||
// with a 415 Unsupported Media Type status.
|
||||
func AllowContentType(contentTypes ...string) func(next http.Handler) http.Handler {
|
||||
cT := []string{}
|
||||
for _, t := range contentTypes {
|
||||
cT = append(cT, strings.ToLower(t))
|
||||
}
|
||||
|
||||
return func(next http.Handler) http.Handler {
|
||||
fn := func(w http.ResponseWriter, r *http.Request) {
|
||||
if r.ContentLength == 0 {
|
||||
// skip check for empty content body
|
||||
next.ServeHTTP(w, r)
|
||||
return
|
||||
}
|
||||
|
||||
s := strings.ToLower(strings.TrimSpace(r.Header.Get("Content-Type")))
|
||||
if i := strings.Index(s, ";"); i > -1 {
|
||||
s = s[0:i]
|
||||
}
|
||||
|
||||
for _, t := range cT {
|
||||
if t == s {
|
||||
next.ServeHTTP(w, r)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
w.WriteHeader(http.StatusUnsupportedMediaType)
|
||||
}
|
||||
return http.HandlerFunc(fn)
|
||||
}
|
||||
}
|
39
vendor/github.com/go-chi/chi/middleware/get_head.go
generated
vendored
Normal file
39
vendor/github.com/go-chi/chi/middleware/get_head.go
generated
vendored
Normal file
|
@ -0,0 +1,39 @@
|
|||
package middleware
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"github.com/go-chi/chi"
|
||||
)
|
||||
|
||||
// GetHead automatically route undefined HEAD requests to GET handlers.
|
||||
func GetHead(next http.Handler) http.Handler {
|
||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
if r.Method == "HEAD" {
|
||||
rctx := chi.RouteContext(r.Context())
|
||||
routePath := rctx.RoutePath
|
||||
if routePath == "" {
|
||||
if r.URL.RawPath != "" {
|
||||
routePath = r.URL.RawPath
|
||||
} else {
|
||||
routePath = r.URL.Path
|
||||
}
|
||||
}
|
||||
|
||||
// Temporary routing context to look-ahead before routing the request
|
||||
tctx := chi.NewRouteContext()
|
||||
|
||||
// Attempt to find a HEAD handler for the routing path, if not found, traverse
|
||||
// the router as through its a GET route, but proceed with the request
|
||||
// with the HEAD method.
|
||||
if !rctx.Routes.Match(tctx, "HEAD", routePath) {
|
||||
rctx.RouteMethod = "GET"
|
||||
rctx.RoutePath = routePath
|
||||
next.ServeHTTP(w, r)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
next.ServeHTTP(w, r)
|
||||
})
|
||||
}
|
26
vendor/github.com/go-chi/chi/middleware/heartbeat.go
generated
vendored
Normal file
26
vendor/github.com/go-chi/chi/middleware/heartbeat.go
generated
vendored
Normal file
|
@ -0,0 +1,26 @@
|
|||
package middleware
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// Heartbeat endpoint middleware useful to setting up a path like
|
||||
// `/ping` that load balancers or uptime testing external services
|
||||
// can make a request before hitting any routes. It's also convenient
|
||||
// to place this above ACL middlewares as well.
|
||||
func Heartbeat(endpoint string) func(http.Handler) http.Handler {
|
||||
f := func(h http.Handler) http.Handler {
|
||||
fn := func(w http.ResponseWriter, r *http.Request) {
|
||||
if r.Method == "GET" && strings.EqualFold(r.URL.Path, endpoint) {
|
||||
w.Header().Set("Content-Type", "text/plain")
|
||||
w.WriteHeader(http.StatusOK)
|
||||
w.Write([]byte("."))
|
||||
return
|
||||
}
|
||||
h.ServeHTTP(w, r)
|
||||
}
|
||||
return http.HandlerFunc(fn)
|
||||
}
|
||||
return f
|
||||
}
|
155
vendor/github.com/go-chi/chi/middleware/logger.go
generated
vendored
Normal file
155
vendor/github.com/go-chi/chi/middleware/logger.go
generated
vendored
Normal file
|
@ -0,0 +1,155 @@
|
|||
package middleware
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"log"
|
||||
"net/http"
|
||||
"os"
|
||||
"time"
|
||||
)
|
||||
|
||||
var (
|
||||
// LogEntryCtxKey is the context.Context key to store the request log entry.
|
||||
LogEntryCtxKey = &contextKey{"LogEntry"}
|
||||
|
||||
// DefaultLogger is called by the Logger middleware handler to log each request.
|
||||
// Its made a package-level variable so that it can be reconfigured for custom
|
||||
// logging configurations.
|
||||
DefaultLogger = RequestLogger(&DefaultLogFormatter{Logger: log.New(os.Stdout, "", log.LstdFlags), NoColor: false})
|
||||
)
|
||||
|
||||
// Logger is a middleware that logs the start and end of each request, along
|
||||
// with some useful data about what was requested, what the response status was,
|
||||
// and how long it took to return. When standard output is a TTY, Logger will
|
||||
// print in color, otherwise it will print in black and white. Logger prints a
|
||||
// request ID if one is provided.
|
||||
//
|
||||
// Alternatively, look at https://github.com/goware/httplog for a more in-depth
|
||||
// http logger with structured logging support.
|
||||
func Logger(next http.Handler) http.Handler {
|
||||
return DefaultLogger(next)
|
||||
}
|
||||
|
||||
// RequestLogger returns a logger handler using a custom LogFormatter.
|
||||
func RequestLogger(f LogFormatter) func(next http.Handler) http.Handler {
|
||||
return func(next http.Handler) http.Handler {
|
||||
fn := func(w http.ResponseWriter, r *http.Request) {
|
||||
entry := f.NewLogEntry(r)
|
||||
ww := NewWrapResponseWriter(w, r.ProtoMajor)
|
||||
|
||||
t1 := time.Now()
|
||||
defer func() {
|
||||
entry.Write(ww.Status(), ww.BytesWritten(), ww.Header(), time.Since(t1), nil)
|
||||
}()
|
||||
|
||||
next.ServeHTTP(ww, WithLogEntry(r, entry))
|
||||
}
|
||||
return http.HandlerFunc(fn)
|
||||
}
|
||||
}
|
||||
|
||||
// LogFormatter initiates the beginning of a new LogEntry per request.
|
||||
// See DefaultLogFormatter for an example implementation.
|
||||
type LogFormatter interface {
|
||||
NewLogEntry(r *http.Request) LogEntry
|
||||
}
|
||||
|
||||
// LogEntry records the final log when a request completes.
|
||||
// See defaultLogEntry for an example implementation.
|
||||
type LogEntry interface {
|
||||
Write(status, bytes int, header http.Header, elapsed time.Duration, extra interface{})
|
||||
Panic(v interface{}, stack []byte)
|
||||
}
|
||||
|
||||
// GetLogEntry returns the in-context LogEntry for a request.
|
||||
func GetLogEntry(r *http.Request) LogEntry {
|
||||
entry, _ := r.Context().Value(LogEntryCtxKey).(LogEntry)
|
||||
return entry
|
||||
}
|
||||
|
||||
// WithLogEntry sets the in-context LogEntry for a request.
|
||||
func WithLogEntry(r *http.Request, entry LogEntry) *http.Request {
|
||||
r = r.WithContext(context.WithValue(r.Context(), LogEntryCtxKey, entry))
|
||||
return r
|
||||
}
|
||||
|
||||
// LoggerInterface accepts printing to stdlib logger or compatible logger.
|
||||
type LoggerInterface interface {
|
||||
Print(v ...interface{})
|
||||
}
|
||||
|
||||
// DefaultLogFormatter is a simple logger that implements a LogFormatter.
|
||||
type DefaultLogFormatter struct {
|
||||
Logger LoggerInterface
|
||||
NoColor bool
|
||||
}
|
||||
|
||||
// NewLogEntry creates a new LogEntry for the request.
|
||||
func (l *DefaultLogFormatter) NewLogEntry(r *http.Request) LogEntry {
|
||||
useColor := !l.NoColor
|
||||
entry := &defaultLogEntry{
|
||||
DefaultLogFormatter: l,
|
||||
request: r,
|
||||
buf: &bytes.Buffer{},
|
||||
useColor: useColor,
|
||||
}
|
||||
|
||||
reqID := GetReqID(r.Context())
|
||||
if reqID != "" {
|
||||
cW(entry.buf, useColor, nYellow, "[%s] ", reqID)
|
||||
}
|
||||
cW(entry.buf, useColor, nCyan, "\"")
|
||||
cW(entry.buf, useColor, bMagenta, "%s ", r.Method)
|
||||
|
||||
scheme := "http"
|
||||
if r.TLS != nil {
|
||||
scheme = "https"
|
||||
}
|
||||
cW(entry.buf, useColor, nCyan, "%s://%s%s %s\" ", scheme, r.Host, r.RequestURI, r.Proto)
|
||||
|
||||
entry.buf.WriteString("from ")
|
||||
entry.buf.WriteString(r.RemoteAddr)
|
||||
entry.buf.WriteString(" - ")
|
||||
|
||||
return entry
|
||||
}
|
||||
|
||||
type defaultLogEntry struct {
|
||||
*DefaultLogFormatter
|
||||
request *http.Request
|
||||
buf *bytes.Buffer
|
||||
useColor bool
|
||||
}
|
||||
|
||||
func (l *defaultLogEntry) Write(status, bytes int, header http.Header, elapsed time.Duration, extra interface{}) {
|
||||
switch {
|
||||
case status < 200:
|
||||
cW(l.buf, l.useColor, bBlue, "%03d", status)
|
||||
case status < 300:
|
||||
cW(l.buf, l.useColor, bGreen, "%03d", status)
|
||||
case status < 400:
|
||||
cW(l.buf, l.useColor, bCyan, "%03d", status)
|
||||
case status < 500:
|
||||
cW(l.buf, l.useColor, bYellow, "%03d", status)
|
||||
default:
|
||||
cW(l.buf, l.useColor, bRed, "%03d", status)
|
||||
}
|
||||
|
||||
cW(l.buf, l.useColor, bBlue, " %dB", bytes)
|
||||
|
||||
l.buf.WriteString(" in ")
|
||||
if elapsed < 500*time.Millisecond {
|
||||
cW(l.buf, l.useColor, nGreen, "%s", elapsed)
|
||||
} else if elapsed < 5*time.Second {
|
||||
cW(l.buf, l.useColor, nYellow, "%s", elapsed)
|
||||
} else {
|
||||
cW(l.buf, l.useColor, nRed, "%s", elapsed)
|
||||
}
|
||||
|
||||
l.Logger.Print(l.buf.String())
|
||||
}
|
||||
|
||||
func (l *defaultLogEntry) Panic(v interface{}, stack []byte) {
|
||||
PrintPrettyStack(v)
|
||||
}
|
23
vendor/github.com/go-chi/chi/middleware/middleware.go
generated
vendored
Normal file
23
vendor/github.com/go-chi/chi/middleware/middleware.go
generated
vendored
Normal file
|
@ -0,0 +1,23 @@
|
|||
package middleware
|
||||
|
||||
import "net/http"
|
||||
|
||||
// New will create a new middleware handler from a http.Handler.
|
||||
func New(h http.Handler) func(next http.Handler) http.Handler {
|
||||
return func(next http.Handler) http.Handler {
|
||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
h.ServeHTTP(w, r)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// contextKey is a value for use with context.WithValue. It's used as
|
||||
// a pointer so it fits in an interface{} without allocation. This technique
|
||||
// for defining context keys was copied from Go 1.7's new use of context in net/http.
|
||||
type contextKey struct {
|
||||
name string
|
||||
}
|
||||
|
||||
func (k *contextKey) String() string {
|
||||
return "chi/middleware context value " + k.name
|
||||
}
|
58
vendor/github.com/go-chi/chi/middleware/nocache.go
generated
vendored
Normal file
58
vendor/github.com/go-chi/chi/middleware/nocache.go
generated
vendored
Normal file
|
@ -0,0 +1,58 @@
|
|||
package middleware
|
||||
|
||||
// Ported from Goji's middleware, source:
|
||||
// https://github.com/zenazn/goji/tree/master/web/middleware
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"time"
|
||||
)
|
||||
|
||||
// Unix epoch time
|
||||
var epoch = time.Unix(0, 0).Format(time.RFC1123)
|
||||
|
||||
// Taken from https://github.com/mytrile/nocache
|
||||
var noCacheHeaders = map[string]string{
|
||||
"Expires": epoch,
|
||||
"Cache-Control": "no-cache, no-store, no-transform, must-revalidate, private, max-age=0",
|
||||
"Pragma": "no-cache",
|
||||
"X-Accel-Expires": "0",
|
||||
}
|
||||
|
||||
var etagHeaders = []string{
|
||||
"ETag",
|
||||
"If-Modified-Since",
|
||||
"If-Match",
|
||||
"If-None-Match",
|
||||
"If-Range",
|
||||
"If-Unmodified-Since",
|
||||
}
|
||||
|
||||
// NoCache is a simple piece of middleware that sets a number of HTTP headers to prevent
|
||||
// a router (or subrouter) from being cached by an upstream proxy and/or client.
|
||||
//
|
||||
// As per http://wiki.nginx.org/HttpProxyModule - NoCache sets:
|
||||
// Expires: Thu, 01 Jan 1970 00:00:00 UTC
|
||||
// Cache-Control: no-cache, private, max-age=0
|
||||
// X-Accel-Expires: 0
|
||||
// Pragma: no-cache (for HTTP/1.0 proxies/clients)
|
||||
func NoCache(h http.Handler) http.Handler {
|
||||
fn := func(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
// Delete any ETag headers that may have been set
|
||||
for _, v := range etagHeaders {
|
||||
if r.Header.Get(v) != "" {
|
||||
r.Header.Del(v)
|
||||
}
|
||||
}
|
||||
|
||||
// Set our NoCache headers
|
||||
for k, v := range noCacheHeaders {
|
||||
w.Header().Set(k, v)
|
||||
}
|
||||
|
||||
h.ServeHTTP(w, r)
|
||||
}
|
||||
|
||||
return http.HandlerFunc(fn)
|
||||
}
|
55
vendor/github.com/go-chi/chi/middleware/profiler.go
generated
vendored
Normal file
55
vendor/github.com/go-chi/chi/middleware/profiler.go
generated
vendored
Normal file
|
@ -0,0 +1,55 @@
|
|||
package middleware
|
||||
|
||||
import (
|
||||
"expvar"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"net/http/pprof"
|
||||
|
||||
"github.com/go-chi/chi"
|
||||
)
|
||||
|
||||
// Profiler is a convenient subrouter used for mounting net/http/pprof. ie.
|
||||
//
|
||||
// func MyService() http.Handler {
|
||||
// r := chi.NewRouter()
|
||||
// // ..middlewares
|
||||
// r.Mount("/debug", middleware.Profiler())
|
||||
// // ..routes
|
||||
// return r
|
||||
// }
|
||||
func Profiler() http.Handler {
|
||||
r := chi.NewRouter()
|
||||
r.Use(NoCache)
|
||||
|
||||
r.Get("/", func(w http.ResponseWriter, r *http.Request) {
|
||||
http.Redirect(w, r, r.RequestURI+"/pprof/", 301)
|
||||
})
|
||||
r.HandleFunc("/pprof", func(w http.ResponseWriter, r *http.Request) {
|
||||
http.Redirect(w, r, r.RequestURI+"/", 301)
|
||||
})
|
||||
|
||||
r.HandleFunc("/pprof/*", pprof.Index)
|
||||
r.HandleFunc("/pprof/cmdline", pprof.Cmdline)
|
||||
r.HandleFunc("/pprof/profile", pprof.Profile)
|
||||
r.HandleFunc("/pprof/symbol", pprof.Symbol)
|
||||
r.HandleFunc("/pprof/trace", pprof.Trace)
|
||||
r.HandleFunc("/vars", expVars)
|
||||
|
||||
return r
|
||||
}
|
||||
|
||||
// Replicated from expvar.go as not public.
|
||||
func expVars(w http.ResponseWriter, r *http.Request) {
|
||||
first := true
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
fmt.Fprintf(w, "{\n")
|
||||
expvar.Do(func(kv expvar.KeyValue) {
|
||||
if !first {
|
||||
fmt.Fprintf(w, ",\n")
|
||||
}
|
||||
first = false
|
||||
fmt.Fprintf(w, "%q: %s", kv.Key, kv.Value)
|
||||
})
|
||||
fmt.Fprintf(w, "\n}\n")
|
||||
}
|
54
vendor/github.com/go-chi/chi/middleware/realip.go
generated
vendored
Normal file
54
vendor/github.com/go-chi/chi/middleware/realip.go
generated
vendored
Normal file
|
@ -0,0 +1,54 @@
|
|||
package middleware
|
||||
|
||||
// Ported from Goji's middleware, source:
|
||||
// https://github.com/zenazn/goji/tree/master/web/middleware
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"strings"
|
||||
)
|
||||
|
||||
var xForwardedFor = http.CanonicalHeaderKey("X-Forwarded-For")
|
||||
var xRealIP = http.CanonicalHeaderKey("X-Real-IP")
|
||||
|
||||
// RealIP is a middleware that sets a http.Request's RemoteAddr to the results
|
||||
// of parsing either the X-Forwarded-For header or the X-Real-IP header (in that
|
||||
// order).
|
||||
//
|
||||
// This middleware should be inserted fairly early in the middleware stack to
|
||||
// ensure that subsequent layers (e.g., request loggers) which examine the
|
||||
// RemoteAddr will see the intended value.
|
||||
//
|
||||
// You should only use this middleware if you can trust the headers passed to
|
||||
// you (in particular, the two headers this middleware uses), for example
|
||||
// because you have placed a reverse proxy like HAProxy or nginx in front of
|
||||
// chi. If your reverse proxies are configured to pass along arbitrary header
|
||||
// values from the client, or if you use this middleware without a reverse
|
||||
// proxy, malicious clients will be able to make you very sad (or, depending on
|
||||
// how you're using RemoteAddr, vulnerable to an attack of some sort).
|
||||
func RealIP(h http.Handler) http.Handler {
|
||||
fn := func(w http.ResponseWriter, r *http.Request) {
|
||||
if rip := realIP(r); rip != "" {
|
||||
r.RemoteAddr = rip
|
||||
}
|
||||
h.ServeHTTP(w, r)
|
||||
}
|
||||
|
||||
return http.HandlerFunc(fn)
|
||||
}
|
||||
|
||||
func realIP(r *http.Request) string {
|
||||
var ip string
|
||||
|
||||
if xrip := r.Header.Get(xRealIP); xrip != "" {
|
||||
ip = xrip
|
||||
} else if xff := r.Header.Get(xForwardedFor); xff != "" {
|
||||
i := strings.Index(xff, ", ")
|
||||
if i == -1 {
|
||||
i = len(xff)
|
||||
}
|
||||
ip = xff[:i]
|
||||
}
|
||||
|
||||
return ip
|
||||
}
|
192
vendor/github.com/go-chi/chi/middleware/recoverer.go
generated
vendored
Normal file
192
vendor/github.com/go-chi/chi/middleware/recoverer.go
generated
vendored
Normal file
|
@ -0,0 +1,192 @@
|
|||
package middleware
|
||||
|
||||
// The original work was derived from Goji's middleware, source:
|
||||
// https://github.com/zenazn/goji/tree/master/web/middleware
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"os"
|
||||
"runtime/debug"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// Recoverer is a middleware that recovers from panics, logs the panic (and a
|
||||
// backtrace), and returns a HTTP 500 (Internal Server Error) status if
|
||||
// possible. Recoverer prints a request ID if one is provided.
|
||||
//
|
||||
// Alternatively, look at https://github.com/pressly/lg middleware pkgs.
|
||||
func Recoverer(next http.Handler) http.Handler {
|
||||
fn := func(w http.ResponseWriter, r *http.Request) {
|
||||
defer func() {
|
||||
if rvr := recover(); rvr != nil && rvr != http.ErrAbortHandler {
|
||||
|
||||
logEntry := GetLogEntry(r)
|
||||
if logEntry != nil {
|
||||
logEntry.Panic(rvr, debug.Stack())
|
||||
} else {
|
||||
PrintPrettyStack(rvr)
|
||||
}
|
||||
|
||||
w.WriteHeader(http.StatusInternalServerError)
|
||||
}
|
||||
}()
|
||||
|
||||
next.ServeHTTP(w, r)
|
||||
}
|
||||
|
||||
return http.HandlerFunc(fn)
|
||||
}
|
||||
|
||||
func PrintPrettyStack(rvr interface{}) {
|
||||
debugStack := debug.Stack()
|
||||
s := prettyStack{}
|
||||
out, err := s.parse(debugStack, rvr)
|
||||
if err == nil {
|
||||
os.Stderr.Write(out)
|
||||
} else {
|
||||
// print stdlib output as a fallback
|
||||
os.Stderr.Write(debugStack)
|
||||
}
|
||||
}
|
||||
|
||||
type prettyStack struct {
|
||||
}
|
||||
|
||||
func (s prettyStack) parse(debugStack []byte, rvr interface{}) ([]byte, error) {
|
||||
var err error
|
||||
useColor := true
|
||||
buf := &bytes.Buffer{}
|
||||
|
||||
cW(buf, false, bRed, "\n")
|
||||
cW(buf, useColor, bCyan, " panic: ")
|
||||
cW(buf, useColor, bBlue, "%v", rvr)
|
||||
cW(buf, false, bWhite, "\n \n")
|
||||
|
||||
// process debug stack info
|
||||
stack := strings.Split(string(debugStack), "\n")
|
||||
lines := []string{}
|
||||
|
||||
// locate panic line, as we may have nested panics
|
||||
for i := len(stack) - 1; i > 0; i-- {
|
||||
lines = append(lines, stack[i])
|
||||
if strings.HasPrefix(stack[i], "panic(0x") {
|
||||
lines = lines[0 : len(lines)-2] // remove boilerplate
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
// reverse
|
||||
for i := len(lines)/2 - 1; i >= 0; i-- {
|
||||
opp := len(lines) - 1 - i
|
||||
lines[i], lines[opp] = lines[opp], lines[i]
|
||||
}
|
||||
|
||||
// decorate
|
||||
for i, line := range lines {
|
||||
lines[i], err = s.decorateLine(line, useColor, i)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
for _, l := range lines {
|
||||
fmt.Fprintf(buf, "%s", l)
|
||||
}
|
||||
return buf.Bytes(), nil
|
||||
}
|
||||
|
||||
func (s prettyStack) decorateLine(line string, useColor bool, num int) (string, error) {
|
||||
line = strings.TrimSpace(line)
|
||||
if strings.HasPrefix(line, "\t") || strings.Contains(line, ".go:") {
|
||||
return s.decorateSourceLine(line, useColor, num)
|
||||
} else if strings.HasSuffix(line, ")") {
|
||||
return s.decorateFuncCallLine(line, useColor, num)
|
||||
} else {
|
||||
if strings.HasPrefix(line, "\t") {
|
||||
return strings.Replace(line, "\t", " ", 1), nil
|
||||
} else {
|
||||
return fmt.Sprintf(" %s\n", line), nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (s prettyStack) decorateFuncCallLine(line string, useColor bool, num int) (string, error) {
|
||||
idx := strings.LastIndex(line, "(")
|
||||
if idx < 0 {
|
||||
return "", errors.New("not a func call line")
|
||||
}
|
||||
|
||||
buf := &bytes.Buffer{}
|
||||
pkg := line[0:idx]
|
||||
// addr := line[idx:]
|
||||
method := ""
|
||||
|
||||
idx = strings.LastIndex(pkg, string(os.PathSeparator))
|
||||
if idx < 0 {
|
||||
idx = strings.Index(pkg, ".")
|
||||
method = pkg[idx:]
|
||||
pkg = pkg[0:idx]
|
||||
} else {
|
||||
method = pkg[idx+1:]
|
||||
pkg = pkg[0 : idx+1]
|
||||
idx = strings.Index(method, ".")
|
||||
pkg += method[0:idx]
|
||||
method = method[idx:]
|
||||
}
|
||||
pkgColor := nYellow
|
||||
methodColor := bGreen
|
||||
|
||||
if num == 0 {
|
||||
cW(buf, useColor, bRed, " -> ")
|
||||
pkgColor = bMagenta
|
||||
methodColor = bRed
|
||||
} else {
|
||||
cW(buf, useColor, bWhite, " ")
|
||||
}
|
||||
cW(buf, useColor, pkgColor, "%s", pkg)
|
||||
cW(buf, useColor, methodColor, "%s\n", method)
|
||||
// cW(buf, useColor, nBlack, "%s", addr)
|
||||
return buf.String(), nil
|
||||
}
|
||||
|
||||
func (s prettyStack) decorateSourceLine(line string, useColor bool, num int) (string, error) {
|
||||
idx := strings.LastIndex(line, ".go:")
|
||||
if idx < 0 {
|
||||
return "", errors.New("not a source line")
|
||||
}
|
||||
|
||||
buf := &bytes.Buffer{}
|
||||
path := line[0 : idx+3]
|
||||
lineno := line[idx+3:]
|
||||
|
||||
idx = strings.LastIndex(path, string(os.PathSeparator))
|
||||
dir := path[0 : idx+1]
|
||||
file := path[idx+1:]
|
||||
|
||||
idx = strings.Index(lineno, " ")
|
||||
if idx > 0 {
|
||||
lineno = lineno[0:idx]
|
||||
}
|
||||
fileColor := bCyan
|
||||
lineColor := bGreen
|
||||
|
||||
if num == 1 {
|
||||
cW(buf, useColor, bRed, " -> ")
|
||||
fileColor = bRed
|
||||
lineColor = bMagenta
|
||||
} else {
|
||||
cW(buf, false, bWhite, " ")
|
||||
}
|
||||
cW(buf, useColor, bWhite, "%s", dir)
|
||||
cW(buf, useColor, fileColor, "%s", file)
|
||||
cW(buf, useColor, lineColor, "%s", lineno)
|
||||
if num == 1 {
|
||||
cW(buf, false, bWhite, "\n")
|
||||
}
|
||||
cW(buf, false, bWhite, "\n")
|
||||
|
||||
return buf.String(), nil
|
||||
}
|
96
vendor/github.com/go-chi/chi/middleware/request_id.go
generated
vendored
Normal file
96
vendor/github.com/go-chi/chi/middleware/request_id.go
generated
vendored
Normal file
|
@ -0,0 +1,96 @@
|
|||
package middleware
|
||||
|
||||
// Ported from Goji's middleware, source:
|
||||
// https://github.com/zenazn/goji/tree/master/web/middleware
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/rand"
|
||||
"encoding/base64"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"os"
|
||||
"strings"
|
||||
"sync/atomic"
|
||||
)
|
||||
|
||||
// Key to use when setting the request ID.
|
||||
type ctxKeyRequestID int
|
||||
|
||||
// RequestIDKey is the key that holds the unique request ID in a request context.
|
||||
const RequestIDKey ctxKeyRequestID = 0
|
||||
|
||||
// RequestIDHeader is the name of the HTTP Header which contains the request id.
|
||||
// Exported so that it can be changed by developers
|
||||
var RequestIDHeader = "X-Request-Id"
|
||||
|
||||
var prefix string
|
||||
var reqid uint64
|
||||
|
||||
// A quick note on the statistics here: we're trying to calculate the chance that
|
||||
// two randomly generated base62 prefixes will collide. We use the formula from
|
||||
// http://en.wikipedia.org/wiki/Birthday_problem
|
||||
//
|
||||
// P[m, n] \approx 1 - e^{-m^2/2n}
|
||||
//
|
||||
// We ballpark an upper bound for $m$ by imagining (for whatever reason) a server
|
||||
// that restarts every second over 10 years, for $m = 86400 * 365 * 10 = 315360000$
|
||||
//
|
||||
// For a $k$ character base-62 identifier, we have $n(k) = 62^k$
|
||||
//
|
||||
// Plugging this in, we find $P[m, n(10)] \approx 5.75%$, which is good enough for
|
||||
// our purposes, and is surely more than anyone would ever need in practice -- a
|
||||
// process that is rebooted a handful of times a day for a hundred years has less
|
||||
// than a millionth of a percent chance of generating two colliding IDs.
|
||||
|
||||
func init() {
|
||||
hostname, err := os.Hostname()
|
||||
if hostname == "" || err != nil {
|
||||
hostname = "localhost"
|
||||
}
|
||||
var buf [12]byte
|
||||
var b64 string
|
||||
for len(b64) < 10 {
|
||||
rand.Read(buf[:])
|
||||
b64 = base64.StdEncoding.EncodeToString(buf[:])
|
||||
b64 = strings.NewReplacer("+", "", "/", "").Replace(b64)
|
||||
}
|
||||
|
||||
prefix = fmt.Sprintf("%s/%s", hostname, b64[0:10])
|
||||
}
|
||||
|
||||
// RequestID is a middleware that injects a request ID into the context of each
|
||||
// request. A request ID is a string of the form "host.example.com/random-0001",
|
||||
// where "random" is a base62 random string that uniquely identifies this go
|
||||
// process, and where the last number is an atomically incremented request
|
||||
// counter.
|
||||
func RequestID(next http.Handler) http.Handler {
|
||||
fn := func(w http.ResponseWriter, r *http.Request) {
|
||||
ctx := r.Context()
|
||||
requestID := r.Header.Get(RequestIDHeader)
|
||||
if requestID == "" {
|
||||
myid := atomic.AddUint64(&reqid, 1)
|
||||
requestID = fmt.Sprintf("%s-%06d", prefix, myid)
|
||||
}
|
||||
ctx = context.WithValue(ctx, RequestIDKey, requestID)
|
||||
next.ServeHTTP(w, r.WithContext(ctx))
|
||||
}
|
||||
return http.HandlerFunc(fn)
|
||||
}
|
||||
|
||||
// GetReqID returns a request ID from the given context if one is present.
|
||||
// Returns the empty string if a request ID cannot be found.
|
||||
func GetReqID(ctx context.Context) string {
|
||||
if ctx == nil {
|
||||
return ""
|
||||
}
|
||||
if reqID, ok := ctx.Value(RequestIDKey).(string); ok {
|
||||
return reqID
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
// NextRequestID generates the next request ID in the sequence.
|
||||
func NextRequestID() uint64 {
|
||||
return atomic.AddUint64(&reqid, 1)
|
||||
}
|
160
vendor/github.com/go-chi/chi/middleware/route_headers.go
generated
vendored
Normal file
160
vendor/github.com/go-chi/chi/middleware/route_headers.go
generated
vendored
Normal file
|
@ -0,0 +1,160 @@
|
|||
package middleware
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// RouteHeaders is a neat little header-based router that allows you to direct
|
||||
// the flow of a request through a middleware stack based on a request header.
|
||||
//
|
||||
// For example, lets say you'd like to setup multiple routers depending on the
|
||||
// request Host header, you could then do something as so:
|
||||
//
|
||||
// r := chi.NewRouter()
|
||||
// rSubdomain := chi.NewRouter()
|
||||
//
|
||||
// r.Use(middleware.RouteHeaders().
|
||||
// Route("Host", "example.com", middleware.New(r)).
|
||||
// Route("Host", "*.example.com", middleware.New(rSubdomain)).
|
||||
// Handler)
|
||||
//
|
||||
// r.Get("/", h)
|
||||
// rSubdomain.Get("/", h2)
|
||||
//
|
||||
//
|
||||
// Another example, imagine you want to setup multiple CORS handlers, where for
|
||||
// your origin servers you allow authorized requests, but for third-party public
|
||||
// requests, authorization is disabled.
|
||||
//
|
||||
// r := chi.NewRouter()
|
||||
//
|
||||
// r.Use(middleware.RouteHeaders().
|
||||
// Route("Origin", "https://app.skyweaver.net", cors.Handler(cors.Options{
|
||||
// AllowedOrigins: []string{"https://api.skyweaver.net"},
|
||||
// AllowedMethods: []string{"GET", "POST", "PUT", "DELETE", "OPTIONS"},
|
||||
// AllowedHeaders: []string{"Accept", "Authorization", "Content-Type"},
|
||||
// AllowCredentials: true, // <----------<<< allow credentials
|
||||
// })).
|
||||
// Route("Origin", "*", cors.Handler(cors.Options{
|
||||
// AllowedOrigins: []string{"*"},
|
||||
// AllowedMethods: []string{"GET", "POST", "PUT", "DELETE", "OPTIONS"},
|
||||
// AllowedHeaders: []string{"Accept", "Content-Type"},
|
||||
// AllowCredentials: false, // <----------<<< do not allow credentials
|
||||
// })).
|
||||
// Handler)
|
||||
//
|
||||
func RouteHeaders() HeaderRouter {
|
||||
return HeaderRouter{}
|
||||
}
|
||||
|
||||
type HeaderRouter map[string][]HeaderRoute
|
||||
|
||||
func (hr HeaderRouter) Route(header string, match string, middlewareHandler func(next http.Handler) http.Handler) HeaderRouter {
|
||||
header = strings.ToLower(header)
|
||||
k := hr[header]
|
||||
if k == nil {
|
||||
hr[header] = []HeaderRoute{}
|
||||
}
|
||||
hr[header] = append(hr[header], HeaderRoute{MatchOne: NewPattern(match), Middleware: middlewareHandler})
|
||||
return hr
|
||||
}
|
||||
|
||||
func (hr HeaderRouter) RouteAny(header string, match []string, middlewareHandler func(next http.Handler) http.Handler) HeaderRouter {
|
||||
header = strings.ToLower(header)
|
||||
k := hr[header]
|
||||
if k == nil {
|
||||
hr[header] = []HeaderRoute{}
|
||||
}
|
||||
patterns := []Pattern{}
|
||||
for _, m := range match {
|
||||
patterns = append(patterns, NewPattern(m))
|
||||
}
|
||||
hr[header] = append(hr[header], HeaderRoute{MatchAny: patterns, Middleware: middlewareHandler})
|
||||
return hr
|
||||
}
|
||||
|
||||
func (hr HeaderRouter) RouteDefault(handler func(next http.Handler) http.Handler) HeaderRouter {
|
||||
hr["*"] = []HeaderRoute{{Middleware: handler}}
|
||||
return hr
|
||||
}
|
||||
|
||||
func (hr HeaderRouter) Handler(next http.Handler) http.Handler {
|
||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
if len(hr) == 0 {
|
||||
// skip if no routes set
|
||||
next.ServeHTTP(w, r)
|
||||
}
|
||||
|
||||
// find first matching header route, and continue
|
||||
for header, matchers := range hr {
|
||||
headerValue := r.Header.Get(header)
|
||||
if headerValue == "" {
|
||||
continue
|
||||
}
|
||||
headerValue = strings.ToLower(headerValue)
|
||||
for _, matcher := range matchers {
|
||||
if matcher.IsMatch(headerValue) {
|
||||
matcher.Middleware(next).ServeHTTP(w, r)
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// if no match, check for "*" default route
|
||||
matcher, ok := hr["*"]
|
||||
if !ok || matcher[0].Middleware == nil {
|
||||
next.ServeHTTP(w, r)
|
||||
return
|
||||
}
|
||||
matcher[0].Middleware(next).ServeHTTP(w, r)
|
||||
})
|
||||
}
|
||||
|
||||
type HeaderRoute struct {
|
||||
MatchAny []Pattern
|
||||
MatchOne Pattern
|
||||
Middleware func(next http.Handler) http.Handler
|
||||
}
|
||||
|
||||
func (r HeaderRoute) IsMatch(value string) bool {
|
||||
if len(r.MatchAny) > 0 {
|
||||
for _, m := range r.MatchAny {
|
||||
if m.Match(value) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
} else if r.MatchOne.Match(value) {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
type Pattern struct {
|
||||
prefix string
|
||||
suffix string
|
||||
wildcard bool
|
||||
}
|
||||
|
||||
func NewPattern(value string) Pattern {
|
||||
p := Pattern{}
|
||||
if i := strings.IndexByte(value, '*'); i >= 0 {
|
||||
p.wildcard = true
|
||||
p.prefix = value[0:i]
|
||||
p.suffix = value[i+1:]
|
||||
} else {
|
||||
p.prefix = value
|
||||
}
|
||||
return p
|
||||
}
|
||||
|
||||
func (p Pattern) Match(v string) bool {
|
||||
if !p.wildcard {
|
||||
if p.prefix == v {
|
||||
return true
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return len(v) >= len(p.prefix+p.suffix) && strings.HasPrefix(v, p.prefix) && strings.HasSuffix(v, p.suffix)
|
||||
}
|
56
vendor/github.com/go-chi/chi/middleware/strip.go
generated
vendored
Normal file
56
vendor/github.com/go-chi/chi/middleware/strip.go
generated
vendored
Normal file
|
@ -0,0 +1,56 @@
|
|||
package middleware
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
|
||||
"github.com/go-chi/chi"
|
||||
)
|
||||
|
||||
// StripSlashes is a middleware that will match request paths with a trailing
|
||||
// slash, strip it from the path and continue routing through the mux, if a route
|
||||
// matches, then it will serve the handler.
|
||||
func StripSlashes(next http.Handler) http.Handler {
|
||||
fn := func(w http.ResponseWriter, r *http.Request) {
|
||||
var path string
|
||||
rctx := chi.RouteContext(r.Context())
|
||||
if rctx.RoutePath != "" {
|
||||
path = rctx.RoutePath
|
||||
} else {
|
||||
path = r.URL.Path
|
||||
}
|
||||
if len(path) > 1 && path[len(path)-1] == '/' {
|
||||
rctx.RoutePath = path[:len(path)-1]
|
||||
}
|
||||
next.ServeHTTP(w, r)
|
||||
}
|
||||
return http.HandlerFunc(fn)
|
||||
}
|
||||
|
||||
// RedirectSlashes is a middleware that will match request paths with a trailing
|
||||
// slash and redirect to the same path, less the trailing slash.
|
||||
//
|
||||
// NOTE: RedirectSlashes middleware is *incompatible* with http.FileServer,
|
||||
// see https://github.com/go-chi/chi/issues/343
|
||||
func RedirectSlashes(next http.Handler) http.Handler {
|
||||
fn := func(w http.ResponseWriter, r *http.Request) {
|
||||
var path string
|
||||
rctx := chi.RouteContext(r.Context())
|
||||
if rctx.RoutePath != "" {
|
||||
path = rctx.RoutePath
|
||||
} else {
|
||||
path = r.URL.Path
|
||||
}
|
||||
if len(path) > 1 && path[len(path)-1] == '/' {
|
||||
if r.URL.RawQuery != "" {
|
||||
path = fmt.Sprintf("%s?%s", path[:len(path)-1], r.URL.RawQuery)
|
||||
} else {
|
||||
path = path[:len(path)-1]
|
||||
}
|
||||
http.Redirect(w, r, path, 301)
|
||||
return
|
||||
}
|
||||
next.ServeHTTP(w, r)
|
||||
}
|
||||
return http.HandlerFunc(fn)
|
||||
}
|
63
vendor/github.com/go-chi/chi/middleware/terminal.go
generated
vendored
Normal file
63
vendor/github.com/go-chi/chi/middleware/terminal.go
generated
vendored
Normal file
|
@ -0,0 +1,63 @@
|
|||
package middleware
|
||||
|
||||
// Ported from Goji's middleware, source:
|
||||
// https://github.com/zenazn/goji/tree/master/web/middleware
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
)
|
||||
|
||||
var (
|
||||
// Normal colors
|
||||
nBlack = []byte{'\033', '[', '3', '0', 'm'}
|
||||
nRed = []byte{'\033', '[', '3', '1', 'm'}
|
||||
nGreen = []byte{'\033', '[', '3', '2', 'm'}
|
||||
nYellow = []byte{'\033', '[', '3', '3', 'm'}
|
||||
nBlue = []byte{'\033', '[', '3', '4', 'm'}
|
||||
nMagenta = []byte{'\033', '[', '3', '5', 'm'}
|
||||
nCyan = []byte{'\033', '[', '3', '6', 'm'}
|
||||
nWhite = []byte{'\033', '[', '3', '7', 'm'}
|
||||
// Bright colors
|
||||
bBlack = []byte{'\033', '[', '3', '0', ';', '1', 'm'}
|
||||
bRed = []byte{'\033', '[', '3', '1', ';', '1', 'm'}
|
||||
bGreen = []byte{'\033', '[', '3', '2', ';', '1', 'm'}
|
||||
bYellow = []byte{'\033', '[', '3', '3', ';', '1', 'm'}
|
||||
bBlue = []byte{'\033', '[', '3', '4', ';', '1', 'm'}
|
||||
bMagenta = []byte{'\033', '[', '3', '5', ';', '1', 'm'}
|
||||
bCyan = []byte{'\033', '[', '3', '6', ';', '1', 'm'}
|
||||
bWhite = []byte{'\033', '[', '3', '7', ';', '1', 'm'}
|
||||
|
||||
reset = []byte{'\033', '[', '0', 'm'}
|
||||
)
|
||||
|
||||
var IsTTY bool
|
||||
|
||||
func init() {
|
||||
// This is sort of cheating: if stdout is a character device, we assume
|
||||
// that means it's a TTY. Unfortunately, there are many non-TTY
|
||||
// character devices, but fortunately stdout is rarely set to any of
|
||||
// them.
|
||||
//
|
||||
// We could solve this properly by pulling in a dependency on
|
||||
// code.google.com/p/go.crypto/ssh/terminal, for instance, but as a
|
||||
// heuristic for whether to print in color or in black-and-white, I'd
|
||||
// really rather not.
|
||||
fi, err := os.Stdout.Stat()
|
||||
if err == nil {
|
||||
m := os.ModeDevice | os.ModeCharDevice
|
||||
IsTTY = fi.Mode()&m == m
|
||||
}
|
||||
}
|
||||
|
||||
// colorWrite
|
||||
func cW(w io.Writer, useColor bool, color []byte, s string, args ...interface{}) {
|
||||
if IsTTY && useColor {
|
||||
w.Write(color)
|
||||
}
|
||||
fmt.Fprintf(w, s, args...)
|
||||
if IsTTY && useColor {
|
||||
w.Write(reset)
|
||||
}
|
||||
}
|
132
vendor/github.com/go-chi/chi/middleware/throttle.go
generated
vendored
Normal file
132
vendor/github.com/go-chi/chi/middleware/throttle.go
generated
vendored
Normal file
|
@ -0,0 +1,132 @@
|
|||
package middleware
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"strconv"
|
||||
"time"
|
||||
)
|
||||
|
||||
const (
|
||||
errCapacityExceeded = "Server capacity exceeded."
|
||||
errTimedOut = "Timed out while waiting for a pending request to complete."
|
||||
errContextCanceled = "Context was canceled."
|
||||
)
|
||||
|
||||
var (
|
||||
defaultBacklogTimeout = time.Second * 60
|
||||
)
|
||||
|
||||
// ThrottleOpts represents a set of throttling options.
|
||||
type ThrottleOpts struct {
|
||||
Limit int
|
||||
BacklogLimit int
|
||||
BacklogTimeout time.Duration
|
||||
RetryAfterFn func(ctxDone bool) time.Duration
|
||||
}
|
||||
|
||||
// Throttle is a middleware that limits number of currently processed requests
|
||||
// at a time across all users. Note: Throttle is not a rate-limiter per user,
|
||||
// instead it just puts a ceiling on the number of currentl in-flight requests
|
||||
// being processed from the point from where the Throttle middleware is mounted.
|
||||
func Throttle(limit int) func(http.Handler) http.Handler {
|
||||
return ThrottleWithOpts(ThrottleOpts{Limit: limit, BacklogTimeout: defaultBacklogTimeout})
|
||||
}
|
||||
|
||||
// ThrottleBacklog is a middleware that limits number of currently processed
|
||||
// requests at a time and provides a backlog for holding a finite number of
|
||||
// pending requests.
|
||||
func ThrottleBacklog(limit int, backlogLimit int, backlogTimeout time.Duration) func(http.Handler) http.Handler {
|
||||
return ThrottleWithOpts(ThrottleOpts{Limit: limit, BacklogLimit: backlogLimit, BacklogTimeout: backlogTimeout})
|
||||
}
|
||||
|
||||
// ThrottleWithOpts is a middleware that limits number of currently processed requests using passed ThrottleOpts.
|
||||
func ThrottleWithOpts(opts ThrottleOpts) func(http.Handler) http.Handler {
|
||||
if opts.Limit < 1 {
|
||||
panic("chi/middleware: Throttle expects limit > 0")
|
||||
}
|
||||
|
||||
if opts.BacklogLimit < 0 {
|
||||
panic("chi/middleware: Throttle expects backlogLimit to be positive")
|
||||
}
|
||||
|
||||
t := throttler{
|
||||
tokens: make(chan token, opts.Limit),
|
||||
backlogTokens: make(chan token, opts.Limit+opts.BacklogLimit),
|
||||
backlogTimeout: opts.BacklogTimeout,
|
||||
retryAfterFn: opts.RetryAfterFn,
|
||||
}
|
||||
|
||||
// Filling tokens.
|
||||
for i := 0; i < opts.Limit+opts.BacklogLimit; i++ {
|
||||
if i < opts.Limit {
|
||||
t.tokens <- token{}
|
||||
}
|
||||
t.backlogTokens <- token{}
|
||||
}
|
||||
|
||||
return func(next http.Handler) http.Handler {
|
||||
fn := func(w http.ResponseWriter, r *http.Request) {
|
||||
ctx := r.Context()
|
||||
|
||||
select {
|
||||
|
||||
case <-ctx.Done():
|
||||
t.setRetryAfterHeaderIfNeeded(w, true)
|
||||
http.Error(w, errContextCanceled, http.StatusTooManyRequests)
|
||||
return
|
||||
|
||||
case btok := <-t.backlogTokens:
|
||||
timer := time.NewTimer(t.backlogTimeout)
|
||||
|
||||
defer func() {
|
||||
t.backlogTokens <- btok
|
||||
}()
|
||||
|
||||
select {
|
||||
case <-timer.C:
|
||||
t.setRetryAfterHeaderIfNeeded(w, false)
|
||||
http.Error(w, errTimedOut, http.StatusTooManyRequests)
|
||||
return
|
||||
case <-ctx.Done():
|
||||
timer.Stop()
|
||||
t.setRetryAfterHeaderIfNeeded(w, true)
|
||||
http.Error(w, errContextCanceled, http.StatusTooManyRequests)
|
||||
return
|
||||
case tok := <-t.tokens:
|
||||
defer func() {
|
||||
timer.Stop()
|
||||
t.tokens <- tok
|
||||
}()
|
||||
next.ServeHTTP(w, r)
|
||||
}
|
||||
return
|
||||
|
||||
default:
|
||||
t.setRetryAfterHeaderIfNeeded(w, false)
|
||||
http.Error(w, errCapacityExceeded, http.StatusTooManyRequests)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
return http.HandlerFunc(fn)
|
||||
}
|
||||
}
|
||||
|
||||
// token represents a request that is being processed.
|
||||
type token struct{}
|
||||
|
||||
// throttler limits number of currently processed requests at a time.
|
||||
type throttler struct {
|
||||
tokens chan token
|
||||
backlogTokens chan token
|
||||
backlogTimeout time.Duration
|
||||
retryAfterFn func(ctxDone bool) time.Duration
|
||||
}
|
||||
|
||||
// setRetryAfterHeaderIfNeeded sets Retry-After HTTP header if corresponding retryAfterFn option of throttler is initialized.
|
||||
func (t throttler) setRetryAfterHeaderIfNeeded(w http.ResponseWriter, ctxDone bool) {
|
||||
if t.retryAfterFn == nil {
|
||||
return
|
||||
}
|
||||
w.Header().Set("Retry-After", strconv.Itoa(int(t.retryAfterFn(ctxDone).Seconds())))
|
||||
}
|
49
vendor/github.com/go-chi/chi/middleware/timeout.go
generated
vendored
Normal file
49
vendor/github.com/go-chi/chi/middleware/timeout.go
generated
vendored
Normal file
|
@ -0,0 +1,49 @@
|
|||
package middleware
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net/http"
|
||||
"time"
|
||||
)
|
||||
|
||||
// Timeout is a middleware that cancels ctx after a given timeout and return
|
||||
// a 504 Gateway Timeout error to the client.
|
||||
//
|
||||
// It's required that you select the ctx.Done() channel to check for the signal
|
||||
// if the context has reached its deadline and return, otherwise the timeout
|
||||
// signal will be just ignored.
|
||||
//
|
||||
// ie. a route/handler may look like:
|
||||
//
|
||||
// r.Get("/long", func(w http.ResponseWriter, r *http.Request) {
|
||||
// ctx := r.Context()
|
||||
// processTime := time.Duration(rand.Intn(4)+1) * time.Second
|
||||
//
|
||||
// select {
|
||||
// case <-ctx.Done():
|
||||
// return
|
||||
//
|
||||
// case <-time.After(processTime):
|
||||
// // The above channel simulates some hard work.
|
||||
// }
|
||||
//
|
||||
// w.Write([]byte("done"))
|
||||
// })
|
||||
//
|
||||
func Timeout(timeout time.Duration) func(next http.Handler) http.Handler {
|
||||
return func(next http.Handler) http.Handler {
|
||||
fn := func(w http.ResponseWriter, r *http.Request) {
|
||||
ctx, cancel := context.WithTimeout(r.Context(), timeout)
|
||||
defer func() {
|
||||
cancel()
|
||||
if ctx.Err() == context.DeadlineExceeded {
|
||||
w.WriteHeader(http.StatusGatewayTimeout)
|
||||
}
|
||||
}()
|
||||
|
||||
r = r.WithContext(ctx)
|
||||
next.ServeHTTP(w, r)
|
||||
}
|
||||
return http.HandlerFunc(fn)
|
||||
}
|
||||
}
|
72
vendor/github.com/go-chi/chi/middleware/url_format.go
generated
vendored
Normal file
72
vendor/github.com/go-chi/chi/middleware/url_format.go
generated
vendored
Normal file
|
@ -0,0 +1,72 @@
|
|||
package middleware
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net/http"
|
||||
"strings"
|
||||
|
||||
"github.com/go-chi/chi"
|
||||
)
|
||||
|
||||
var (
|
||||
// URLFormatCtxKey is the context.Context key to store the URL format data
|
||||
// for a request.
|
||||
URLFormatCtxKey = &contextKey{"URLFormat"}
|
||||
)
|
||||
|
||||
// URLFormat is a middleware that parses the url extension from a request path and stores it
|
||||
// on the context as a string under the key `middleware.URLFormatCtxKey`. The middleware will
|
||||
// trim the suffix from the routing path and continue routing.
|
||||
//
|
||||
// Routers should not include a url parameter for the suffix when using this middleware.
|
||||
//
|
||||
// Sample usage.. for url paths: `/articles/1`, `/articles/1.json` and `/articles/1.xml`
|
||||
//
|
||||
// func routes() http.Handler {
|
||||
// r := chi.NewRouter()
|
||||
// r.Use(middleware.URLFormat)
|
||||
//
|
||||
// r.Get("/articles/{id}", ListArticles)
|
||||
//
|
||||
// return r
|
||||
// }
|
||||
//
|
||||
// func ListArticles(w http.ResponseWriter, r *http.Request) {
|
||||
// urlFormat, _ := r.Context().Value(middleware.URLFormatCtxKey).(string)
|
||||
//
|
||||
// switch urlFormat {
|
||||
// case "json":
|
||||
// render.JSON(w, r, articles)
|
||||
// case "xml:"
|
||||
// render.XML(w, r, articles)
|
||||
// default:
|
||||
// render.JSON(w, r, articles)
|
||||
// }
|
||||
// }
|
||||
//
|
||||
func URLFormat(next http.Handler) http.Handler {
|
||||
fn := func(w http.ResponseWriter, r *http.Request) {
|
||||
ctx := r.Context()
|
||||
|
||||
var format string
|
||||
path := r.URL.Path
|
||||
|
||||
if strings.Index(path, ".") > 0 {
|
||||
base := strings.LastIndex(path, "/")
|
||||
idx := strings.Index(path[base:], ".")
|
||||
|
||||
if idx > 0 {
|
||||
idx += base
|
||||
format = path[idx+1:]
|
||||
|
||||
rctx := chi.RouteContext(r.Context())
|
||||
rctx.RoutePath = path[:idx]
|
||||
}
|
||||
}
|
||||
|
||||
r = r.WithContext(context.WithValue(ctx, URLFormatCtxKey, format))
|
||||
|
||||
next.ServeHTTP(w, r)
|
||||
}
|
||||
return http.HandlerFunc(fn)
|
||||
}
|
17
vendor/github.com/go-chi/chi/middleware/value.go
generated
vendored
Normal file
17
vendor/github.com/go-chi/chi/middleware/value.go
generated
vendored
Normal file
|
@ -0,0 +1,17 @@
|
|||
package middleware
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
// WithValue is a middleware that sets a given key/value in a context chain.
|
||||
func WithValue(key interface{}, val interface{}) func(next http.Handler) http.Handler {
|
||||
return func(next http.Handler) http.Handler {
|
||||
fn := func(w http.ResponseWriter, r *http.Request) {
|
||||
r = r.WithContext(context.WithValue(r.Context(), key, val))
|
||||
next.ServeHTTP(w, r)
|
||||
}
|
||||
return http.HandlerFunc(fn)
|
||||
}
|
||||
}
|
180
vendor/github.com/go-chi/chi/middleware/wrap_writer.go
generated
vendored
Normal file
180
vendor/github.com/go-chi/chi/middleware/wrap_writer.go
generated
vendored
Normal file
|
@ -0,0 +1,180 @@
|
|||
package middleware
|
||||
|
||||
// The original work was derived from Goji's middleware, source:
|
||||
// https://github.com/zenazn/goji/tree/master/web/middleware
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"io"
|
||||
"net"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
// NewWrapResponseWriter wraps an http.ResponseWriter, returning a proxy that allows you to
|
||||
// hook into various parts of the response process.
|
||||
func NewWrapResponseWriter(w http.ResponseWriter, protoMajor int) WrapResponseWriter {
|
||||
_, fl := w.(http.Flusher)
|
||||
|
||||
bw := basicWriter{ResponseWriter: w}
|
||||
|
||||
if protoMajor == 2 {
|
||||
_, ps := w.(http.Pusher)
|
||||
if fl && ps {
|
||||
return &http2FancyWriter{bw}
|
||||
}
|
||||
} else {
|
||||
_, hj := w.(http.Hijacker)
|
||||
_, rf := w.(io.ReaderFrom)
|
||||
if fl && hj && rf {
|
||||
return &httpFancyWriter{bw}
|
||||
}
|
||||
}
|
||||
if fl {
|
||||
return &flushWriter{bw}
|
||||
}
|
||||
|
||||
return &bw
|
||||
}
|
||||
|
||||
// WrapResponseWriter is a proxy around an http.ResponseWriter that allows you to hook
|
||||
// into various parts of the response process.
|
||||
type WrapResponseWriter interface {
|
||||
http.ResponseWriter
|
||||
// Status returns the HTTP status of the request, or 0 if one has not
|
||||
// yet been sent.
|
||||
Status() int
|
||||
// BytesWritten returns the total number of bytes sent to the client.
|
||||
BytesWritten() int
|
||||
// Tee causes the response body to be written to the given io.Writer in
|
||||
// addition to proxying the writes through. Only one io.Writer can be
|
||||
// tee'd to at once: setting a second one will overwrite the first.
|
||||
// Writes will be sent to the proxy before being written to this
|
||||
// io.Writer. It is illegal for the tee'd writer to be modified
|
||||
// concurrently with writes.
|
||||
Tee(io.Writer)
|
||||
// Unwrap returns the original proxied target.
|
||||
Unwrap() http.ResponseWriter
|
||||
}
|
||||
|
||||
// basicWriter wraps a http.ResponseWriter that implements the minimal
|
||||
// http.ResponseWriter interface.
|
||||
type basicWriter struct {
|
||||
http.ResponseWriter
|
||||
wroteHeader bool
|
||||
code int
|
||||
bytes int
|
||||
tee io.Writer
|
||||
}
|
||||
|
||||
func (b *basicWriter) WriteHeader(code int) {
|
||||
if !b.wroteHeader {
|
||||
b.code = code
|
||||
b.wroteHeader = true
|
||||
b.ResponseWriter.WriteHeader(code)
|
||||
}
|
||||
}
|
||||
|
||||
func (b *basicWriter) Write(buf []byte) (int, error) {
|
||||
b.maybeWriteHeader()
|
||||
n, err := b.ResponseWriter.Write(buf)
|
||||
if b.tee != nil {
|
||||
_, err2 := b.tee.Write(buf[:n])
|
||||
// Prefer errors generated by the proxied writer.
|
||||
if err == nil {
|
||||
err = err2
|
||||
}
|
||||
}
|
||||
b.bytes += n
|
||||
return n, err
|
||||
}
|
||||
|
||||
func (b *basicWriter) maybeWriteHeader() {
|
||||
if !b.wroteHeader {
|
||||
b.WriteHeader(http.StatusOK)
|
||||
}
|
||||
}
|
||||
|
||||
func (b *basicWriter) Status() int {
|
||||
return b.code
|
||||
}
|
||||
|
||||
func (b *basicWriter) BytesWritten() int {
|
||||
return b.bytes
|
||||
}
|
||||
|
||||
func (b *basicWriter) Tee(w io.Writer) {
|
||||
b.tee = w
|
||||
}
|
||||
|
||||
func (b *basicWriter) Unwrap() http.ResponseWriter {
|
||||
return b.ResponseWriter
|
||||
}
|
||||
|
||||
type flushWriter struct {
|
||||
basicWriter
|
||||
}
|
||||
|
||||
func (f *flushWriter) Flush() {
|
||||
f.wroteHeader = true
|
||||
fl := f.basicWriter.ResponseWriter.(http.Flusher)
|
||||
fl.Flush()
|
||||
}
|
||||
|
||||
var _ http.Flusher = &flushWriter{}
|
||||
|
||||
// httpFancyWriter is a HTTP writer that additionally satisfies
|
||||
// http.Flusher, http.Hijacker, and io.ReaderFrom. It exists for the common case
|
||||
// of wrapping the http.ResponseWriter that package http gives you, in order to
|
||||
// make the proxied object support the full method set of the proxied object.
|
||||
type httpFancyWriter struct {
|
||||
basicWriter
|
||||
}
|
||||
|
||||
func (f *httpFancyWriter) Flush() {
|
||||
f.wroteHeader = true
|
||||
fl := f.basicWriter.ResponseWriter.(http.Flusher)
|
||||
fl.Flush()
|
||||
}
|
||||
|
||||
func (f *httpFancyWriter) Hijack() (net.Conn, *bufio.ReadWriter, error) {
|
||||
hj := f.basicWriter.ResponseWriter.(http.Hijacker)
|
||||
return hj.Hijack()
|
||||
}
|
||||
|
||||
func (f *http2FancyWriter) Push(target string, opts *http.PushOptions) error {
|
||||
return f.basicWriter.ResponseWriter.(http.Pusher).Push(target, opts)
|
||||
}
|
||||
|
||||
func (f *httpFancyWriter) ReadFrom(r io.Reader) (int64, error) {
|
||||
if f.basicWriter.tee != nil {
|
||||
n, err := io.Copy(&f.basicWriter, r)
|
||||
f.basicWriter.bytes += int(n)
|
||||
return n, err
|
||||
}
|
||||
rf := f.basicWriter.ResponseWriter.(io.ReaderFrom)
|
||||
f.basicWriter.maybeWriteHeader()
|
||||
n, err := rf.ReadFrom(r)
|
||||
f.basicWriter.bytes += int(n)
|
||||
return n, err
|
||||
}
|
||||
|
||||
var _ http.Flusher = &httpFancyWriter{}
|
||||
var _ http.Hijacker = &httpFancyWriter{}
|
||||
var _ http.Pusher = &http2FancyWriter{}
|
||||
var _ io.ReaderFrom = &httpFancyWriter{}
|
||||
|
||||
// http2FancyWriter is a HTTP2 writer that additionally satisfies
|
||||
// http.Flusher, and io.ReaderFrom. It exists for the common case
|
||||
// of wrapping the http.ResponseWriter that package http gives you, in order to
|
||||
// make the proxied object support the full method set of the proxied object.
|
||||
type http2FancyWriter struct {
|
||||
basicWriter
|
||||
}
|
||||
|
||||
func (f *http2FancyWriter) Flush() {
|
||||
f.wroteHeader = true
|
||||
fl := f.basicWriter.ResponseWriter.(http.Flusher)
|
||||
fl.Flush()
|
||||
}
|
||||
|
||||
var _ http.Flusher = &http2FancyWriter{}
|
Loading…
Add table
Add a link
Reference in a new issue