diff --git a/go.mod b/go.mod index a0b279d..30fe67c 100644 --- a/go.mod +++ b/go.mod @@ -5,6 +5,7 @@ go 1.15 require ( github.com/golangci/golangci-lint v1.43.0 github.com/goreleaser/goreleaser v1.3.0 + github.com/justinas/alice v1.2.0 github.com/rs/zerolog v1.20.0 github.com/stretchr/testify v1.7.0 ) diff --git a/go.sum b/go.sum index 36130bc..2cd9fc4 100644 --- a/go.sum +++ b/go.sum @@ -785,6 +785,8 @@ github.com/juju/ratelimit v1.0.1/go.mod h1:qapgC/Gy+xNh9UxzV13HGGl/6UXNN+ct+vwSg github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= github.com/julz/importas v0.0.0-20210419104244-841f0c0fe66d h1:XeSMXURZPtUffuWAaq90o6kLgZdgu+QA8wk4MPC8ikI= github.com/julz/importas v0.0.0-20210419104244-841f0c0fe66d/go.mod h1:oSFU2R4XK/P7kNBrnL/FEQlDGN1/6WoxXEjSSXO0DV0= +github.com/justinas/alice v1.2.0 h1:+MHSA/vccVCF4Uq37S42jwlkvI2Xzl7zTPCN5BnZNVo= +github.com/justinas/alice v1.2.0/go.mod h1:fN5HRH/reO/zrUflLfTN43t3vXvKzvZIENsNEe7i7qA= github.com/k0kubun/colorstring v0.0.0-20150214042306-9440f1994b88/go.mod h1:3w7q1U84EfirKl04SVQ/s7nPm1ZPhiXd34z40TNz36k= github.com/kevinburke/ssh_config v0.0.0-20190725054713-01f96b0aa0cd/go.mod h1:CT57kijsi8u/K/BOFA39wgDQJ9CxiF4nAY/ojJ6r6mM= github.com/kevinburke/ssh_config v0.0.0-20201106050909-4977a11b4351/go.mod h1:CT57kijsi8u/K/BOFA39wgDQJ9CxiF4nAY/ojJ6r6mM= diff --git a/logginghandler.go b/logginghandler.go index 8d8ff59..fe79a01 100644 --- a/logginghandler.go +++ b/logginghandler.go @@ -4,7 +4,9 @@ package logginghandler import ( "net/http" + "time" + "github.com/justinas/alice" "github.com/rs/zerolog" "github.com/rs/zerolog/hlog" ) @@ -24,23 +26,26 @@ func Logger(r *http.Request) zerolog.Logger { return *hlog.FromRequest(r) } -// Handler sets up all the logging. -func Handler(logger zerolog.Logger) func(http.Handler) http.Handler { +func Handler(log zerolog.Logger) func(http.Handler) http.Handler { return func(next http.Handler) http.Handler { - return hlog.NewHandler(logger)( - hlog.RemoteAddrHandler("remote")( - hlog.UserAgentHandler("user-agent")( - hlog.RefererHandler("referer")( - hlog.MethodHandler("method")( - hlog.RequestIDHandler("uuid", "X-Request-ID")( - hlog.URLHandler("request-url")( - next, - ), - ), - ), - ), - ), - ), - ) + chain := alice.New( + hlog.NewHandler(log), + hlog.AccessHandler(func(r *http.Request, status, size int, duration time.Duration) { + hlog.FromRequest(r).Info(). + Str("method", r.Method). + Str("proto", r.Proto). + Stringer("request-url", r.URL). + Int("status", status). + Int("size", size). + Dur("duration", duration). + Msg("") + }), + hlog.RemoteAddrHandler("remote"), + hlog.UserAgentHandler("user-agent"), + hlog.RefererHandler("referer"), + hlog.RequestIDHandler("uuid", "X-Request-ID"), + ).Then(next) + + return chain } } diff --git a/vendor/github.com/justinas/alice/.travis.yml b/vendor/github.com/justinas/alice/.travis.yml new file mode 100644 index 0000000..f2800ce --- /dev/null +++ b/vendor/github.com/justinas/alice/.travis.yml @@ -0,0 +1,19 @@ +language: go + +matrix: + include: + - go: 1.2.x + - go: 1.3.x + - go: 1.4.x + - go: 1.5.x + - go: 1.6.x + - go: 1.7.x + - go: 1.8.x + - go: 1.9.x + - go: 1.10.x + - go: 1.11.x + - go: 1.12.x + - go: 1.13.x + - go: tip + allow_failures: + - go: tip diff --git a/vendor/github.com/justinas/alice/LICENSE b/vendor/github.com/justinas/alice/LICENSE new file mode 100644 index 0000000..0d0d352 --- /dev/null +++ b/vendor/github.com/justinas/alice/LICENSE @@ -0,0 +1,20 @@ +The MIT License (MIT) + +Copyright (c) 2014 Justinas Stankevicius + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of +the Software, and to permit persons to whom the Software is furnished to do so, +subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS +FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR +COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER +IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/vendor/github.com/justinas/alice/README.md b/vendor/github.com/justinas/alice/README.md new file mode 100644 index 0000000..e4f9157 --- /dev/null +++ b/vendor/github.com/justinas/alice/README.md @@ -0,0 +1,98 @@ +# Alice + +[![GoDoc](https://godoc.org/github.com/golang/gddo?status.svg)](http://godoc.org/github.com/justinas/alice) +[![Build Status](https://travis-ci.org/justinas/alice.svg?branch=master)](https://travis-ci.org/justinas/alice) +[![Coverage](http://gocover.io/_badge/github.com/justinas/alice)](http://gocover.io/github.com/justinas/alice) + +Alice provides a convenient way to chain +your HTTP middleware functions and the app handler. + +In short, it transforms + +```go +Middleware1(Middleware2(Middleware3(App))) +``` + +to + +```go +alice.New(Middleware1, Middleware2, Middleware3).Then(App) +``` + +### Why? + +None of the other middleware chaining solutions +behaves exactly like Alice. +Alice is as minimal as it gets: +in essence, it's just a for loop that does the wrapping for you. + +Check out [this blog post](http://justinas.org/alice-painless-middleware-chaining-for-go/) +for explanation how Alice is different from other chaining solutions. + +### Usage + +Your middleware constructors should have the form of + +```go +func (http.Handler) http.Handler +``` + +Some middleware provide this out of the box. +For ones that don't, it's trivial to write one yourself. + +```go +func myStripPrefix(h http.Handler) http.Handler { + return http.StripPrefix("/old", h) +} +``` + +This complete example shows the full power of Alice. + +```go +package main + +import ( + "net/http" + "time" + + "github.com/throttled/throttled" + "github.com/justinas/alice" + "github.com/justinas/nosurf" +) + +func timeoutHandler(h http.Handler) http.Handler { + return http.TimeoutHandler(h, 1*time.Second, "timed out") +} + +func myApp(w http.ResponseWriter, r *http.Request) { + w.Write([]byte("Hello world!")) +} + +func main() { + th := throttled.Interval(throttled.PerSec(10), 1, &throttled.VaryBy{Path: true}, 50) + myHandler := http.HandlerFunc(myApp) + + chain := alice.New(th.Throttle, timeoutHandler, nosurf.NewPure).Then(myHandler) + http.ListenAndServe(":8000", chain) +} +``` + +Here, the request will pass [throttled](https://github.com/PuerkitoBio/throttled) first, +then an http.TimeoutHandler we've set up, +then [nosurf](https://github.com/justinas/nosurf) +and will finally reach our handler. + +Note that Alice makes **no guarantees** for +how one or another piece of middleware will behave. +Once it passes the execution to the outer layer of middleware, +it has no saying in whether middleware will execute the inner handlers. +This is intentional behavior. + +Alice works with Go 1.0 and higher. + +### Contributing + +0. Find an issue that bugs you / open a new one. +1. Discuss. +2. Branch off, commit, test. +3. Make a pull request / attach the commits to the issue. diff --git a/vendor/github.com/justinas/alice/chain.go b/vendor/github.com/justinas/alice/chain.go new file mode 100644 index 0000000..da0e2b5 --- /dev/null +++ b/vendor/github.com/justinas/alice/chain.go @@ -0,0 +1,112 @@ +// Package alice provides a convenient way to chain http handlers. +package alice + +import "net/http" + +// A constructor for a piece of middleware. +// Some middleware use this constructor out of the box, +// so in most cases you can just pass somepackage.New +type Constructor func(http.Handler) http.Handler + +// Chain acts as a list of http.Handler constructors. +// Chain is effectively immutable: +// once created, it will always hold +// the same set of constructors in the same order. +type Chain struct { + constructors []Constructor +} + +// New creates a new chain, +// memorizing the given list of middleware constructors. +// New serves no other function, +// constructors are only called upon a call to Then(). +func New(constructors ...Constructor) Chain { + return Chain{append(([]Constructor)(nil), constructors...)} +} + +// Then chains the middleware and returns the final http.Handler. +// New(m1, m2, m3).Then(h) +// is equivalent to: +// m1(m2(m3(h))) +// When the request comes in, it will be passed to m1, then m2, then m3 +// and finally, the given handler +// (assuming every middleware calls the following one). +// +// A chain can be safely reused by calling Then() several times. +// stdStack := alice.New(ratelimitHandler, csrfHandler) +// indexPipe = stdStack.Then(indexHandler) +// authPipe = stdStack.Then(authHandler) +// Note that constructors are called on every call to Then() +// and thus several instances of the same middleware will be created +// when a chain is reused in this way. +// For proper middleware, this should cause no problems. +// +// Then() treats nil as http.DefaultServeMux. +func (c Chain) Then(h http.Handler) http.Handler { + if h == nil { + h = http.DefaultServeMux + } + + for i := range c.constructors { + h = c.constructors[len(c.constructors)-1-i](h) + } + + return h +} + +// ThenFunc works identically to Then, but takes +// a HandlerFunc instead of a Handler. +// +// The following two statements are equivalent: +// c.Then(http.HandlerFunc(fn)) +// c.ThenFunc(fn) +// +// ThenFunc provides all the guarantees of Then. +func (c Chain) ThenFunc(fn http.HandlerFunc) http.Handler { + if fn == nil { + return c.Then(nil) + } + return c.Then(fn) +} + +// Append extends a chain, adding the specified constructors +// as the last ones in the request flow. +// +// Append returns a new chain, leaving the original one untouched. +// +// stdChain := alice.New(m1, m2) +// extChain := stdChain.Append(m3, m4) +// // requests in stdChain go m1 -> m2 +// // requests in extChain go m1 -> m2 -> m3 -> m4 +func (c Chain) Append(constructors ...Constructor) Chain { + newCons := make([]Constructor, 0, len(c.constructors)+len(constructors)) + newCons = append(newCons, c.constructors...) + newCons = append(newCons, constructors...) + + return Chain{newCons} +} + +// Extend extends a chain by adding the specified chain +// as the last one in the request flow. +// +// Extend returns a new chain, leaving the original one untouched. +// +// stdChain := alice.New(m1, m2) +// ext1Chain := alice.New(m3, m4) +// ext2Chain := stdChain.Extend(ext1Chain) +// // requests in stdChain go m1 -> m2 +// // requests in ext1Chain go m3 -> m4 +// // requests in ext2Chain go m1 -> m2 -> m3 -> m4 +// +// Another example: +// aHtmlAfterNosurf := alice.New(m2) +// aHtml := alice.New(m1, func(h http.Handler) http.Handler { +// csrf := nosurf.New(h) +// csrf.SetFailureHandler(aHtmlAfterNosurf.ThenFunc(csrfFail)) +// return csrf +// }).Extend(aHtmlAfterNosurf) +// // requests to aHtml hitting nosurfs success handler go m1 -> nosurf -> m2 -> target-handler +// // requests to aHtml hitting nosurfs failure handler go m1 -> nosurf -> m2 -> csrfFail +func (c Chain) Extend(chain Chain) Chain { + return c.Append(chain.constructors...) +} diff --git a/vendor/github.com/justinas/alice/go.mod b/vendor/github.com/justinas/alice/go.mod new file mode 100644 index 0000000..b630d63 --- /dev/null +++ b/vendor/github.com/justinas/alice/go.mod @@ -0,0 +1,3 @@ +module github.com/justinas/alice + +go 1.12 diff --git a/vendor/modules.txt b/vendor/modules.txt index c409c83..2485345 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -681,6 +681,9 @@ github.com/jirfag/go-printf-func-name/pkg/analyzer github.com/jmespath/go-jmespath # github.com/julz/importas v0.0.0-20210419104244-841f0c0fe66d github.com/julz/importas +# github.com/justinas/alice v1.2.0 +## explicit +github.com/justinas/alice # github.com/kevinburke/ssh_config v1.1.0 github.com/kevinburke/ssh_config # github.com/kisielk/errcheck v1.6.0