Bump Gorilla Handlers to `v1.5.1`.
Signed-off-by: olegburov <oleg.burov@outlook.com>master
							parent
							
								
									f4506b517a
								
							
						
					
					
						commit
						03aaf6ab51
					
				
							
								
								
									
										2
									
								
								go.mod
								
								
								
								
							
							
						
						
									
										2
									
								
								go.mod
								
								
								
								
							|  | @ -20,7 +20,7 @@ require ( | |||
| 	github.com/docker/go-metrics v0.0.1 | ||||
| 	github.com/docker/libtrust v0.0.0-20150114040149-fa567046d9b1 | ||||
| 	github.com/gomodule/redigo v1.8.2 | ||||
| 	github.com/gorilla/handlers v0.0.0-20150720190736-60c7bfde3e33 | ||||
| 	github.com/gorilla/handlers v1.5.1 | ||||
| 	github.com/gorilla/mux v1.8.0 | ||||
| 	github.com/inconshreveable/mousetrap v1.0.0 // indirect | ||||
| 	github.com/kr/pretty v0.1.0 // indirect | ||||
|  |  | |||
							
								
								
									
										6
									
								
								go.sum
								
								
								
								
							
							
						
						
									
										6
									
								
								go.sum
								
								
								
								
							|  | @ -41,6 +41,8 @@ github.com/docker/go-metrics v0.0.1 h1:AgB/0SvBxihN0X8OR4SjsblXkbMvalQ8cjmtKQ2rQ | |||
| github.com/docker/go-metrics v0.0.1/go.mod h1:cG1hvH2utMXtqgqqYE9plW6lDxS3/5ayHzueweSI3Vw= | ||||
| github.com/docker/libtrust v0.0.0-20150114040149-fa567046d9b1 h1:ZClxb8laGDf5arXfYcAtECDFgAgHklGI8CxgjHnXKJ4= | ||||
| github.com/docker/libtrust v0.0.0-20150114040149-fa567046d9b1/go.mod h1:cyGadeNEkKy96OOhEzfZl+yxihPEzKnqJwvfuSUqbZE= | ||||
| github.com/felixge/httpsnoop v1.0.1 h1:lvB5Jl89CsZtGIWuTcDM1E/vkVs49/Ml7JJe07l8SPQ= | ||||
| github.com/felixge/httpsnoop v1.0.1/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= | ||||
| github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= | ||||
| github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE= | ||||
| github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk= | ||||
|  | @ -55,8 +57,8 @@ github.com/gomodule/redigo v1.8.2 h1:H5XSIre1MB5NbPYFp+i1NBbb5qN1W8Y8YAQoAYbkm8k | |||
| github.com/gomodule/redigo v1.8.2/go.mod h1:P9dn9mFrCBvWhGE1wpxx6fgq7BAeLBk+UUUzlpkBYO0= | ||||
| github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= | ||||
| github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= | ||||
| github.com/gorilla/handlers v0.0.0-20150720190736-60c7bfde3e33 h1:893HsJqtxp9z1SF76gg6hY70hRY1wVlTSnC/h1yUDCo= | ||||
| github.com/gorilla/handlers v0.0.0-20150720190736-60c7bfde3e33/go.mod h1:Qkdc/uu4tH4g6mTK6auzZ766c4CA0Ng8+o/OAirnOIQ= | ||||
| github.com/gorilla/handlers v1.5.1 h1:9lRY6j8DEeeBT10CvO9hGW0gmky0BprnvDI5vfhUHH4= | ||||
| github.com/gorilla/handlers v1.5.1/go.mod h1:t8XrUpc4KVXb7HGyJ4/cEnwQiaxrX/hz1Zv/4g96P1Q= | ||||
| github.com/gorilla/mux v1.8.0 h1:i40aqfkR1h2SlN9hojwV5ZA91wcXFOvkdNIeFDP5koI= | ||||
| github.com/gorilla/mux v1.8.0/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So= | ||||
| github.com/inconshreveable/mousetrap v1.0.0 h1:Z8tu5sraLXCXIcARxBp/8cbvlwVa7Z1NHg9XEKhtSvM= | ||||
|  |  | |||
|  | @ -0,0 +1,6 @@ | |||
| language: go | ||||
| 
 | ||||
| go: | ||||
|   - 1.6 | ||||
|   - 1.7 | ||||
|   - 1.8 | ||||
|  | @ -0,0 +1,19 @@ | |||
| Copyright (c) 2016 Felix Geisendörfer (felix@debuggable.com) | ||||
| 
 | ||||
|  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. | ||||
|  | @ -0,0 +1,10 @@ | |||
| .PHONY: ci generate clean | ||||
| 
 | ||||
| ci: clean generate | ||||
| 	go test -v ./... | ||||
| 
 | ||||
| generate: | ||||
| 	go generate . | ||||
| 
 | ||||
| clean: | ||||
| 	rm -rf *_generated*.go | ||||
|  | @ -0,0 +1,94 @@ | |||
| # httpsnoop | ||||
| 
 | ||||
| Package httpsnoop provides an easy way to capture http related metrics (i.e. | ||||
| response time, bytes written, and http status code) from your application's | ||||
| http.Handlers. | ||||
| 
 | ||||
| Doing this requires non-trivial wrapping of the http.ResponseWriter interface, | ||||
| which is also exposed for users interested in a more low-level API. | ||||
| 
 | ||||
| [](https://godoc.org/github.com/felixge/httpsnoop) | ||||
| [](https://travis-ci.org/felixge/httpsnoop) | ||||
| 
 | ||||
| ## Usage Example | ||||
| 
 | ||||
| ```go | ||||
| // myH is your app's http handler, perhaps a http.ServeMux or similar. | ||||
| var myH http.Handler | ||||
| // wrappedH wraps myH in order to log every request. | ||||
| wrappedH := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { | ||||
| 	m := httpsnoop.CaptureMetrics(myH, w, r) | ||||
| 	log.Printf( | ||||
| 		"%s %s (code=%d dt=%s written=%d)", | ||||
| 		r.Method, | ||||
| 		r.URL, | ||||
| 		m.Code, | ||||
| 		m.Duration, | ||||
| 		m.Written, | ||||
| 	) | ||||
| }) | ||||
| http.ListenAndServe(":8080", wrappedH) | ||||
| ``` | ||||
| 
 | ||||
| ## Why this package exists | ||||
| 
 | ||||
| Instrumenting an application's http.Handler is surprisingly difficult. | ||||
| 
 | ||||
| However if you google for e.g. "capture ResponseWriter status code" you'll find | ||||
| lots of advise and code examples that suggest it to be a fairly trivial | ||||
| undertaking. Unfortunately everything I've seen so far has a high chance of | ||||
| breaking your application. | ||||
| 
 | ||||
| The main problem is that a `http.ResponseWriter` often implements additional | ||||
| interfaces such as `http.Flusher`, `http.CloseNotifier`, `http.Hijacker`, `http.Pusher`, and | ||||
| `io.ReaderFrom`. So the naive approach of just wrapping `http.ResponseWriter` | ||||
| in your own struct that also implements the `http.ResponseWriter` interface | ||||
| will hide the additional interfaces mentioned above. This has a high change of | ||||
| introducing subtle bugs into any non-trivial application. | ||||
| 
 | ||||
| Another approach I've seen people take is to return a struct that implements | ||||
| all of the interfaces above. However, that's also problematic, because it's | ||||
| difficult to fake some of these interfaces behaviors when the underlying | ||||
| `http.ResponseWriter` doesn't have an implementation. It's also dangerous, | ||||
| because an application may choose to operate differently, merely because it | ||||
| detects the presence of these additional interfaces. | ||||
| 
 | ||||
| This package solves this problem by checking which additional interfaces a | ||||
| `http.ResponseWriter` implements, returning a wrapped version implementing the | ||||
| exact same set of interfaces. | ||||
| 
 | ||||
| Additionally this package properly handles edge cases such as `WriteHeader` not | ||||
| being called, or called more than once, as well as concurrent calls to | ||||
| `http.ResponseWriter` methods, and even calls happening after the wrapped | ||||
| `ServeHTTP` has already returned. | ||||
| 
 | ||||
| Unfortunately this package is not perfect either. It's possible that it is | ||||
| still missing some interfaces provided by the go core (let me know if you find | ||||
| one), and it won't work for applications adding their own interfaces into the | ||||
| mix. | ||||
| 
 | ||||
| However, hopefully the explanation above has sufficiently scared you of rolling | ||||
| your own solution to this problem. httpsnoop may still break your application, | ||||
| but at least it tries to avoid it as much as possible. | ||||
| 
 | ||||
| Anyway, the real problem here is that smuggling additional interfaces inside | ||||
| `http.ResponseWriter` is a problematic design choice, but it probably goes as | ||||
| deep as the Go language specification itself. But that's okay, I still prefer | ||||
| Go over the alternatives ;). | ||||
| 
 | ||||
| ## Performance | ||||
| 
 | ||||
| ``` | ||||
| BenchmarkBaseline-8      	   20000	     94912 ns/op | ||||
| BenchmarkCaptureMetrics-8	   20000	     95461 ns/op | ||||
| ``` | ||||
| 
 | ||||
| As you can see, using `CaptureMetrics` on a vanilla http.Handler introduces an | ||||
| overhead of ~500 ns per http request on my machine. However, the margin of | ||||
| error appears to be larger than that, therefor it should be reasonable to | ||||
| assume that the overhead introduced by `CaptureMetrics` is absolutely | ||||
| negligible. | ||||
| 
 | ||||
| ## License | ||||
| 
 | ||||
| MIT | ||||
|  | @ -0,0 +1,84 @@ | |||
| package httpsnoop | ||||
| 
 | ||||
| import ( | ||||
| 	"io" | ||||
| 	"net/http" | ||||
| 	"sync" | ||||
| 	"time" | ||||
| ) | ||||
| 
 | ||||
| // Metrics holds metrics captured from CaptureMetrics.
 | ||||
| type Metrics struct { | ||||
| 	// Code is the first http response code passed to the WriteHeader func of
 | ||||
| 	// the ResponseWriter. If no such call is made, a default code of 200 is
 | ||||
| 	// assumed instead.
 | ||||
| 	Code int | ||||
| 	// Duration is the time it took to execute the handler.
 | ||||
| 	Duration time.Duration | ||||
| 	// Written is the number of bytes successfully written by the Write or
 | ||||
| 	// ReadFrom function of the ResponseWriter. ResponseWriters may also write
 | ||||
| 	// data to their underlaying connection directly (e.g. headers), but those
 | ||||
| 	// are not tracked. Therefor the number of Written bytes will usually match
 | ||||
| 	// the size of the response body.
 | ||||
| 	Written int64 | ||||
| } | ||||
| 
 | ||||
| // CaptureMetrics wraps the given hnd, executes it with the given w and r, and
 | ||||
| // returns the metrics it captured from it.
 | ||||
| func CaptureMetrics(hnd http.Handler, w http.ResponseWriter, r *http.Request) Metrics { | ||||
| 	return CaptureMetricsFn(w, func(ww http.ResponseWriter) { | ||||
| 		hnd.ServeHTTP(ww, r) | ||||
| 	}) | ||||
| } | ||||
| 
 | ||||
| // CaptureMetricsFn wraps w and calls fn with the wrapped w and returns the
 | ||||
| // resulting metrics. This is very similar to CaptureMetrics (which is just
 | ||||
| // sugar on top of this func), but is a more usable interface if your
 | ||||
| // application doesn't use the Go http.Handler interface.
 | ||||
| func CaptureMetricsFn(w http.ResponseWriter, fn func(http.ResponseWriter)) Metrics { | ||||
| 	var ( | ||||
| 		start         = time.Now() | ||||
| 		m             = Metrics{Code: http.StatusOK} | ||||
| 		headerWritten bool | ||||
| 		lock          sync.Mutex | ||||
| 		hooks         = Hooks{ | ||||
| 			WriteHeader: func(next WriteHeaderFunc) WriteHeaderFunc { | ||||
| 				return func(code int) { | ||||
| 					next(code) | ||||
| 					lock.Lock() | ||||
| 					defer lock.Unlock() | ||||
| 					if !headerWritten { | ||||
| 						m.Code = code | ||||
| 						headerWritten = true | ||||
| 					} | ||||
| 				} | ||||
| 			}, | ||||
| 
 | ||||
| 			Write: func(next WriteFunc) WriteFunc { | ||||
| 				return func(p []byte) (int, error) { | ||||
| 					n, err := next(p) | ||||
| 					lock.Lock() | ||||
| 					defer lock.Unlock() | ||||
| 					m.Written += int64(n) | ||||
| 					headerWritten = true | ||||
| 					return n, err | ||||
| 				} | ||||
| 			}, | ||||
| 
 | ||||
| 			ReadFrom: func(next ReadFromFunc) ReadFromFunc { | ||||
| 				return func(src io.Reader) (int64, error) { | ||||
| 					n, err := next(src) | ||||
| 					lock.Lock() | ||||
| 					defer lock.Unlock() | ||||
| 					headerWritten = true | ||||
| 					m.Written += n | ||||
| 					return n, err | ||||
| 				} | ||||
| 			}, | ||||
| 		} | ||||
| 	) | ||||
| 
 | ||||
| 	fn(Wrap(w, hooks)) | ||||
| 	m.Duration = time.Since(start) | ||||
| 	return m | ||||
| } | ||||
|  | @ -0,0 +1,10 @@ | |||
| // Package httpsnoop provides an easy way to capture http related metrics (i.e.
 | ||||
| // response time, bytes written, and http status code) from your application's
 | ||||
| // http.Handlers.
 | ||||
| //
 | ||||
| // Doing this requires non-trivial wrapping of the http.ResponseWriter
 | ||||
| // interface, which is also exposed for users interested in a more low-level
 | ||||
| // API.
 | ||||
| package httpsnoop | ||||
| 
 | ||||
| //go:generate go run codegen/main.go
 | ||||
|  | @ -0,0 +1,3 @@ | |||
| module github.com/felixge/httpsnoop | ||||
| 
 | ||||
| go 1.13 | ||||
|  | @ -0,0 +1,385 @@ | |||
| // +build go1.8
 | ||||
| // Code generated by "httpsnoop/codegen"; DO NOT EDIT
 | ||||
| 
 | ||||
| package httpsnoop | ||||
| 
 | ||||
| import ( | ||||
| 	"bufio" | ||||
| 	"io" | ||||
| 	"net" | ||||
| 	"net/http" | ||||
| ) | ||||
| 
 | ||||
| // HeaderFunc is part of the http.ResponseWriter interface.
 | ||||
| type HeaderFunc func() http.Header | ||||
| 
 | ||||
| // WriteHeaderFunc is part of the http.ResponseWriter interface.
 | ||||
| type WriteHeaderFunc func(code int) | ||||
| 
 | ||||
| // WriteFunc is part of the http.ResponseWriter interface.
 | ||||
| type WriteFunc func(b []byte) (int, error) | ||||
| 
 | ||||
| // FlushFunc is part of the http.Flusher interface.
 | ||||
| type FlushFunc func() | ||||
| 
 | ||||
| // CloseNotifyFunc is part of the http.CloseNotifier interface.
 | ||||
| type CloseNotifyFunc func() <-chan bool | ||||
| 
 | ||||
| // HijackFunc is part of the http.Hijacker interface.
 | ||||
| type HijackFunc func() (net.Conn, *bufio.ReadWriter, error) | ||||
| 
 | ||||
| // ReadFromFunc is part of the io.ReaderFrom interface.
 | ||||
| type ReadFromFunc func(src io.Reader) (int64, error) | ||||
| 
 | ||||
| // PushFunc is part of the http.Pusher interface.
 | ||||
| type PushFunc func(target string, opts *http.PushOptions) error | ||||
| 
 | ||||
| // Hooks defines a set of method interceptors for methods included in
 | ||||
| // http.ResponseWriter as well as some others. You can think of them as
 | ||||
| // middleware for the function calls they target. See Wrap for more details.
 | ||||
| type Hooks struct { | ||||
| 	Header      func(HeaderFunc) HeaderFunc | ||||
| 	WriteHeader func(WriteHeaderFunc) WriteHeaderFunc | ||||
| 	Write       func(WriteFunc) WriteFunc | ||||
| 	Flush       func(FlushFunc) FlushFunc | ||||
| 	CloseNotify func(CloseNotifyFunc) CloseNotifyFunc | ||||
| 	Hijack      func(HijackFunc) HijackFunc | ||||
| 	ReadFrom    func(ReadFromFunc) ReadFromFunc | ||||
| 	Push        func(PushFunc) PushFunc | ||||
| } | ||||
| 
 | ||||
| // Wrap returns a wrapped version of w that provides the exact same interface
 | ||||
| // as w. Specifically if w implements any combination of:
 | ||||
| //
 | ||||
| // - http.Flusher
 | ||||
| // - http.CloseNotifier
 | ||||
| // - http.Hijacker
 | ||||
| // - io.ReaderFrom
 | ||||
| // - http.Pusher
 | ||||
| //
 | ||||
| // The wrapped version will implement the exact same combination. If no hooks
 | ||||
| // are set, the wrapped version also behaves exactly as w. Hooks targeting
 | ||||
| // methods not supported by w are ignored. Any other hooks will intercept the
 | ||||
| // method they target and may modify the call's arguments and/or return values.
 | ||||
| // The CaptureMetrics implementation serves as a working example for how the
 | ||||
| // hooks can be used.
 | ||||
| func Wrap(w http.ResponseWriter, hooks Hooks) http.ResponseWriter { | ||||
| 	rw := &rw{w: w, h: hooks} | ||||
| 	_, i0 := w.(http.Flusher) | ||||
| 	_, i1 := w.(http.CloseNotifier) | ||||
| 	_, i2 := w.(http.Hijacker) | ||||
| 	_, i3 := w.(io.ReaderFrom) | ||||
| 	_, i4 := w.(http.Pusher) | ||||
| 	switch { | ||||
| 	// combination 1/32
 | ||||
| 	case !i0 && !i1 && !i2 && !i3 && !i4: | ||||
| 		return struct { | ||||
| 			http.ResponseWriter | ||||
| 		}{rw} | ||||
| 	// combination 2/32
 | ||||
| 	case !i0 && !i1 && !i2 && !i3 && i4: | ||||
| 		return struct { | ||||
| 			http.ResponseWriter | ||||
| 			http.Pusher | ||||
| 		}{rw, rw} | ||||
| 	// combination 3/32
 | ||||
| 	case !i0 && !i1 && !i2 && i3 && !i4: | ||||
| 		return struct { | ||||
| 			http.ResponseWriter | ||||
| 			io.ReaderFrom | ||||
| 		}{rw, rw} | ||||
| 	// combination 4/32
 | ||||
| 	case !i0 && !i1 && !i2 && i3 && i4: | ||||
| 		return struct { | ||||
| 			http.ResponseWriter | ||||
| 			io.ReaderFrom | ||||
| 			http.Pusher | ||||
| 		}{rw, rw, rw} | ||||
| 	// combination 5/32
 | ||||
| 	case !i0 && !i1 && i2 && !i3 && !i4: | ||||
| 		return struct { | ||||
| 			http.ResponseWriter | ||||
| 			http.Hijacker | ||||
| 		}{rw, rw} | ||||
| 	// combination 6/32
 | ||||
| 	case !i0 && !i1 && i2 && !i3 && i4: | ||||
| 		return struct { | ||||
| 			http.ResponseWriter | ||||
| 			http.Hijacker | ||||
| 			http.Pusher | ||||
| 		}{rw, rw, rw} | ||||
| 	// combination 7/32
 | ||||
| 	case !i0 && !i1 && i2 && i3 && !i4: | ||||
| 		return struct { | ||||
| 			http.ResponseWriter | ||||
| 			http.Hijacker | ||||
| 			io.ReaderFrom | ||||
| 		}{rw, rw, rw} | ||||
| 	// combination 8/32
 | ||||
| 	case !i0 && !i1 && i2 && i3 && i4: | ||||
| 		return struct { | ||||
| 			http.ResponseWriter | ||||
| 			http.Hijacker | ||||
| 			io.ReaderFrom | ||||
| 			http.Pusher | ||||
| 		}{rw, rw, rw, rw} | ||||
| 	// combination 9/32
 | ||||
| 	case !i0 && i1 && !i2 && !i3 && !i4: | ||||
| 		return struct { | ||||
| 			http.ResponseWriter | ||||
| 			http.CloseNotifier | ||||
| 		}{rw, rw} | ||||
| 	// combination 10/32
 | ||||
| 	case !i0 && i1 && !i2 && !i3 && i4: | ||||
| 		return struct { | ||||
| 			http.ResponseWriter | ||||
| 			http.CloseNotifier | ||||
| 			http.Pusher | ||||
| 		}{rw, rw, rw} | ||||
| 	// combination 11/32
 | ||||
| 	case !i0 && i1 && !i2 && i3 && !i4: | ||||
| 		return struct { | ||||
| 			http.ResponseWriter | ||||
| 			http.CloseNotifier | ||||
| 			io.ReaderFrom | ||||
| 		}{rw, rw, rw} | ||||
| 	// combination 12/32
 | ||||
| 	case !i0 && i1 && !i2 && i3 && i4: | ||||
| 		return struct { | ||||
| 			http.ResponseWriter | ||||
| 			http.CloseNotifier | ||||
| 			io.ReaderFrom | ||||
| 			http.Pusher | ||||
| 		}{rw, rw, rw, rw} | ||||
| 	// combination 13/32
 | ||||
| 	case !i0 && i1 && i2 && !i3 && !i4: | ||||
| 		return struct { | ||||
| 			http.ResponseWriter | ||||
| 			http.CloseNotifier | ||||
| 			http.Hijacker | ||||
| 		}{rw, rw, rw} | ||||
| 	// combination 14/32
 | ||||
| 	case !i0 && i1 && i2 && !i3 && i4: | ||||
| 		return struct { | ||||
| 			http.ResponseWriter | ||||
| 			http.CloseNotifier | ||||
| 			http.Hijacker | ||||
| 			http.Pusher | ||||
| 		}{rw, rw, rw, rw} | ||||
| 	// combination 15/32
 | ||||
| 	case !i0 && i1 && i2 && i3 && !i4: | ||||
| 		return struct { | ||||
| 			http.ResponseWriter | ||||
| 			http.CloseNotifier | ||||
| 			http.Hijacker | ||||
| 			io.ReaderFrom | ||||
| 		}{rw, rw, rw, rw} | ||||
| 	// combination 16/32
 | ||||
| 	case !i0 && i1 && i2 && i3 && i4: | ||||
| 		return struct { | ||||
| 			http.ResponseWriter | ||||
| 			http.CloseNotifier | ||||
| 			http.Hijacker | ||||
| 			io.ReaderFrom | ||||
| 			http.Pusher | ||||
| 		}{rw, rw, rw, rw, rw} | ||||
| 	// combination 17/32
 | ||||
| 	case i0 && !i1 && !i2 && !i3 && !i4: | ||||
| 		return struct { | ||||
| 			http.ResponseWriter | ||||
| 			http.Flusher | ||||
| 		}{rw, rw} | ||||
| 	// combination 18/32
 | ||||
| 	case i0 && !i1 && !i2 && !i3 && i4: | ||||
| 		return struct { | ||||
| 			http.ResponseWriter | ||||
| 			http.Flusher | ||||
| 			http.Pusher | ||||
| 		}{rw, rw, rw} | ||||
| 	// combination 19/32
 | ||||
| 	case i0 && !i1 && !i2 && i3 && !i4: | ||||
| 		return struct { | ||||
| 			http.ResponseWriter | ||||
| 			http.Flusher | ||||
| 			io.ReaderFrom | ||||
| 		}{rw, rw, rw} | ||||
| 	// combination 20/32
 | ||||
| 	case i0 && !i1 && !i2 && i3 && i4: | ||||
| 		return struct { | ||||
| 			http.ResponseWriter | ||||
| 			http.Flusher | ||||
| 			io.ReaderFrom | ||||
| 			http.Pusher | ||||
| 		}{rw, rw, rw, rw} | ||||
| 	// combination 21/32
 | ||||
| 	case i0 && !i1 && i2 && !i3 && !i4: | ||||
| 		return struct { | ||||
| 			http.ResponseWriter | ||||
| 			http.Flusher | ||||
| 			http.Hijacker | ||||
| 		}{rw, rw, rw} | ||||
| 	// combination 22/32
 | ||||
| 	case i0 && !i1 && i2 && !i3 && i4: | ||||
| 		return struct { | ||||
| 			http.ResponseWriter | ||||
| 			http.Flusher | ||||
| 			http.Hijacker | ||||
| 			http.Pusher | ||||
| 		}{rw, rw, rw, rw} | ||||
| 	// combination 23/32
 | ||||
| 	case i0 && !i1 && i2 && i3 && !i4: | ||||
| 		return struct { | ||||
| 			http.ResponseWriter | ||||
| 			http.Flusher | ||||
| 			http.Hijacker | ||||
| 			io.ReaderFrom | ||||
| 		}{rw, rw, rw, rw} | ||||
| 	// combination 24/32
 | ||||
| 	case i0 && !i1 && i2 && i3 && i4: | ||||
| 		return struct { | ||||
| 			http.ResponseWriter | ||||
| 			http.Flusher | ||||
| 			http.Hijacker | ||||
| 			io.ReaderFrom | ||||
| 			http.Pusher | ||||
| 		}{rw, rw, rw, rw, rw} | ||||
| 	// combination 25/32
 | ||||
| 	case i0 && i1 && !i2 && !i3 && !i4: | ||||
| 		return struct { | ||||
| 			http.ResponseWriter | ||||
| 			http.Flusher | ||||
| 			http.CloseNotifier | ||||
| 		}{rw, rw, rw} | ||||
| 	// combination 26/32
 | ||||
| 	case i0 && i1 && !i2 && !i3 && i4: | ||||
| 		return struct { | ||||
| 			http.ResponseWriter | ||||
| 			http.Flusher | ||||
| 			http.CloseNotifier | ||||
| 			http.Pusher | ||||
| 		}{rw, rw, rw, rw} | ||||
| 	// combination 27/32
 | ||||
| 	case i0 && i1 && !i2 && i3 && !i4: | ||||
| 		return struct { | ||||
| 			http.ResponseWriter | ||||
| 			http.Flusher | ||||
| 			http.CloseNotifier | ||||
| 			io.ReaderFrom | ||||
| 		}{rw, rw, rw, rw} | ||||
| 	// combination 28/32
 | ||||
| 	case i0 && i1 && !i2 && i3 && i4: | ||||
| 		return struct { | ||||
| 			http.ResponseWriter | ||||
| 			http.Flusher | ||||
| 			http.CloseNotifier | ||||
| 			io.ReaderFrom | ||||
| 			http.Pusher | ||||
| 		}{rw, rw, rw, rw, rw} | ||||
| 	// combination 29/32
 | ||||
| 	case i0 && i1 && i2 && !i3 && !i4: | ||||
| 		return struct { | ||||
| 			http.ResponseWriter | ||||
| 			http.Flusher | ||||
| 			http.CloseNotifier | ||||
| 			http.Hijacker | ||||
| 		}{rw, rw, rw, rw} | ||||
| 	// combination 30/32
 | ||||
| 	case i0 && i1 && i2 && !i3 && i4: | ||||
| 		return struct { | ||||
| 			http.ResponseWriter | ||||
| 			http.Flusher | ||||
| 			http.CloseNotifier | ||||
| 			http.Hijacker | ||||
| 			http.Pusher | ||||
| 		}{rw, rw, rw, rw, rw} | ||||
| 	// combination 31/32
 | ||||
| 	case i0 && i1 && i2 && i3 && !i4: | ||||
| 		return struct { | ||||
| 			http.ResponseWriter | ||||
| 			http.Flusher | ||||
| 			http.CloseNotifier | ||||
| 			http.Hijacker | ||||
| 			io.ReaderFrom | ||||
| 		}{rw, rw, rw, rw, rw} | ||||
| 	// combination 32/32
 | ||||
| 	case i0 && i1 && i2 && i3 && i4: | ||||
| 		return struct { | ||||
| 			http.ResponseWriter | ||||
| 			http.Flusher | ||||
| 			http.CloseNotifier | ||||
| 			http.Hijacker | ||||
| 			io.ReaderFrom | ||||
| 			http.Pusher | ||||
| 		}{rw, rw, rw, rw, rw, rw} | ||||
| 	} | ||||
| 	panic("unreachable") | ||||
| } | ||||
| 
 | ||||
| type rw struct { | ||||
| 	w http.ResponseWriter | ||||
| 	h Hooks | ||||
| } | ||||
| 
 | ||||
| func (w *rw) Header() http.Header { | ||||
| 	f := w.w.(http.ResponseWriter).Header | ||||
| 	if w.h.Header != nil { | ||||
| 		f = w.h.Header(f) | ||||
| 	} | ||||
| 	return f() | ||||
| } | ||||
| 
 | ||||
| func (w *rw) WriteHeader(code int) { | ||||
| 	f := w.w.(http.ResponseWriter).WriteHeader | ||||
| 	if w.h.WriteHeader != nil { | ||||
| 		f = w.h.WriteHeader(f) | ||||
| 	} | ||||
| 	f(code) | ||||
| } | ||||
| 
 | ||||
| func (w *rw) Write(b []byte) (int, error) { | ||||
| 	f := w.w.(http.ResponseWriter).Write | ||||
| 	if w.h.Write != nil { | ||||
| 		f = w.h.Write(f) | ||||
| 	} | ||||
| 	return f(b) | ||||
| } | ||||
| 
 | ||||
| func (w *rw) Flush() { | ||||
| 	f := w.w.(http.Flusher).Flush | ||||
| 	if w.h.Flush != nil { | ||||
| 		f = w.h.Flush(f) | ||||
| 	} | ||||
| 	f() | ||||
| } | ||||
| 
 | ||||
| func (w *rw) CloseNotify() <-chan bool { | ||||
| 	f := w.w.(http.CloseNotifier).CloseNotify | ||||
| 	if w.h.CloseNotify != nil { | ||||
| 		f = w.h.CloseNotify(f) | ||||
| 	} | ||||
| 	return f() | ||||
| } | ||||
| 
 | ||||
| func (w *rw) Hijack() (net.Conn, *bufio.ReadWriter, error) { | ||||
| 	f := w.w.(http.Hijacker).Hijack | ||||
| 	if w.h.Hijack != nil { | ||||
| 		f = w.h.Hijack(f) | ||||
| 	} | ||||
| 	return f() | ||||
| } | ||||
| 
 | ||||
| func (w *rw) ReadFrom(src io.Reader) (int64, error) { | ||||
| 	f := w.w.(io.ReaderFrom).ReadFrom | ||||
| 	if w.h.ReadFrom != nil { | ||||
| 		f = w.h.ReadFrom(f) | ||||
| 	} | ||||
| 	return f(src) | ||||
| } | ||||
| 
 | ||||
| func (w *rw) Push(target string, opts *http.PushOptions) error { | ||||
| 	f := w.w.(http.Pusher).Push | ||||
| 	if w.h.Push != nil { | ||||
| 		f = w.h.Push(f) | ||||
| 	} | ||||
| 	return f(target, opts) | ||||
| } | ||||
|  | @ -0,0 +1,243 @@ | |||
| // +build !go1.8
 | ||||
| // Code generated by "httpsnoop/codegen"; DO NOT EDIT
 | ||||
| 
 | ||||
| package httpsnoop | ||||
| 
 | ||||
| import ( | ||||
| 	"bufio" | ||||
| 	"io" | ||||
| 	"net" | ||||
| 	"net/http" | ||||
| ) | ||||
| 
 | ||||
| // HeaderFunc is part of the http.ResponseWriter interface.
 | ||||
| type HeaderFunc func() http.Header | ||||
| 
 | ||||
| // WriteHeaderFunc is part of the http.ResponseWriter interface.
 | ||||
| type WriteHeaderFunc func(code int) | ||||
| 
 | ||||
| // WriteFunc is part of the http.ResponseWriter interface.
 | ||||
| type WriteFunc func(b []byte) (int, error) | ||||
| 
 | ||||
| // FlushFunc is part of the http.Flusher interface.
 | ||||
| type FlushFunc func() | ||||
| 
 | ||||
| // CloseNotifyFunc is part of the http.CloseNotifier interface.
 | ||||
| type CloseNotifyFunc func() <-chan bool | ||||
| 
 | ||||
| // HijackFunc is part of the http.Hijacker interface.
 | ||||
| type HijackFunc func() (net.Conn, *bufio.ReadWriter, error) | ||||
| 
 | ||||
| // ReadFromFunc is part of the io.ReaderFrom interface.
 | ||||
| type ReadFromFunc func(src io.Reader) (int64, error) | ||||
| 
 | ||||
| // Hooks defines a set of method interceptors for methods included in
 | ||||
| // http.ResponseWriter as well as some others. You can think of them as
 | ||||
| // middleware for the function calls they target. See Wrap for more details.
 | ||||
| type Hooks struct { | ||||
| 	Header      func(HeaderFunc) HeaderFunc | ||||
| 	WriteHeader func(WriteHeaderFunc) WriteHeaderFunc | ||||
| 	Write       func(WriteFunc) WriteFunc | ||||
| 	Flush       func(FlushFunc) FlushFunc | ||||
| 	CloseNotify func(CloseNotifyFunc) CloseNotifyFunc | ||||
| 	Hijack      func(HijackFunc) HijackFunc | ||||
| 	ReadFrom    func(ReadFromFunc) ReadFromFunc | ||||
| } | ||||
| 
 | ||||
| // Wrap returns a wrapped version of w that provides the exact same interface
 | ||||
| // as w. Specifically if w implements any combination of:
 | ||||
| //
 | ||||
| // - http.Flusher
 | ||||
| // - http.CloseNotifier
 | ||||
| // - http.Hijacker
 | ||||
| // - io.ReaderFrom
 | ||||
| //
 | ||||
| // The wrapped version will implement the exact same combination. If no hooks
 | ||||
| // are set, the wrapped version also behaves exactly as w. Hooks targeting
 | ||||
| // methods not supported by w are ignored. Any other hooks will intercept the
 | ||||
| // method they target and may modify the call's arguments and/or return values.
 | ||||
| // The CaptureMetrics implementation serves as a working example for how the
 | ||||
| // hooks can be used.
 | ||||
| func Wrap(w http.ResponseWriter, hooks Hooks) http.ResponseWriter { | ||||
| 	rw := &rw{w: w, h: hooks} | ||||
| 	_, i0 := w.(http.Flusher) | ||||
| 	_, i1 := w.(http.CloseNotifier) | ||||
| 	_, i2 := w.(http.Hijacker) | ||||
| 	_, i3 := w.(io.ReaderFrom) | ||||
| 	switch { | ||||
| 	// combination 1/16
 | ||||
| 	case !i0 && !i1 && !i2 && !i3: | ||||
| 		return struct { | ||||
| 			http.ResponseWriter | ||||
| 		}{rw} | ||||
| 	// combination 2/16
 | ||||
| 	case !i0 && !i1 && !i2 && i3: | ||||
| 		return struct { | ||||
| 			http.ResponseWriter | ||||
| 			io.ReaderFrom | ||||
| 		}{rw, rw} | ||||
| 	// combination 3/16
 | ||||
| 	case !i0 && !i1 && i2 && !i3: | ||||
| 		return struct { | ||||
| 			http.ResponseWriter | ||||
| 			http.Hijacker | ||||
| 		}{rw, rw} | ||||
| 	// combination 4/16
 | ||||
| 	case !i0 && !i1 && i2 && i3: | ||||
| 		return struct { | ||||
| 			http.ResponseWriter | ||||
| 			http.Hijacker | ||||
| 			io.ReaderFrom | ||||
| 		}{rw, rw, rw} | ||||
| 	// combination 5/16
 | ||||
| 	case !i0 && i1 && !i2 && !i3: | ||||
| 		return struct { | ||||
| 			http.ResponseWriter | ||||
| 			http.CloseNotifier | ||||
| 		}{rw, rw} | ||||
| 	// combination 6/16
 | ||||
| 	case !i0 && i1 && !i2 && i3: | ||||
| 		return struct { | ||||
| 			http.ResponseWriter | ||||
| 			http.CloseNotifier | ||||
| 			io.ReaderFrom | ||||
| 		}{rw, rw, rw} | ||||
| 	// combination 7/16
 | ||||
| 	case !i0 && i1 && i2 && !i3: | ||||
| 		return struct { | ||||
| 			http.ResponseWriter | ||||
| 			http.CloseNotifier | ||||
| 			http.Hijacker | ||||
| 		}{rw, rw, rw} | ||||
| 	// combination 8/16
 | ||||
| 	case !i0 && i1 && i2 && i3: | ||||
| 		return struct { | ||||
| 			http.ResponseWriter | ||||
| 			http.CloseNotifier | ||||
| 			http.Hijacker | ||||
| 			io.ReaderFrom | ||||
| 		}{rw, rw, rw, rw} | ||||
| 	// combination 9/16
 | ||||
| 	case i0 && !i1 && !i2 && !i3: | ||||
| 		return struct { | ||||
| 			http.ResponseWriter | ||||
| 			http.Flusher | ||||
| 		}{rw, rw} | ||||
| 	// combination 10/16
 | ||||
| 	case i0 && !i1 && !i2 && i3: | ||||
| 		return struct { | ||||
| 			http.ResponseWriter | ||||
| 			http.Flusher | ||||
| 			io.ReaderFrom | ||||
| 		}{rw, rw, rw} | ||||
| 	// combination 11/16
 | ||||
| 	case i0 && !i1 && i2 && !i3: | ||||
| 		return struct { | ||||
| 			http.ResponseWriter | ||||
| 			http.Flusher | ||||
| 			http.Hijacker | ||||
| 		}{rw, rw, rw} | ||||
| 	// combination 12/16
 | ||||
| 	case i0 && !i1 && i2 && i3: | ||||
| 		return struct { | ||||
| 			http.ResponseWriter | ||||
| 			http.Flusher | ||||
| 			http.Hijacker | ||||
| 			io.ReaderFrom | ||||
| 		}{rw, rw, rw, rw} | ||||
| 	// combination 13/16
 | ||||
| 	case i0 && i1 && !i2 && !i3: | ||||
| 		return struct { | ||||
| 			http.ResponseWriter | ||||
| 			http.Flusher | ||||
| 			http.CloseNotifier | ||||
| 		}{rw, rw, rw} | ||||
| 	// combination 14/16
 | ||||
| 	case i0 && i1 && !i2 && i3: | ||||
| 		return struct { | ||||
| 			http.ResponseWriter | ||||
| 			http.Flusher | ||||
| 			http.CloseNotifier | ||||
| 			io.ReaderFrom | ||||
| 		}{rw, rw, rw, rw} | ||||
| 	// combination 15/16
 | ||||
| 	case i0 && i1 && i2 && !i3: | ||||
| 		return struct { | ||||
| 			http.ResponseWriter | ||||
| 			http.Flusher | ||||
| 			http.CloseNotifier | ||||
| 			http.Hijacker | ||||
| 		}{rw, rw, rw, rw} | ||||
| 	// combination 16/16
 | ||||
| 	case i0 && i1 && i2 && i3: | ||||
| 		return struct { | ||||
| 			http.ResponseWriter | ||||
| 			http.Flusher | ||||
| 			http.CloseNotifier | ||||
| 			http.Hijacker | ||||
| 			io.ReaderFrom | ||||
| 		}{rw, rw, rw, rw, rw} | ||||
| 	} | ||||
| 	panic("unreachable") | ||||
| } | ||||
| 
 | ||||
| type rw struct { | ||||
| 	w http.ResponseWriter | ||||
| 	h Hooks | ||||
| } | ||||
| 
 | ||||
| func (w *rw) Header() http.Header { | ||||
| 	f := w.w.(http.ResponseWriter).Header | ||||
| 	if w.h.Header != nil { | ||||
| 		f = w.h.Header(f) | ||||
| 	} | ||||
| 	return f() | ||||
| } | ||||
| 
 | ||||
| func (w *rw) WriteHeader(code int) { | ||||
| 	f := w.w.(http.ResponseWriter).WriteHeader | ||||
| 	if w.h.WriteHeader != nil { | ||||
| 		f = w.h.WriteHeader(f) | ||||
| 	} | ||||
| 	f(code) | ||||
| } | ||||
| 
 | ||||
| func (w *rw) Write(b []byte) (int, error) { | ||||
| 	f := w.w.(http.ResponseWriter).Write | ||||
| 	if w.h.Write != nil { | ||||
| 		f = w.h.Write(f) | ||||
| 	} | ||||
| 	return f(b) | ||||
| } | ||||
| 
 | ||||
| func (w *rw) Flush() { | ||||
| 	f := w.w.(http.Flusher).Flush | ||||
| 	if w.h.Flush != nil { | ||||
| 		f = w.h.Flush(f) | ||||
| 	} | ||||
| 	f() | ||||
| } | ||||
| 
 | ||||
| func (w *rw) CloseNotify() <-chan bool { | ||||
| 	f := w.w.(http.CloseNotifier).CloseNotify | ||||
| 	if w.h.CloseNotify != nil { | ||||
| 		f = w.h.CloseNotify(f) | ||||
| 	} | ||||
| 	return f() | ||||
| } | ||||
| 
 | ||||
| func (w *rw) Hijack() (net.Conn, *bufio.ReadWriter, error) { | ||||
| 	f := w.w.(http.Hijacker).Hijack | ||||
| 	if w.h.Hijack != nil { | ||||
| 		f = w.h.Hijack(f) | ||||
| 	} | ||||
| 	return f() | ||||
| } | ||||
| 
 | ||||
| func (w *rw) ReadFrom(src io.Reader) (int64, error) { | ||||
| 	f := w.w.(io.ReaderFrom).ReadFrom | ||||
| 	if w.h.ReadFrom != nil { | ||||
| 		f = w.h.ReadFrom(f) | ||||
| 	} | ||||
| 	return f(src) | ||||
| } | ||||
|  | @ -1,8 +0,0 @@ | |||
| language: go | ||||
| 
 | ||||
| go: | ||||
|   - 1.1 | ||||
|   - 1.2 | ||||
|   - 1.3 | ||||
|   - 1.4 | ||||
|   - tip | ||||
|  | @ -1,28 +1,32 @@ | |||
| gorilla/handlers | ||||
| ================ | ||||
| [](https://godoc.org/github.com/gorilla/handlers) [](https://travis-ci.org/gorilla/handlers) | ||||
| [](https://godoc.org/github.com/gorilla/handlers) | ||||
| [](https://circleci.com/gh/gorilla/handlers) | ||||
| [](https://sourcegraph.com/github.com/gorilla/handlers?badge) | ||||
| 
 | ||||
| 
 | ||||
| Package handlers is a collection of handlers (aka "HTTP middleware") for use | ||||
| with Go's `net/http` package (or any framework supporting `http.Handler`), including: | ||||
| 
 | ||||
| * `LoggingHandler` for logging HTTP requests in the Apache [Common Log | ||||
| * [**LoggingHandler**](https://godoc.org/github.com/gorilla/handlers#LoggingHandler) for logging HTTP requests in the Apache [Common Log | ||||
|   Format](http://httpd.apache.org/docs/2.2/logs.html#common). | ||||
| * `CombinedLoggingHandler` for logging HTTP requests in the Apache [Combined Log | ||||
| * [**CombinedLoggingHandler**](https://godoc.org/github.com/gorilla/handlers#CombinedLoggingHandler) for logging HTTP requests in the Apache [Combined Log | ||||
|   Format](http://httpd.apache.org/docs/2.2/logs.html#combined) commonly used by | ||||
|   both Apache and nginx. | ||||
| * `CompressHandler` for gzipping responses. | ||||
| * `ContentTypeHandler` for validating requests against a list of accepted | ||||
| * [**CompressHandler**](https://godoc.org/github.com/gorilla/handlers#CompressHandler) for gzipping responses. | ||||
| * [**ContentTypeHandler**](https://godoc.org/github.com/gorilla/handlers#ContentTypeHandler) for validating requests against a list of accepted | ||||
|   content types. | ||||
| * `MethodHandler` for matching HTTP methods against handlers in a | ||||
| * [**MethodHandler**](https://godoc.org/github.com/gorilla/handlers#MethodHandler) for matching HTTP methods against handlers in a | ||||
|   `map[string]http.Handler` | ||||
| * `ProxyHeaders` for populating `r.RemoteAddr` and `r.URL.Scheme` based on the | ||||
| * [**ProxyHeaders**](https://godoc.org/github.com/gorilla/handlers#ProxyHeaders) for populating `r.RemoteAddr` and `r.URL.Scheme` based on the | ||||
|   `X-Forwarded-For`, `X-Real-IP`, `X-Forwarded-Proto` and RFC7239 `Forwarded` | ||||
|   headers when running a Go server behind a HTTP reverse proxy. | ||||
| * `CanonicalHost` for re-directing to the preferred host when handling multiple  | ||||
| * [**CanonicalHost**](https://godoc.org/github.com/gorilla/handlers#CanonicalHost) for re-directing to the preferred host when handling multiple  | ||||
|   domains (i.e. multiple CNAME aliases). | ||||
| * [**RecoveryHandler**](https://godoc.org/github.com/gorilla/handlers#RecoveryHandler) for recovering from unexpected panics. | ||||
| 
 | ||||
| Other handlers are documented [on the Gorilla | ||||
| website](http://www.gorillatoolkit.org/pkg/handlers). | ||||
| website](https://www.gorillatoolkit.org/pkg/handlers). | ||||
| 
 | ||||
| ## Example | ||||
| 
 | ||||
|  |  | |||
|  | @ -18,7 +18,6 @@ type canonical struct { | |||
| //
 | ||||
| // Note: If the provided domain is considered invalid by url.Parse or otherwise
 | ||||
| // returns an empty scheme or host, clients are not re-directed.
 | ||||
| // not re-directed.
 | ||||
| //
 | ||||
| // Example:
 | ||||
| //
 | ||||
|  | @ -54,7 +53,11 @@ func (c canonical) ServeHTTP(w http.ResponseWriter, r *http.Request) { | |||
| 	if !strings.EqualFold(cleanHost(r.Host), dest.Host) { | ||||
| 		// Re-build the destination URL
 | ||||
| 		dest := dest.Scheme + "://" + dest.Host + r.URL.Path | ||||
| 		if r.URL.RawQuery != "" { | ||||
| 			dest += "?" + r.URL.RawQuery | ||||
| 		} | ||||
| 		http.Redirect(w, r, dest, c.code) | ||||
| 		return | ||||
| 	} | ||||
| 
 | ||||
| 	c.h.ServeHTTP(w, r) | ||||
|  |  | |||
|  | @ -10,75 +10,134 @@ import ( | |||
| 	"io" | ||||
| 	"net/http" | ||||
| 	"strings" | ||||
| 
 | ||||
| 	"github.com/felixge/httpsnoop" | ||||
| ) | ||||
| 
 | ||||
| const acceptEncoding string = "Accept-Encoding" | ||||
| 
 | ||||
| type compressResponseWriter struct { | ||||
| 	io.Writer | ||||
| 	http.ResponseWriter | ||||
| 	http.Hijacker | ||||
| 	compressor io.Writer | ||||
| 	w          http.ResponseWriter | ||||
| } | ||||
| 
 | ||||
| func (w *compressResponseWriter) Header() http.Header { | ||||
| 	return w.ResponseWriter.Header() | ||||
| func (cw *compressResponseWriter) WriteHeader(c int) { | ||||
| 	cw.w.Header().Del("Content-Length") | ||||
| 	cw.w.WriteHeader(c) | ||||
| } | ||||
| 
 | ||||
| func (w *compressResponseWriter) Write(b []byte) (int, error) { | ||||
| 	h := w.ResponseWriter.Header() | ||||
| func (cw *compressResponseWriter) Write(b []byte) (int, error) { | ||||
| 	h := cw.w.Header() | ||||
| 	if h.Get("Content-Type") == "" { | ||||
| 		h.Set("Content-Type", http.DetectContentType(b)) | ||||
| 	} | ||||
| 	h.Del("Content-Length") | ||||
| 
 | ||||
| 	return w.Writer.Write(b) | ||||
| 	return cw.compressor.Write(b) | ||||
| } | ||||
| 
 | ||||
| func (cw *compressResponseWriter) ReadFrom(r io.Reader) (int64, error) { | ||||
| 	return io.Copy(cw.compressor, r) | ||||
| } | ||||
| 
 | ||||
| type flusher interface { | ||||
| 	Flush() error | ||||
| } | ||||
| 
 | ||||
| func (w *compressResponseWriter) Flush() { | ||||
| 	// Flush compressed data if compressor supports it.
 | ||||
| 	if f, ok := w.compressor.(flusher); ok { | ||||
| 		f.Flush() | ||||
| 	} | ||||
| 	// Flush HTTP response.
 | ||||
| 	if f, ok := w.w.(http.Flusher); ok { | ||||
| 		f.Flush() | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| // CompressHandler gzip compresses HTTP responses for clients that support it
 | ||||
| // via the 'Accept-Encoding' header.
 | ||||
| //
 | ||||
| // Compressing TLS traffic may leak the page contents to an attacker if the
 | ||||
| // page contains user input: http://security.stackexchange.com/a/102015/12208
 | ||||
| func CompressHandler(h http.Handler) http.Handler { | ||||
| 	return CompressHandlerLevel(h, gzip.DefaultCompression) | ||||
| } | ||||
| 
 | ||||
| // CompressHandlerLevel gzip compresses HTTP responses with specified compression level
 | ||||
| // for clients that support it via the 'Accept-Encoding' header.
 | ||||
| //
 | ||||
| // The compression level should be gzip.DefaultCompression, gzip.NoCompression,
 | ||||
| // or any integer value between gzip.BestSpeed and gzip.BestCompression inclusive.
 | ||||
| // gzip.DefaultCompression is used in case of invalid compression level.
 | ||||
| func CompressHandlerLevel(h http.Handler, level int) http.Handler { | ||||
| 	if level < gzip.DefaultCompression || level > gzip.BestCompression { | ||||
| 		level = gzip.DefaultCompression | ||||
| 	} | ||||
| 
 | ||||
| 	const ( | ||||
| 		gzipEncoding  = "gzip" | ||||
| 		flateEncoding = "deflate" | ||||
| 	) | ||||
| 
 | ||||
| 	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { | ||||
| 	L: | ||||
| 		for _, enc := range strings.Split(r.Header.Get("Accept-Encoding"), ",") { | ||||
| 			switch strings.TrimSpace(enc) { | ||||
| 			case "gzip": | ||||
| 				w.Header().Set("Content-Encoding", "gzip") | ||||
| 				w.Header().Add("Vary", "Accept-Encoding") | ||||
| 
 | ||||
| 				gw := gzip.NewWriter(w) | ||||
| 				defer gw.Close() | ||||
| 
 | ||||
| 				h, hok := w.(http.Hijacker) | ||||
| 				if !hok { /* w is not Hijacker... oh well... */ | ||||
| 					h = nil | ||||
| 				} | ||||
| 
 | ||||
| 				w = &compressResponseWriter{ | ||||
| 					Writer:         gw, | ||||
| 					ResponseWriter: w, | ||||
| 					Hijacker:       h, | ||||
| 				} | ||||
| 
 | ||||
| 				break L | ||||
| 			case "deflate": | ||||
| 				w.Header().Set("Content-Encoding", "deflate") | ||||
| 				w.Header().Add("Vary", "Accept-Encoding") | ||||
| 
 | ||||
| 				fw, _ := flate.NewWriter(w, flate.DefaultCompression) | ||||
| 				defer fw.Close() | ||||
| 
 | ||||
| 				h, hok := w.(http.Hijacker) | ||||
| 				if !hok { /* w is not Hijacker... oh well... */ | ||||
| 					h = nil | ||||
| 				} | ||||
| 
 | ||||
| 				w = &compressResponseWriter{ | ||||
| 					Writer:         fw, | ||||
| 					ResponseWriter: w, | ||||
| 					Hijacker:       h, | ||||
| 				} | ||||
| 
 | ||||
| 				break L | ||||
| 		// detect what encoding to use
 | ||||
| 		var encoding string | ||||
| 		for _, curEnc := range strings.Split(r.Header.Get(acceptEncoding), ",") { | ||||
| 			curEnc = strings.TrimSpace(curEnc) | ||||
| 			if curEnc == gzipEncoding || curEnc == flateEncoding { | ||||
| 				encoding = curEnc | ||||
| 				break | ||||
| 			} | ||||
| 		} | ||||
| 
 | ||||
| 		// always add Accept-Encoding to Vary to prevent intermediate caches corruption
 | ||||
| 		w.Header().Add("Vary", acceptEncoding) | ||||
| 
 | ||||
| 		// if we weren't able to identify an encoding we're familiar with, pass on the
 | ||||
| 		// request to the handler and return
 | ||||
| 		if encoding == "" { | ||||
| 			h.ServeHTTP(w, r) | ||||
| 			return | ||||
| 		} | ||||
| 
 | ||||
| 		if r.Header.Get("Upgrade") != "" { | ||||
| 			h.ServeHTTP(w, r) | ||||
| 			return | ||||
| 		} | ||||
| 
 | ||||
| 		// wrap the ResponseWriter with the writer for the chosen encoding
 | ||||
| 		var encWriter io.WriteCloser | ||||
| 		if encoding == gzipEncoding { | ||||
| 			encWriter, _ = gzip.NewWriterLevel(w, level) | ||||
| 		} else if encoding == flateEncoding { | ||||
| 			encWriter, _ = flate.NewWriter(w, level) | ||||
| 		} | ||||
| 		defer encWriter.Close() | ||||
| 
 | ||||
| 		w.Header().Set("Content-Encoding", encoding) | ||||
| 		r.Header.Del(acceptEncoding) | ||||
| 
 | ||||
| 		cw := &compressResponseWriter{ | ||||
| 			w:          w, | ||||
| 			compressor: encWriter, | ||||
| 		} | ||||
| 
 | ||||
| 		w = httpsnoop.Wrap(w, httpsnoop.Hooks{ | ||||
| 			Write: func(httpsnoop.WriteFunc) httpsnoop.WriteFunc { | ||||
| 				return cw.Write | ||||
| 			}, | ||||
| 			WriteHeader: func(httpsnoop.WriteHeaderFunc) httpsnoop.WriteHeaderFunc { | ||||
| 				return cw.WriteHeader | ||||
| 			}, | ||||
| 			Flush: func(httpsnoop.FlushFunc) httpsnoop.FlushFunc { | ||||
| 				return cw.Flush | ||||
| 			}, | ||||
| 			ReadFrom: func(rff httpsnoop.ReadFromFunc) httpsnoop.ReadFromFunc { | ||||
| 				return cw.ReadFrom | ||||
| 			}, | ||||
| 		}) | ||||
| 
 | ||||
| 		h.ServeHTTP(w, r) | ||||
| 	}) | ||||
| } | ||||
|  |  | |||
|  | @ -0,0 +1,355 @@ | |||
| package handlers | ||||
| 
 | ||||
| import ( | ||||
| 	"net/http" | ||||
| 	"strconv" | ||||
| 	"strings" | ||||
| ) | ||||
| 
 | ||||
| // CORSOption represents a functional option for configuring the CORS middleware.
 | ||||
| type CORSOption func(*cors) error | ||||
| 
 | ||||
| type cors struct { | ||||
| 	h                      http.Handler | ||||
| 	allowedHeaders         []string | ||||
| 	allowedMethods         []string | ||||
| 	allowedOrigins         []string | ||||
| 	allowedOriginValidator OriginValidator | ||||
| 	exposedHeaders         []string | ||||
| 	maxAge                 int | ||||
| 	ignoreOptions          bool | ||||
| 	allowCredentials       bool | ||||
| 	optionStatusCode       int | ||||
| } | ||||
| 
 | ||||
| // OriginValidator takes an origin string and returns whether or not that origin is allowed.
 | ||||
| type OriginValidator func(string) bool | ||||
| 
 | ||||
| var ( | ||||
| 	defaultCorsOptionStatusCode = 200 | ||||
| 	defaultCorsMethods          = []string{"GET", "HEAD", "POST"} | ||||
| 	defaultCorsHeaders          = []string{"Accept", "Accept-Language", "Content-Language", "Origin"} | ||||
| 	// (WebKit/Safari v9 sends the Origin header by default in AJAX requests)
 | ||||
| ) | ||||
| 
 | ||||
| const ( | ||||
| 	corsOptionMethod           string = "OPTIONS" | ||||
| 	corsAllowOriginHeader      string = "Access-Control-Allow-Origin" | ||||
| 	corsExposeHeadersHeader    string = "Access-Control-Expose-Headers" | ||||
| 	corsMaxAgeHeader           string = "Access-Control-Max-Age" | ||||
| 	corsAllowMethodsHeader     string = "Access-Control-Allow-Methods" | ||||
| 	corsAllowHeadersHeader     string = "Access-Control-Allow-Headers" | ||||
| 	corsAllowCredentialsHeader string = "Access-Control-Allow-Credentials" | ||||
| 	corsRequestMethodHeader    string = "Access-Control-Request-Method" | ||||
| 	corsRequestHeadersHeader   string = "Access-Control-Request-Headers" | ||||
| 	corsOriginHeader           string = "Origin" | ||||
| 	corsVaryHeader             string = "Vary" | ||||
| 	corsOriginMatchAll         string = "*" | ||||
| ) | ||||
| 
 | ||||
| func (ch *cors) ServeHTTP(w http.ResponseWriter, r *http.Request) { | ||||
| 	origin := r.Header.Get(corsOriginHeader) | ||||
| 	if !ch.isOriginAllowed(origin) { | ||||
| 		if r.Method != corsOptionMethod || ch.ignoreOptions { | ||||
| 			ch.h.ServeHTTP(w, r) | ||||
| 		} | ||||
| 
 | ||||
| 		return | ||||
| 	} | ||||
| 
 | ||||
| 	if r.Method == corsOptionMethod { | ||||
| 		if ch.ignoreOptions { | ||||
| 			ch.h.ServeHTTP(w, r) | ||||
| 			return | ||||
| 		} | ||||
| 
 | ||||
| 		if _, ok := r.Header[corsRequestMethodHeader]; !ok { | ||||
| 			w.WriteHeader(http.StatusBadRequest) | ||||
| 			return | ||||
| 		} | ||||
| 
 | ||||
| 		method := r.Header.Get(corsRequestMethodHeader) | ||||
| 		if !ch.isMatch(method, ch.allowedMethods) { | ||||
| 			w.WriteHeader(http.StatusMethodNotAllowed) | ||||
| 			return | ||||
| 		} | ||||
| 
 | ||||
| 		requestHeaders := strings.Split(r.Header.Get(corsRequestHeadersHeader), ",") | ||||
| 		allowedHeaders := []string{} | ||||
| 		for _, v := range requestHeaders { | ||||
| 			canonicalHeader := http.CanonicalHeaderKey(strings.TrimSpace(v)) | ||||
| 			if canonicalHeader == "" || ch.isMatch(canonicalHeader, defaultCorsHeaders) { | ||||
| 				continue | ||||
| 			} | ||||
| 
 | ||||
| 			if !ch.isMatch(canonicalHeader, ch.allowedHeaders) { | ||||
| 				w.WriteHeader(http.StatusForbidden) | ||||
| 				return | ||||
| 			} | ||||
| 
 | ||||
| 			allowedHeaders = append(allowedHeaders, canonicalHeader) | ||||
| 		} | ||||
| 
 | ||||
| 		if len(allowedHeaders) > 0 { | ||||
| 			w.Header().Set(corsAllowHeadersHeader, strings.Join(allowedHeaders, ",")) | ||||
| 		} | ||||
| 
 | ||||
| 		if ch.maxAge > 0 { | ||||
| 			w.Header().Set(corsMaxAgeHeader, strconv.Itoa(ch.maxAge)) | ||||
| 		} | ||||
| 
 | ||||
| 		if !ch.isMatch(method, defaultCorsMethods) { | ||||
| 			w.Header().Set(corsAllowMethodsHeader, method) | ||||
| 		} | ||||
| 	} else { | ||||
| 		if len(ch.exposedHeaders) > 0 { | ||||
| 			w.Header().Set(corsExposeHeadersHeader, strings.Join(ch.exposedHeaders, ",")) | ||||
| 		} | ||||
| 	} | ||||
| 
 | ||||
| 	if ch.allowCredentials { | ||||
| 		w.Header().Set(corsAllowCredentialsHeader, "true") | ||||
| 	} | ||||
| 
 | ||||
| 	if len(ch.allowedOrigins) > 1 { | ||||
| 		w.Header().Set(corsVaryHeader, corsOriginHeader) | ||||
| 	} | ||||
| 
 | ||||
| 	returnOrigin := origin | ||||
| 	if ch.allowedOriginValidator == nil && len(ch.allowedOrigins) == 0 { | ||||
| 		returnOrigin = "*" | ||||
| 	} else { | ||||
| 		for _, o := range ch.allowedOrigins { | ||||
| 			// A configuration of * is different than explicitly setting an allowed
 | ||||
| 			// origin. Returning arbitrary origin headers in an access control allow
 | ||||
| 			// origin header is unsafe and is not required by any use case.
 | ||||
| 			if o == corsOriginMatchAll { | ||||
| 				returnOrigin = "*" | ||||
| 				break | ||||
| 			} | ||||
| 		} | ||||
| 	} | ||||
| 	w.Header().Set(corsAllowOriginHeader, returnOrigin) | ||||
| 
 | ||||
| 	if r.Method == corsOptionMethod { | ||||
| 		w.WriteHeader(ch.optionStatusCode) | ||||
| 		return | ||||
| 	} | ||||
| 	ch.h.ServeHTTP(w, r) | ||||
| } | ||||
| 
 | ||||
| // CORS provides Cross-Origin Resource Sharing middleware.
 | ||||
| // Example:
 | ||||
| //
 | ||||
| //  import (
 | ||||
| //      "net/http"
 | ||||
| //
 | ||||
| //      "github.com/gorilla/handlers"
 | ||||
| //      "github.com/gorilla/mux"
 | ||||
| //  )
 | ||||
| //
 | ||||
| //  func main() {
 | ||||
| //      r := mux.NewRouter()
 | ||||
| //      r.HandleFunc("/users", UserEndpoint)
 | ||||
| //      r.HandleFunc("/projects", ProjectEndpoint)
 | ||||
| //
 | ||||
| //      // Apply the CORS middleware to our top-level router, with the defaults.
 | ||||
| //      http.ListenAndServe(":8000", handlers.CORS()(r))
 | ||||
| //  }
 | ||||
| //
 | ||||
| func CORS(opts ...CORSOption) func(http.Handler) http.Handler { | ||||
| 	return func(h http.Handler) http.Handler { | ||||
| 		ch := parseCORSOptions(opts...) | ||||
| 		ch.h = h | ||||
| 		return ch | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| func parseCORSOptions(opts ...CORSOption) *cors { | ||||
| 	ch := &cors{ | ||||
| 		allowedMethods:   defaultCorsMethods, | ||||
| 		allowedHeaders:   defaultCorsHeaders, | ||||
| 		allowedOrigins:   []string{}, | ||||
| 		optionStatusCode: defaultCorsOptionStatusCode, | ||||
| 	} | ||||
| 
 | ||||
| 	for _, option := range opts { | ||||
| 		option(ch) | ||||
| 	} | ||||
| 
 | ||||
| 	return ch | ||||
| } | ||||
| 
 | ||||
| //
 | ||||
| // Functional options for configuring CORS.
 | ||||
| //
 | ||||
| 
 | ||||
| // AllowedHeaders adds the provided headers to the list of allowed headers in a
 | ||||
| // CORS request.
 | ||||
| // This is an append operation so the headers Accept, Accept-Language,
 | ||||
| // and Content-Language are always allowed.
 | ||||
| // Content-Type must be explicitly declared if accepting Content-Types other than
 | ||||
| // application/x-www-form-urlencoded, multipart/form-data, or text/plain.
 | ||||
| func AllowedHeaders(headers []string) CORSOption { | ||||
| 	return func(ch *cors) error { | ||||
| 		for _, v := range headers { | ||||
| 			normalizedHeader := http.CanonicalHeaderKey(strings.TrimSpace(v)) | ||||
| 			if normalizedHeader == "" { | ||||
| 				continue | ||||
| 			} | ||||
| 
 | ||||
| 			if !ch.isMatch(normalizedHeader, ch.allowedHeaders) { | ||||
| 				ch.allowedHeaders = append(ch.allowedHeaders, normalizedHeader) | ||||
| 			} | ||||
| 		} | ||||
| 
 | ||||
| 		return nil | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| // AllowedMethods can be used to explicitly allow methods in the
 | ||||
| // Access-Control-Allow-Methods header.
 | ||||
| // This is a replacement operation so you must also
 | ||||
| // pass GET, HEAD, and POST if you wish to support those methods.
 | ||||
| func AllowedMethods(methods []string) CORSOption { | ||||
| 	return func(ch *cors) error { | ||||
| 		ch.allowedMethods = []string{} | ||||
| 		for _, v := range methods { | ||||
| 			normalizedMethod := strings.ToUpper(strings.TrimSpace(v)) | ||||
| 			if normalizedMethod == "" { | ||||
| 				continue | ||||
| 			} | ||||
| 
 | ||||
| 			if !ch.isMatch(normalizedMethod, ch.allowedMethods) { | ||||
| 				ch.allowedMethods = append(ch.allowedMethods, normalizedMethod) | ||||
| 			} | ||||
| 		} | ||||
| 
 | ||||
| 		return nil | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| // AllowedOrigins sets the allowed origins for CORS requests, as used in the
 | ||||
| // 'Allow-Access-Control-Origin' HTTP header.
 | ||||
| // Note: Passing in a []string{"*"} will allow any domain.
 | ||||
| func AllowedOrigins(origins []string) CORSOption { | ||||
| 	return func(ch *cors) error { | ||||
| 		for _, v := range origins { | ||||
| 			if v == corsOriginMatchAll { | ||||
| 				ch.allowedOrigins = []string{corsOriginMatchAll} | ||||
| 				return nil | ||||
| 			} | ||||
| 		} | ||||
| 
 | ||||
| 		ch.allowedOrigins = origins | ||||
| 		return nil | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| // AllowedOriginValidator sets a function for evaluating allowed origins in CORS requests, represented by the
 | ||||
| // 'Allow-Access-Control-Origin' HTTP header.
 | ||||
| func AllowedOriginValidator(fn OriginValidator) CORSOption { | ||||
| 	return func(ch *cors) error { | ||||
| 		ch.allowedOriginValidator = fn | ||||
| 		return nil | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| // OptionStatusCode sets a custom status code on the OPTIONS requests.
 | ||||
| // Default behaviour sets it to 200 to reflect best practices. This is option is not mandatory
 | ||||
| // and can be used if you need a custom status code (i.e 204).
 | ||||
| //
 | ||||
| // More informations on the spec:
 | ||||
| // https://fetch.spec.whatwg.org/#cors-preflight-fetch
 | ||||
| func OptionStatusCode(code int) CORSOption { | ||||
| 	return func(ch *cors) error { | ||||
| 		ch.optionStatusCode = code | ||||
| 		return nil | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| // ExposedHeaders can be used to specify headers that are available
 | ||||
| // and will not be stripped out by the user-agent.
 | ||||
| func ExposedHeaders(headers []string) CORSOption { | ||||
| 	return func(ch *cors) error { | ||||
| 		ch.exposedHeaders = []string{} | ||||
| 		for _, v := range headers { | ||||
| 			normalizedHeader := http.CanonicalHeaderKey(strings.TrimSpace(v)) | ||||
| 			if normalizedHeader == "" { | ||||
| 				continue | ||||
| 			} | ||||
| 
 | ||||
| 			if !ch.isMatch(normalizedHeader, ch.exposedHeaders) { | ||||
| 				ch.exposedHeaders = append(ch.exposedHeaders, normalizedHeader) | ||||
| 			} | ||||
| 		} | ||||
| 
 | ||||
| 		return nil | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| // MaxAge determines the maximum age (in seconds) between preflight requests. A
 | ||||
| // maximum of 10 minutes is allowed. An age above this value will default to 10
 | ||||
| // minutes.
 | ||||
| func MaxAge(age int) CORSOption { | ||||
| 	return func(ch *cors) error { | ||||
| 		// Maximum of 10 minutes.
 | ||||
| 		if age > 600 { | ||||
| 			age = 600 | ||||
| 		} | ||||
| 
 | ||||
| 		ch.maxAge = age | ||||
| 		return nil | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| // IgnoreOptions causes the CORS middleware to ignore OPTIONS requests, instead
 | ||||
| // passing them through to the next handler. This is useful when your application
 | ||||
| // or framework has a pre-existing mechanism for responding to OPTIONS requests.
 | ||||
| func IgnoreOptions() CORSOption { | ||||
| 	return func(ch *cors) error { | ||||
| 		ch.ignoreOptions = true | ||||
| 		return nil | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| // AllowCredentials can be used to specify that the user agent may pass
 | ||||
| // authentication details along with the request.
 | ||||
| func AllowCredentials() CORSOption { | ||||
| 	return func(ch *cors) error { | ||||
| 		ch.allowCredentials = true | ||||
| 		return nil | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| func (ch *cors) isOriginAllowed(origin string) bool { | ||||
| 	if origin == "" { | ||||
| 		return false | ||||
| 	} | ||||
| 
 | ||||
| 	if ch.allowedOriginValidator != nil { | ||||
| 		return ch.allowedOriginValidator(origin) | ||||
| 	} | ||||
| 
 | ||||
| 	if len(ch.allowedOrigins) == 0 { | ||||
| 		return true | ||||
| 	} | ||||
| 
 | ||||
| 	for _, allowedOrigin := range ch.allowedOrigins { | ||||
| 		if allowedOrigin == origin || allowedOrigin == corsOriginMatchAll { | ||||
| 			return true | ||||
| 		} | ||||
| 	} | ||||
| 
 | ||||
| 	return false | ||||
| } | ||||
| 
 | ||||
| func (ch *cors) isMatch(needle string, haystack []string) bool { | ||||
| 	for _, v := range haystack { | ||||
| 		if v == needle { | ||||
| 			return true | ||||
| 		} | ||||
| 	} | ||||
| 
 | ||||
| 	return false | ||||
| } | ||||
|  | @ -0,0 +1,5 @@ | |||
| module github.com/gorilla/handlers | ||||
| 
 | ||||
| go 1.14 | ||||
| 
 | ||||
| require github.com/felixge/httpsnoop v1.0.1 | ||||
|  | @ -0,0 +1,2 @@ | |||
| github.com/felixge/httpsnoop v1.0.1 h1:lvB5Jl89CsZtGIWuTcDM1E/vkVs49/Ml7JJe07l8SPQ= | ||||
| github.com/felixge/httpsnoop v1.0.1/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= | ||||
|  | @ -7,27 +7,22 @@ package handlers | |||
| import ( | ||||
| 	"bufio" | ||||
| 	"fmt" | ||||
| 	"io" | ||||
| 	"net" | ||||
| 	"net/http" | ||||
| 	"net/url" | ||||
| 	"sort" | ||||
| 	"strconv" | ||||
| 	"strings" | ||||
| 	"time" | ||||
| 	"unicode/utf8" | ||||
| ) | ||||
| 
 | ||||
| // MethodHandler is an http.Handler that dispatches to a handler whose key in the MethodHandler's
 | ||||
| // map matches the name of the HTTP request's method, eg: GET
 | ||||
| // MethodHandler is an http.Handler that dispatches to a handler whose key in the
 | ||||
| // MethodHandler's map matches the name of the HTTP request's method, eg: GET
 | ||||
| //
 | ||||
| // If the request's method is OPTIONS and OPTIONS is not a key in the map then the handler
 | ||||
| // responds with a status of 200 and sets the Allow header to a comma-separated list of
 | ||||
| // available methods.
 | ||||
| // If the request's method is OPTIONS and OPTIONS is not a key in the map then
 | ||||
| // the handler responds with a status of 200 and sets the Allow header to a
 | ||||
| // comma-separated list of available methods.
 | ||||
| //
 | ||||
| // If the request's method doesn't match any of its keys the handler responds with
 | ||||
| // a status of 405, Method not allowed and sets the Allow header to a comma-separated list
 | ||||
| // of available methods.
 | ||||
| // If the request's method doesn't match any of its keys the handler responds
 | ||||
| // with a status of HTTP 405 "Method Not Allowed" and sets the Allow header to a
 | ||||
| // comma-separated list of available methods.
 | ||||
| type MethodHandler map[string]http.Handler | ||||
| 
 | ||||
| func (h MethodHandler) ServeHTTP(w http.ResponseWriter, req *http.Request) { | ||||
|  | @ -48,74 +43,15 @@ func (h MethodHandler) ServeHTTP(w http.ResponseWriter, req *http.Request) { | |||
| 	} | ||||
| } | ||||
| 
 | ||||
| // loggingHandler is the http.Handler implementation for LoggingHandlerTo and its friends
 | ||||
| type loggingHandler struct { | ||||
| 	writer  io.Writer | ||||
| 	handler http.Handler | ||||
| } | ||||
| 
 | ||||
| // combinedLoggingHandler is the http.Handler implementation for LoggingHandlerTo and its friends
 | ||||
| type combinedLoggingHandler struct { | ||||
| 	writer  io.Writer | ||||
| 	handler http.Handler | ||||
| } | ||||
| 
 | ||||
| func (h loggingHandler) ServeHTTP(w http.ResponseWriter, req *http.Request) { | ||||
| 	t := time.Now() | ||||
| 	logger := makeLogger(w) | ||||
| 	url := *req.URL | ||||
| 	h.handler.ServeHTTP(logger, req) | ||||
| 	writeLog(h.writer, req, url, t, logger.Status(), logger.Size()) | ||||
| } | ||||
| 
 | ||||
| func (h combinedLoggingHandler) ServeHTTP(w http.ResponseWriter, req *http.Request) { | ||||
| 	t := time.Now() | ||||
| 	logger := makeLogger(w) | ||||
| 	url := *req.URL | ||||
| 	h.handler.ServeHTTP(logger, req) | ||||
| 	writeCombinedLog(h.writer, req, url, t, logger.Status(), logger.Size()) | ||||
| } | ||||
| 
 | ||||
| func makeLogger(w http.ResponseWriter) loggingResponseWriter { | ||||
| 	var logger loggingResponseWriter = &responseLogger{w: w} | ||||
| 	if _, ok := w.(http.Hijacker); ok { | ||||
| 		logger = &hijackLogger{responseLogger{w: w}} | ||||
| 	} | ||||
| 	h, ok1 := logger.(http.Hijacker) | ||||
| 	c, ok2 := w.(http.CloseNotifier) | ||||
| 	if ok1 && ok2 { | ||||
| 		return hijackCloseNotifier{logger, h, c} | ||||
| 	} | ||||
| 	if ok2 { | ||||
| 		return &closeNotifyWriter{logger, c} | ||||
| 	} | ||||
| 	return logger | ||||
| } | ||||
| 
 | ||||
| type loggingResponseWriter interface { | ||||
| 	http.ResponseWriter | ||||
| 	http.Flusher | ||||
| 	Status() int | ||||
| 	Size() int | ||||
| } | ||||
| 
 | ||||
| // responseLogger is wrapper of http.ResponseWriter that keeps track of its HTTP status
 | ||||
| // code and body size
 | ||||
| // responseLogger is wrapper of http.ResponseWriter that keeps track of its HTTP
 | ||||
| // status code and body size
 | ||||
| type responseLogger struct { | ||||
| 	w      http.ResponseWriter | ||||
| 	status int | ||||
| 	size   int | ||||
| } | ||||
| 
 | ||||
| func (l *responseLogger) Header() http.Header { | ||||
| 	return l.w.Header() | ||||
| } | ||||
| 
 | ||||
| func (l *responseLogger) Write(b []byte) (int, error) { | ||||
| 	if l.status == 0 { | ||||
| 		// The status will be StatusOK if WriteHeader has not been called yet
 | ||||
| 		l.status = http.StatusOK | ||||
| 	} | ||||
| 	size, err := l.w.Write(b) | ||||
| 	l.size += size | ||||
| 	return size, err | ||||
|  | @ -134,187 +70,18 @@ func (l *responseLogger) Size() int { | |||
| 	return l.size | ||||
| } | ||||
| 
 | ||||
| func (l *responseLogger) Flush() { | ||||
| 	f, ok := l.w.(http.Flusher) | ||||
| 	if ok { | ||||
| 		f.Flush() | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| type hijackLogger struct { | ||||
| 	responseLogger | ||||
| } | ||||
| 
 | ||||
| func (l *hijackLogger) Hijack() (net.Conn, *bufio.ReadWriter, error) { | ||||
| 	h := l.responseLogger.w.(http.Hijacker) | ||||
| 	conn, rw, err := h.Hijack() | ||||
| 	if err == nil && l.responseLogger.status == 0 { | ||||
| 		// The status will be StatusSwitchingProtocols if there was no error and WriteHeader has not been called yet
 | ||||
| 		l.responseLogger.status = http.StatusSwitchingProtocols | ||||
| func (l *responseLogger) Hijack() (net.Conn, *bufio.ReadWriter, error) { | ||||
| 	conn, rw, err := l.w.(http.Hijacker).Hijack() | ||||
| 	if err == nil && l.status == 0 { | ||||
| 		// The status will be StatusSwitchingProtocols if there was no error and
 | ||||
| 		// WriteHeader has not been called yet
 | ||||
| 		l.status = http.StatusSwitchingProtocols | ||||
| 	} | ||||
| 	return conn, rw, err | ||||
| } | ||||
| 
 | ||||
| type closeNotifyWriter struct { | ||||
| 	loggingResponseWriter | ||||
| 	http.CloseNotifier | ||||
| } | ||||
| 
 | ||||
| type hijackCloseNotifier struct { | ||||
| 	loggingResponseWriter | ||||
| 	http.Hijacker | ||||
| 	http.CloseNotifier | ||||
| } | ||||
| 
 | ||||
| const lowerhex = "0123456789abcdef" | ||||
| 
 | ||||
| func appendQuoted(buf []byte, s string) []byte { | ||||
| 	var runeTmp [utf8.UTFMax]byte | ||||
| 	for width := 0; len(s) > 0; s = s[width:] { | ||||
| 		r := rune(s[0]) | ||||
| 		width = 1 | ||||
| 		if r >= utf8.RuneSelf { | ||||
| 			r, width = utf8.DecodeRuneInString(s) | ||||
| 		} | ||||
| 		if width == 1 && r == utf8.RuneError { | ||||
| 			buf = append(buf, `\x`...) | ||||
| 			buf = append(buf, lowerhex[s[0]>>4]) | ||||
| 			buf = append(buf, lowerhex[s[0]&0xF]) | ||||
| 			continue | ||||
| 		} | ||||
| 		if r == rune('"') || r == '\\' { // always backslashed
 | ||||
| 			buf = append(buf, '\\') | ||||
| 			buf = append(buf, byte(r)) | ||||
| 			continue | ||||
| 		} | ||||
| 		if strconv.IsPrint(r) { | ||||
| 			n := utf8.EncodeRune(runeTmp[:], r) | ||||
| 			buf = append(buf, runeTmp[:n]...) | ||||
| 			continue | ||||
| 		} | ||||
| 		switch r { | ||||
| 		case '\a': | ||||
| 			buf = append(buf, `\a`...) | ||||
| 		case '\b': | ||||
| 			buf = append(buf, `\b`...) | ||||
| 		case '\f': | ||||
| 			buf = append(buf, `\f`...) | ||||
| 		case '\n': | ||||
| 			buf = append(buf, `\n`...) | ||||
| 		case '\r': | ||||
| 			buf = append(buf, `\r`...) | ||||
| 		case '\t': | ||||
| 			buf = append(buf, `\t`...) | ||||
| 		case '\v': | ||||
| 			buf = append(buf, `\v`...) | ||||
| 		default: | ||||
| 			switch { | ||||
| 			case r < ' ': | ||||
| 				buf = append(buf, `\x`...) | ||||
| 				buf = append(buf, lowerhex[s[0]>>4]) | ||||
| 				buf = append(buf, lowerhex[s[0]&0xF]) | ||||
| 			case r > utf8.MaxRune: | ||||
| 				r = 0xFFFD | ||||
| 				fallthrough | ||||
| 			case r < 0x10000: | ||||
| 				buf = append(buf, `\u`...) | ||||
| 				for s := 12; s >= 0; s -= 4 { | ||||
| 					buf = append(buf, lowerhex[r>>uint(s)&0xF]) | ||||
| 				} | ||||
| 			default: | ||||
| 				buf = append(buf, `\U`...) | ||||
| 				for s := 28; s >= 0; s -= 4 { | ||||
| 					buf = append(buf, lowerhex[r>>uint(s)&0xF]) | ||||
| 				} | ||||
| 			} | ||||
| 		} | ||||
| 	} | ||||
| 	return buf | ||||
| 
 | ||||
| } | ||||
| 
 | ||||
| // buildCommonLogLine builds a log entry for req in Apache Common Log Format.
 | ||||
| // ts is the timestamp with which the entry should be logged.
 | ||||
| // status and size are used to provide the response HTTP status and size.
 | ||||
| func buildCommonLogLine(req *http.Request, url url.URL, ts time.Time, status int, size int) []byte { | ||||
| 	username := "-" | ||||
| 	if url.User != nil { | ||||
| 		if name := url.User.Username(); name != "" { | ||||
| 			username = name | ||||
| 		} | ||||
| 	} | ||||
| 
 | ||||
| 	host, _, err := net.SplitHostPort(req.RemoteAddr) | ||||
| 
 | ||||
| 	if err != nil { | ||||
| 		host = req.RemoteAddr | ||||
| 	} | ||||
| 
 | ||||
| 	uri := url.RequestURI() | ||||
| 
 | ||||
| 	buf := make([]byte, 0, 3*(len(host)+len(username)+len(req.Method)+len(uri)+len(req.Proto)+50)/2) | ||||
| 	buf = append(buf, host...) | ||||
| 	buf = append(buf, " - "...) | ||||
| 	buf = append(buf, username...) | ||||
| 	buf = append(buf, " ["...) | ||||
| 	buf = append(buf, ts.Format("02/Jan/2006:15:04:05 -0700")...) | ||||
| 	buf = append(buf, `] "`...) | ||||
| 	buf = append(buf, req.Method...) | ||||
| 	buf = append(buf, " "...) | ||||
| 	buf = appendQuoted(buf, uri) | ||||
| 	buf = append(buf, " "...) | ||||
| 	buf = append(buf, req.Proto...) | ||||
| 	buf = append(buf, `" `...) | ||||
| 	buf = append(buf, strconv.Itoa(status)...) | ||||
| 	buf = append(buf, " "...) | ||||
| 	buf = append(buf, strconv.Itoa(size)...) | ||||
| 	return buf | ||||
| } | ||||
| 
 | ||||
| // writeLog writes a log entry for req to w in Apache Common Log Format.
 | ||||
| // ts is the timestamp with which the entry should be logged.
 | ||||
| // status and size are used to provide the response HTTP status and size.
 | ||||
| func writeLog(w io.Writer, req *http.Request, url url.URL, ts time.Time, status, size int) { | ||||
| 	buf := buildCommonLogLine(req, url, ts, status, size) | ||||
| 	buf = append(buf, '\n') | ||||
| 	w.Write(buf) | ||||
| } | ||||
| 
 | ||||
| // writeCombinedLog writes a log entry for req to w in Apache Combined Log Format.
 | ||||
| // ts is the timestamp with which the entry should be logged.
 | ||||
| // status and size are used to provide the response HTTP status and size.
 | ||||
| func writeCombinedLog(w io.Writer, req *http.Request, url url.URL, ts time.Time, status, size int) { | ||||
| 	buf := buildCommonLogLine(req, url, ts, status, size) | ||||
| 	buf = append(buf, ` "`...) | ||||
| 	buf = appendQuoted(buf, req.Referer()) | ||||
| 	buf = append(buf, `" "`...) | ||||
| 	buf = appendQuoted(buf, req.UserAgent()) | ||||
| 	buf = append(buf, '"', '\n') | ||||
| 	w.Write(buf) | ||||
| } | ||||
| 
 | ||||
| // CombinedLoggingHandler return a http.Handler that wraps h and logs requests to out in
 | ||||
| // Apache Combined Log Format.
 | ||||
| //
 | ||||
| // See http://httpd.apache.org/docs/2.2/logs.html#combined for a description of this format.
 | ||||
| //
 | ||||
| // LoggingHandler always sets the ident field of the log to -
 | ||||
| func CombinedLoggingHandler(out io.Writer, h http.Handler) http.Handler { | ||||
| 	return combinedLoggingHandler{out, h} | ||||
| } | ||||
| 
 | ||||
| // LoggingHandler return a http.Handler that wraps h and logs requests to out in
 | ||||
| // Apache Common Log Format (CLF).
 | ||||
| //
 | ||||
| // See http://httpd.apache.org/docs/2.2/logs.html#common for a description of this format.
 | ||||
| //
 | ||||
| // LoggingHandler always sets the ident field of the log to -
 | ||||
| func LoggingHandler(out io.Writer, h http.Handler) http.Handler { | ||||
| 	return loggingHandler{out, h} | ||||
| } | ||||
| 
 | ||||
| // isContentType validates the Content-Type header
 | ||||
| // is contentType. That is, its type and subtype match.
 | ||||
| // isContentType validates the Content-Type header matches the supplied
 | ||||
| // contentType. That is, its type and subtype match.
 | ||||
| func isContentType(h http.Header, contentType string) bool { | ||||
| 	ct := h.Get("Content-Type") | ||||
| 	if i := strings.IndexRune(ct, ';'); i != -1 { | ||||
|  | @ -323,9 +90,9 @@ func isContentType(h http.Header, contentType string) bool { | |||
| 	return ct == contentType | ||||
| } | ||||
| 
 | ||||
| // ContentTypeHandler wraps and returns a http.Handler, validating the request content type
 | ||||
| // is acompatible with the contentTypes list.
 | ||||
| // It writes a HTTP 415 error if that fails.
 | ||||
| // ContentTypeHandler wraps and returns a http.Handler, validating the request
 | ||||
| // content type is compatible with the contentTypes list. It writes a HTTP 415
 | ||||
| // error if that fails.
 | ||||
| //
 | ||||
| // Only PUT, POST, and PATCH requests are considered.
 | ||||
| func ContentTypeHandler(h http.Handler, contentTypes ...string) http.Handler { | ||||
|  | @ -354,12 +121,14 @@ const ( | |||
| 	HTTPMethodOverrideFormKey = "_method" | ||||
| ) | ||||
| 
 | ||||
| // HTTPMethodOverrideHandler wraps and returns a http.Handler which checks for the X-HTTP-Method-Override header
 | ||||
| // or the _method form key, and overrides (if valid) request.Method with its value.
 | ||||
| // HTTPMethodOverrideHandler wraps and returns a http.Handler which checks for
 | ||||
| // the X-HTTP-Method-Override header or the _method form key, and overrides (if
 | ||||
| // valid) request.Method with its value.
 | ||||
| //
 | ||||
| // This is especially useful for http clients that don't support many http verbs.
 | ||||
| // It isn't secure to override e.g a GET to a POST, so only POST requests are considered.
 | ||||
| // Likewise, the override method can only be a "write" method: PUT, PATCH or DELETE.
 | ||||
| // This is especially useful for HTTP clients that don't support many http verbs.
 | ||||
| // It isn't secure to override e.g a GET to a POST, so only POST requests are
 | ||||
| // considered.  Likewise, the override method can only be a "write" method: PUT,
 | ||||
| // PATCH or DELETE.
 | ||||
| //
 | ||||
| // Form method takes precedence over header method.
 | ||||
| func HTTPMethodOverrideHandler(h http.Handler) http.Handler { | ||||
|  |  | |||
|  | @ -0,0 +1,244 @@ | |||
| // Copyright 2013 The Gorilla Authors. All rights reserved.
 | ||||
| // Use of this source code is governed by a BSD-style
 | ||||
| // license that can be found in the LICENSE file.
 | ||||
| 
 | ||||
| package handlers | ||||
| 
 | ||||
| import ( | ||||
| 	"io" | ||||
| 	"net" | ||||
| 	"net/http" | ||||
| 	"net/url" | ||||
| 	"strconv" | ||||
| 	"time" | ||||
| 	"unicode/utf8" | ||||
| 
 | ||||
| 	"github.com/felixge/httpsnoop" | ||||
| ) | ||||
| 
 | ||||
| // Logging
 | ||||
| 
 | ||||
| // LogFormatterParams is the structure any formatter will be handed when time to log comes
 | ||||
| type LogFormatterParams struct { | ||||
| 	Request    *http.Request | ||||
| 	URL        url.URL | ||||
| 	TimeStamp  time.Time | ||||
| 	StatusCode int | ||||
| 	Size       int | ||||
| } | ||||
| 
 | ||||
| // LogFormatter gives the signature of the formatter function passed to CustomLoggingHandler
 | ||||
| type LogFormatter func(writer io.Writer, params LogFormatterParams) | ||||
| 
 | ||||
| // loggingHandler is the http.Handler implementation for LoggingHandlerTo and its
 | ||||
| // friends
 | ||||
| 
 | ||||
| type loggingHandler struct { | ||||
| 	writer    io.Writer | ||||
| 	handler   http.Handler | ||||
| 	formatter LogFormatter | ||||
| } | ||||
| 
 | ||||
| func (h loggingHandler) ServeHTTP(w http.ResponseWriter, req *http.Request) { | ||||
| 	t := time.Now() | ||||
| 	logger, w := makeLogger(w) | ||||
| 	url := *req.URL | ||||
| 
 | ||||
| 	h.handler.ServeHTTP(w, req) | ||||
| 	if req.MultipartForm != nil { | ||||
| 		req.MultipartForm.RemoveAll() | ||||
| 	} | ||||
| 
 | ||||
| 	params := LogFormatterParams{ | ||||
| 		Request:    req, | ||||
| 		URL:        url, | ||||
| 		TimeStamp:  t, | ||||
| 		StatusCode: logger.Status(), | ||||
| 		Size:       logger.Size(), | ||||
| 	} | ||||
| 
 | ||||
| 	h.formatter(h.writer, params) | ||||
| } | ||||
| 
 | ||||
| func makeLogger(w http.ResponseWriter) (*responseLogger, http.ResponseWriter) { | ||||
| 	logger := &responseLogger{w: w, status: http.StatusOK} | ||||
| 	return logger, httpsnoop.Wrap(w, httpsnoop.Hooks{ | ||||
| 		Write: func(httpsnoop.WriteFunc) httpsnoop.WriteFunc { | ||||
| 			return logger.Write | ||||
| 		}, | ||||
| 		WriteHeader: func(httpsnoop.WriteHeaderFunc) httpsnoop.WriteHeaderFunc { | ||||
| 			return logger.WriteHeader | ||||
| 		}, | ||||
| 	}) | ||||
| } | ||||
| 
 | ||||
| const lowerhex = "0123456789abcdef" | ||||
| 
 | ||||
| func appendQuoted(buf []byte, s string) []byte { | ||||
| 	var runeTmp [utf8.UTFMax]byte | ||||
| 	for width := 0; len(s) > 0; s = s[width:] { | ||||
| 		r := rune(s[0]) | ||||
| 		width = 1 | ||||
| 		if r >= utf8.RuneSelf { | ||||
| 			r, width = utf8.DecodeRuneInString(s) | ||||
| 		} | ||||
| 		if width == 1 && r == utf8.RuneError { | ||||
| 			buf = append(buf, `\x`...) | ||||
| 			buf = append(buf, lowerhex[s[0]>>4]) | ||||
| 			buf = append(buf, lowerhex[s[0]&0xF]) | ||||
| 			continue | ||||
| 		} | ||||
| 		if r == rune('"') || r == '\\' { // always backslashed
 | ||||
| 			buf = append(buf, '\\') | ||||
| 			buf = append(buf, byte(r)) | ||||
| 			continue | ||||
| 		} | ||||
| 		if strconv.IsPrint(r) { | ||||
| 			n := utf8.EncodeRune(runeTmp[:], r) | ||||
| 			buf = append(buf, runeTmp[:n]...) | ||||
| 			continue | ||||
| 		} | ||||
| 		switch r { | ||||
| 		case '\a': | ||||
| 			buf = append(buf, `\a`...) | ||||
| 		case '\b': | ||||
| 			buf = append(buf, `\b`...) | ||||
| 		case '\f': | ||||
| 			buf = append(buf, `\f`...) | ||||
| 		case '\n': | ||||
| 			buf = append(buf, `\n`...) | ||||
| 		case '\r': | ||||
| 			buf = append(buf, `\r`...) | ||||
| 		case '\t': | ||||
| 			buf = append(buf, `\t`...) | ||||
| 		case '\v': | ||||
| 			buf = append(buf, `\v`...) | ||||
| 		default: | ||||
| 			switch { | ||||
| 			case r < ' ': | ||||
| 				buf = append(buf, `\x`...) | ||||
| 				buf = append(buf, lowerhex[s[0]>>4]) | ||||
| 				buf = append(buf, lowerhex[s[0]&0xF]) | ||||
| 			case r > utf8.MaxRune: | ||||
| 				r = 0xFFFD | ||||
| 				fallthrough | ||||
| 			case r < 0x10000: | ||||
| 				buf = append(buf, `\u`...) | ||||
| 				for s := 12; s >= 0; s -= 4 { | ||||
| 					buf = append(buf, lowerhex[r>>uint(s)&0xF]) | ||||
| 				} | ||||
| 			default: | ||||
| 				buf = append(buf, `\U`...) | ||||
| 				for s := 28; s >= 0; s -= 4 { | ||||
| 					buf = append(buf, lowerhex[r>>uint(s)&0xF]) | ||||
| 				} | ||||
| 			} | ||||
| 		} | ||||
| 	} | ||||
| 	return buf | ||||
| } | ||||
| 
 | ||||
| // buildCommonLogLine builds a log entry for req in Apache Common Log Format.
 | ||||
| // ts is the timestamp with which the entry should be logged.
 | ||||
| // status and size are used to provide the response HTTP status and size.
 | ||||
| func buildCommonLogLine(req *http.Request, url url.URL, ts time.Time, status int, size int) []byte { | ||||
| 	username := "-" | ||||
| 	if url.User != nil { | ||||
| 		if name := url.User.Username(); name != "" { | ||||
| 			username = name | ||||
| 		} | ||||
| 	} | ||||
| 
 | ||||
| 	host, _, err := net.SplitHostPort(req.RemoteAddr) | ||||
| 	if err != nil { | ||||
| 		host = req.RemoteAddr | ||||
| 	} | ||||
| 
 | ||||
| 	uri := req.RequestURI | ||||
| 
 | ||||
| 	// Requests using the CONNECT method over HTTP/2.0 must use
 | ||||
| 	// the authority field (aka r.Host) to identify the target.
 | ||||
| 	// Refer: https://httpwg.github.io/specs/rfc7540.html#CONNECT
 | ||||
| 	if req.ProtoMajor == 2 && req.Method == "CONNECT" { | ||||
| 		uri = req.Host | ||||
| 	} | ||||
| 	if uri == "" { | ||||
| 		uri = url.RequestURI() | ||||
| 	} | ||||
| 
 | ||||
| 	buf := make([]byte, 0, 3*(len(host)+len(username)+len(req.Method)+len(uri)+len(req.Proto)+50)/2) | ||||
| 	buf = append(buf, host...) | ||||
| 	buf = append(buf, " - "...) | ||||
| 	buf = append(buf, username...) | ||||
| 	buf = append(buf, " ["...) | ||||
| 	buf = append(buf, ts.Format("02/Jan/2006:15:04:05 -0700")...) | ||||
| 	buf = append(buf, `] "`...) | ||||
| 	buf = append(buf, req.Method...) | ||||
| 	buf = append(buf, " "...) | ||||
| 	buf = appendQuoted(buf, uri) | ||||
| 	buf = append(buf, " "...) | ||||
| 	buf = append(buf, req.Proto...) | ||||
| 	buf = append(buf, `" `...) | ||||
| 	buf = append(buf, strconv.Itoa(status)...) | ||||
| 	buf = append(buf, " "...) | ||||
| 	buf = append(buf, strconv.Itoa(size)...) | ||||
| 	return buf | ||||
| } | ||||
| 
 | ||||
| // writeLog writes a log entry for req to w in Apache Common Log Format.
 | ||||
| // ts is the timestamp with which the entry should be logged.
 | ||||
| // status and size are used to provide the response HTTP status and size.
 | ||||
| func writeLog(writer io.Writer, params LogFormatterParams) { | ||||
| 	buf := buildCommonLogLine(params.Request, params.URL, params.TimeStamp, params.StatusCode, params.Size) | ||||
| 	buf = append(buf, '\n') | ||||
| 	writer.Write(buf) | ||||
| } | ||||
| 
 | ||||
| // writeCombinedLog writes a log entry for req to w in Apache Combined Log Format.
 | ||||
| // ts is the timestamp with which the entry should be logged.
 | ||||
| // status and size are used to provide the response HTTP status and size.
 | ||||
| func writeCombinedLog(writer io.Writer, params LogFormatterParams) { | ||||
| 	buf := buildCommonLogLine(params.Request, params.URL, params.TimeStamp, params.StatusCode, params.Size) | ||||
| 	buf = append(buf, ` "`...) | ||||
| 	buf = appendQuoted(buf, params.Request.Referer()) | ||||
| 	buf = append(buf, `" "`...) | ||||
| 	buf = appendQuoted(buf, params.Request.UserAgent()) | ||||
| 	buf = append(buf, '"', '\n') | ||||
| 	writer.Write(buf) | ||||
| } | ||||
| 
 | ||||
| // CombinedLoggingHandler return a http.Handler that wraps h and logs requests to out in
 | ||||
| // Apache Combined Log Format.
 | ||||
| //
 | ||||
| // See http://httpd.apache.org/docs/2.2/logs.html#combined for a description of this format.
 | ||||
| //
 | ||||
| // LoggingHandler always sets the ident field of the log to -
 | ||||
| func CombinedLoggingHandler(out io.Writer, h http.Handler) http.Handler { | ||||
| 	return loggingHandler{out, h, writeCombinedLog} | ||||
| } | ||||
| 
 | ||||
| // LoggingHandler return a http.Handler that wraps h and logs requests to out in
 | ||||
| // Apache Common Log Format (CLF).
 | ||||
| //
 | ||||
| // See http://httpd.apache.org/docs/2.2/logs.html#common for a description of this format.
 | ||||
| //
 | ||||
| // LoggingHandler always sets the ident field of the log to -
 | ||||
| //
 | ||||
| // Example:
 | ||||
| //
 | ||||
| //  r := mux.NewRouter()
 | ||||
| //  r.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
 | ||||
| //  	w.Write([]byte("This is a catch-all route"))
 | ||||
| //  })
 | ||||
| //  loggedRouter := handlers.LoggingHandler(os.Stdout, r)
 | ||||
| //  http.ListenAndServe(":1123", loggedRouter)
 | ||||
| //
 | ||||
| func LoggingHandler(out io.Writer, h http.Handler) http.Handler { | ||||
| 	return loggingHandler{out, h, writeLog} | ||||
| } | ||||
| 
 | ||||
| // CustomLoggingHandler provides a way to supply a custom log formatter
 | ||||
| // while taking advantage of the mechanisms in this package
 | ||||
| func CustomLoggingHandler(out io.Writer, h http.Handler, f LogFormatter) http.Handler { | ||||
| 	return loggingHandler{out, h, f} | ||||
| } | ||||
|  | @ -8,9 +8,11 @@ import ( | |||
| 
 | ||||
| var ( | ||||
| 	// De-facto standard header keys.
 | ||||
| 	xForwardedFor   = http.CanonicalHeaderKey("X-Forwarded-For") | ||||
| 	xRealIP         = http.CanonicalHeaderKey("X-Real-IP") | ||||
| 	xForwardedProto = http.CanonicalHeaderKey("X-Forwarded-Scheme") | ||||
| 	xForwardedFor    = http.CanonicalHeaderKey("X-Forwarded-For") | ||||
| 	xForwardedHost   = http.CanonicalHeaderKey("X-Forwarded-Host") | ||||
| 	xForwardedProto  = http.CanonicalHeaderKey("X-Forwarded-Proto") | ||||
| 	xForwardedScheme = http.CanonicalHeaderKey("X-Forwarded-Scheme") | ||||
| 	xRealIP          = http.CanonicalHeaderKey("X-Real-IP") | ||||
| ) | ||||
| 
 | ||||
| var ( | ||||
|  | @ -28,9 +30,9 @@ var ( | |||
| 
 | ||||
| // ProxyHeaders inspects common reverse proxy headers and sets the corresponding
 | ||||
| // fields in the HTTP request struct. These are X-Forwarded-For and X-Real-IP
 | ||||
| // for the remote (client) IP address, X-Forwarded-Proto for the scheme
 | ||||
| // (http|https) and the RFC7239 Forwarded header, which may include both client
 | ||||
| // IPs and schemes.
 | ||||
| // for the remote (client) IP address, X-Forwarded-Proto or X-Forwarded-Scheme
 | ||||
| // for the scheme (http|https), X-Forwarded-Host for the host and the RFC7239
 | ||||
| // Forwarded header, which may include both client IPs and schemes.
 | ||||
| //
 | ||||
| // NOTE: This middleware should only be used when behind a reverse
 | ||||
| // proxy like nginx, HAProxy or Apache. Reverse proxies that don't (or are
 | ||||
|  | @ -49,7 +51,10 @@ func ProxyHeaders(h http.Handler) http.Handler { | |||
| 		if scheme := getScheme(r); scheme != "" { | ||||
| 			r.URL.Scheme = scheme | ||||
| 		} | ||||
| 
 | ||||
| 		// Set the host with the value passed by the proxy
 | ||||
| 		if r.Header.Get(xForwardedHost) != "" { | ||||
| 			r.Host = r.Header.Get(xForwardedHost) | ||||
| 		} | ||||
| 		// Call the next handler in the chain.
 | ||||
| 		h.ServeHTTP(w, r) | ||||
| 	} | ||||
|  | @ -99,7 +104,9 @@ func getScheme(r *http.Request) string { | |||
| 	// Retrieve the scheme from X-Forwarded-Proto.
 | ||||
| 	if proto := r.Header.Get(xForwardedProto); proto != "" { | ||||
| 		scheme = strings.ToLower(proto) | ||||
| 	} else if proto := r.Header.Get(forwarded); proto != "" { | ||||
| 	} else if proto = r.Header.Get(xForwardedScheme); proto != "" { | ||||
| 		scheme = strings.ToLower(proto) | ||||
| 	} else if proto = r.Header.Get(forwarded); proto != "" { | ||||
| 		// match should contain at least two elements if the protocol was
 | ||||
| 		// specified in the Forwarded header. The first element will always be
 | ||||
| 		// the 'proto=' capture, which we ignore. In the case of multiple proto
 | ||||
|  |  | |||
|  | @ -0,0 +1,96 @@ | |||
| package handlers | ||||
| 
 | ||||
| import ( | ||||
| 	"log" | ||||
| 	"net/http" | ||||
| 	"runtime/debug" | ||||
| ) | ||||
| 
 | ||||
| // RecoveryHandlerLogger is an interface used by the recovering handler to print logs.
 | ||||
| type RecoveryHandlerLogger interface { | ||||
| 	Println(...interface{}) | ||||
| } | ||||
| 
 | ||||
| type recoveryHandler struct { | ||||
| 	handler    http.Handler | ||||
| 	logger     RecoveryHandlerLogger | ||||
| 	printStack bool | ||||
| } | ||||
| 
 | ||||
| // RecoveryOption provides a functional approach to define
 | ||||
| // configuration for a handler; such as setting the logging
 | ||||
| // whether or not to print stack traces on panic.
 | ||||
| type RecoveryOption func(http.Handler) | ||||
| 
 | ||||
| func parseRecoveryOptions(h http.Handler, opts ...RecoveryOption) http.Handler { | ||||
| 	for _, option := range opts { | ||||
| 		option(h) | ||||
| 	} | ||||
| 
 | ||||
| 	return h | ||||
| } | ||||
| 
 | ||||
| // RecoveryHandler is HTTP middleware that recovers from a panic,
 | ||||
| // logs the panic, writes http.StatusInternalServerError, and
 | ||||
| // continues to the next handler.
 | ||||
| //
 | ||||
| // Example:
 | ||||
| //
 | ||||
| //  r := mux.NewRouter()
 | ||||
| //  r.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
 | ||||
| //  	panic("Unexpected error!")
 | ||||
| //  })
 | ||||
| //
 | ||||
| //  http.ListenAndServe(":1123", handlers.RecoveryHandler()(r))
 | ||||
| func RecoveryHandler(opts ...RecoveryOption) func(h http.Handler) http.Handler { | ||||
| 	return func(h http.Handler) http.Handler { | ||||
| 		r := &recoveryHandler{handler: h} | ||||
| 		return parseRecoveryOptions(r, opts...) | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| // RecoveryLogger is a functional option to override
 | ||||
| // the default logger
 | ||||
| func RecoveryLogger(logger RecoveryHandlerLogger) RecoveryOption { | ||||
| 	return func(h http.Handler) { | ||||
| 		r := h.(*recoveryHandler) | ||||
| 		r.logger = logger | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| // PrintRecoveryStack is a functional option to enable
 | ||||
| // or disable printing stack traces on panic.
 | ||||
| func PrintRecoveryStack(print bool) RecoveryOption { | ||||
| 	return func(h http.Handler) { | ||||
| 		r := h.(*recoveryHandler) | ||||
| 		r.printStack = print | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| func (h recoveryHandler) ServeHTTP(w http.ResponseWriter, req *http.Request) { | ||||
| 	defer func() { | ||||
| 		if err := recover(); err != nil { | ||||
| 			w.WriteHeader(http.StatusInternalServerError) | ||||
| 			h.log(err) | ||||
| 		} | ||||
| 	}() | ||||
| 
 | ||||
| 	h.handler.ServeHTTP(w, req) | ||||
| } | ||||
| 
 | ||||
| func (h recoveryHandler) log(v ...interface{}) { | ||||
| 	if h.logger != nil { | ||||
| 		h.logger.Println(v...) | ||||
| 	} else { | ||||
| 		log.Println(v...) | ||||
| 	} | ||||
| 
 | ||||
| 	if h.printStack { | ||||
| 		stack := string(debug.Stack()) | ||||
| 		if h.logger != nil { | ||||
| 			h.logger.Println(stack) | ||||
| 		} else { | ||||
| 			log.Println(stack) | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
|  | @ -79,11 +79,13 @@ github.com/docker/go-events | |||
| github.com/docker/go-metrics | ||||
| # github.com/docker/libtrust v0.0.0-20150114040149-fa567046d9b1 | ||||
| github.com/docker/libtrust | ||||
| # github.com/felixge/httpsnoop v1.0.1 | ||||
| github.com/felixge/httpsnoop | ||||
| # github.com/golang/protobuf v1.3.2 | ||||
| github.com/golang/protobuf/proto | ||||
| # github.com/gomodule/redigo v1.8.2 | ||||
| github.com/gomodule/redigo/redis | ||||
| # github.com/gorilla/handlers v0.0.0-20150720190736-60c7bfde3e33 | ||||
| # github.com/gorilla/handlers v1.5.1 | ||||
| github.com/gorilla/handlers | ||||
| # github.com/gorilla/mux v1.8.0 | ||||
| github.com/gorilla/mux | ||||
|  |  | |||
		Loading…
	
		Reference in New Issue