├── .idea
├── misc.xml
├── modules.xml
├── periodic-table-api-go.iml
├── vcs.xml
└── workspace.xml
├── Godeps
├── Godeps.json
└── Readme
├── README.md
├── data.json
├── main.go
└── vendor
└── github.com
└── gorilla
├── handlers
├── .travis.yml
├── LICENSE
├── README.md
├── canonical.go
├── compress.go
├── cors.go
├── doc.go
├── go.mod
├── handlers.go
├── handlers_go18.go
├── handlers_pre18.go
├── logging.go
├── proxy_headers.go
└── recovery.go
└── mux
├── .travis.yml
├── AUTHORS
├── ISSUE_TEMPLATE.md
├── LICENSE
├── README.md
├── context.go
├── doc.go
├── go.mod
├── middleware.go
├── mux.go
├── regexp.go
├── route.go
└── test_helpers.go
/.idea/misc.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/.idea/modules.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/.idea/periodic-table-api-go.iml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/.idea/vcs.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/.idea/workspace.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 |
74 |
75 |
76 |
77 |
78 |
79 |
80 |
81 |
82 |
83 |
84 |
85 |
86 |
87 |
88 |
89 |
90 |
91 |
92 |
93 |
94 |
95 |
96 |
97 |
98 |
99 |
100 |
101 |
102 |
103 |
104 |
105 |
106 |
107 |
108 |
109 |
110 |
111 |
112 |
113 |
114 |
115 |
116 |
117 |
118 |
119 |
120 |
121 |
122 |
123 |
124 |
125 |
126 |
--------------------------------------------------------------------------------
/Godeps/Godeps.json:
--------------------------------------------------------------------------------
1 | {
2 | "ImportPath": "periodic-table-api-go",
3 | "GoVersion": "go1.12",
4 | "GodepVersion": "v80",
5 | "Deps": [
6 | {
7 | "ImportPath": "github.com/gorilla/handlers",
8 | "Comment": "v1.4.0-6-gac6d24f",
9 | "Rev": "ac6d24f88de4584385a0cb3a88f953d08a2f7a05"
10 | },
11 | {
12 | "ImportPath": "github.com/gorilla/mux",
13 | "Comment": "v1.7.1",
14 | "Rev": "c5c6c98bc25355028a63748a498942a6398ccd22"
15 | }
16 | ]
17 | }
18 |
--------------------------------------------------------------------------------
/Godeps/Readme:
--------------------------------------------------------------------------------
1 | This directory tree is generated automatically by godep.
2 |
3 | Please do not edit.
4 |
5 | See https://github.com/tools/godep for more information.
6 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # periodic-table-api
2 | API to fetch elements of the periodic table in JSON format. Developed using Golang for faster execution
3 |
--------------------------------------------------------------------------------
/main.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "encoding/json"
5 | "github.com/gorilla/handlers"
6 | "github.com/gorilla/mux"
7 | "io/ioutil"
8 | "log"
9 | "net/http"
10 | "os"
11 | "strings"
12 | )
13 |
14 | var element []elements
15 |
16 | type elements struct {
17 | AtomicMass string `json:"atomicMass"` //0
18 | AtomicNumber string `json:"atomicNumber"` //1
19 | AtomicRadius string `json:"atomicRadius"` //2
20 | BoilingPoint string `json:"boilingPoint"` //3
21 | BondingType string `json:"bondingType"` //4
22 | CpkHexColor string `json:"cpkHexColor"` //5
23 | Density string `json:"density"` //6
24 | ElectronAffinity string `json:"electronAffinity"` //7
25 | Electronegativity string `json:"electronegativity"` //8
26 | ElectronicConfiguration string `json:"electronicConfiguration"` //9
27 | GroupBlock string `json:"groupBlock"` //10
28 | IonRadius string `json:"ionRadius"` //11
29 | IonizationEnergy string `json:"ionizationEnergy"` //12
30 | MeltingPoint string `json:"meltingPoint"` //13
31 | Name string `json:"name"` //14
32 | OxidationStates string `json:"oxidationStates"` //15
33 | StandardState string `json:"standardState"` //16
34 | Symbol string `json:"symbol"` //17
35 | VanDelWaalsRadius string `json:"vanDelWaalsRadius"` //18
36 | YearDiscovered string `json:"yearDiscovered"` //19
37 | }
38 |
39 | func cleanup() {
40 | if r:=recover(); r!=nil {
41 | log.Fatal(r)
42 | }
43 | }
44 |
45 | func allElements(w http.ResponseWriter, _ *http.Request) {
46 | defer cleanup()
47 | w.Header().Set("Content-Type", "application/json")
48 |
49 | err := json.NewEncoder(w).Encode(element)
50 | if err!=nil {
51 | panic(err.Error())
52 | }
53 | }
54 |
55 | func atomicNumber(w http.ResponseWriter, r *http.Request) {
56 | defer cleanup()
57 | w.Header().Set("Content-Type", "application/json")
58 |
59 | vars := mux.Vars(r)
60 | atomicNumber := vars["atomicNumber"]
61 | flag := false
62 | var finalData interface{}
63 |
64 | for _, data := range element {
65 | atomicNumber = strings.ToLower(atomicNumber)
66 | DataAtomicNumber := strings.ToLower(data.AtomicNumber)
67 | if strings.Compare(atomicNumber, DataAtomicNumber) == 0 {
68 | finalData = data
69 | flag = true
70 | break
71 | }
72 | }
73 |
74 | if flag {
75 | err := json.NewEncoder(w).Encode(finalData)
76 | if err!=nil {
77 | panic(err.Error())
78 | }
79 | } else {
80 | err := make(map[string]interface{})
81 | err["status"] = false
82 | err["message"] = "Not Found"
83 | err1 := json.NewEncoder(w).Encode(err)
84 | if err1!=nil {
85 | panic(err1.Error())
86 | }
87 | }
88 | }
89 |
90 | func atomicName(w http.ResponseWriter, r *http.Request) {
91 | defer cleanup()
92 | w.Header().Set("Content-Type", "application/json")
93 |
94 | vars := mux.Vars(r)
95 | atomicName := vars["atomicName"]
96 | flag := false
97 | var finalData interface{}
98 |
99 | for _, data := range element {
100 | atomicName = strings.ToLower(atomicName)
101 | DataAtomicName := strings.ToLower(data.Name)
102 | if strings.Compare(atomicName, DataAtomicName) == 0 {
103 | finalData = data
104 | flag = true
105 | break
106 | }
107 | }
108 |
109 | if flag {
110 | err := json.NewEncoder(w).Encode(finalData)
111 | if err!=nil {
112 | panic(err.Error())
113 | }
114 | } else {
115 | err := make(map[string]interface{})
116 | err["status"] = false
117 | err["message"] = "Not Found"
118 | err1 := json.NewEncoder(w).Encode(err)
119 | if err1!=nil {
120 | panic(err1.Error())
121 | }
122 | }
123 | }
124 |
125 | func atomicSymbol(w http.ResponseWriter, r *http.Request) {
126 | defer cleanup()
127 | w.Header().Set("Content-Type", "application/json")
128 |
129 | vars := mux.Vars(r)
130 | atomicSymbol := vars["atomicSymbol"]
131 | flag := false
132 | var finalData interface{}
133 |
134 | for _, data := range element {
135 | atomicSymbol = strings.ToLower(atomicSymbol)
136 | DataAtomicSymbol := strings.ToLower(data.Symbol)
137 | if strings.Compare(atomicSymbol, DataAtomicSymbol) == 0 {
138 | finalData = data
139 | flag = true
140 | break
141 | }
142 | }
143 |
144 | if flag {
145 | err := json.NewEncoder(w).Encode(finalData)
146 | if err!=nil {
147 | panic(err.Error())
148 | }
149 | } else {
150 | err := make(map[string]interface{})
151 | err["status"] = false
152 | err["message"] = "Not Found"
153 | err1 := json.NewEncoder(w).Encode(err)
154 | if err1!=nil {
155 | panic(err1.Error())
156 | }
157 | }
158 | }
159 |
160 | func atomicBonding(w http.ResponseWriter, r *http.Request) {
161 | defer cleanup()
162 | w.Header().Set("Content-Type", "application/json")
163 |
164 | vars := mux.Vars(r)
165 | atomicBonding := vars["atomicBonding"]
166 | flag := false
167 | var finalData []interface{}
168 |
169 | for _, data := range element {
170 | atomicBonding = strings.ToLower(atomicBonding)
171 | DataAtomicBonding := strings.ToLower(data.BondingType)
172 | if strings.Compare(atomicBonding, DataAtomicBonding) == 0 {
173 | finalData = append(finalData, data)
174 | flag = true
175 | }
176 | }
177 |
178 | if flag {
179 | err := json.NewEncoder(w).Encode(finalData)
180 | if err!=nil {
181 | panic(err.Error())
182 | }
183 | } else {
184 | err := make(map[string]interface{})
185 | err["status"] = false
186 | err["message"] = "Not Found"
187 | err1 := json.NewEncoder(w).Encode(err)
188 | if err1!=nil {
189 | panic(err1.Error())
190 | }
191 | }
192 | }
193 |
194 | func atomicGroup(w http.ResponseWriter, r *http.Request) {
195 | defer cleanup()
196 | w.Header().Set("Content-Type", "application/json")
197 |
198 | vars := mux.Vars(r)
199 | atomicGroup := vars["atomicGroup"]
200 | flag := false
201 | var finalData []interface{}
202 |
203 | for _, data := range element {
204 | atomicGroup = strings.ToLower(atomicGroup)
205 | DataAtomicGroup := strings.ToLower(data.GroupBlock)
206 | if strings.Compare(atomicGroup, DataAtomicGroup) == 0 {
207 | finalData = append(finalData, data)
208 | flag = true
209 | }
210 | }
211 |
212 | if flag {
213 | err := json.NewEncoder(w).Encode(finalData)
214 | if err!=nil {
215 | panic(err.Error())
216 | }
217 | } else {
218 | err := make(map[string]interface{})
219 | err["status"] = false
220 | err["message"] = "Not Found"
221 | err1 := json.NewEncoder(w).Encode(err)
222 | if err1!=nil {
223 | panic(err1.Error())
224 | }
225 | }
226 | }
227 |
228 | func atomicState(w http.ResponseWriter, r *http.Request) {
229 | defer cleanup()
230 | w.Header().Set("Content-Type", "application/json")
231 |
232 | vars := mux.Vars(r)
233 | atomicState := vars["atomicState"]
234 | flag := false
235 | var finalData []interface{}
236 |
237 | for _, data := range element {
238 | atomicState = strings.ToLower(atomicState)
239 | DataAtomicState := strings.ToLower(data.StandardState)
240 | if strings.Compare(atomicState, DataAtomicState) == 0 {
241 | finalData = append(finalData, data)
242 | flag = true
243 | }
244 | }
245 |
246 | if flag {
247 | _ = json.NewEncoder(w).Encode(finalData)
248 | } else {
249 | err := make(map[string]interface{})
250 | err["status"] = false
251 | err["message"] = "Not Found"
252 | _ = json.NewEncoder(w).Encode(err)
253 | }
254 | }
255 |
256 | func main() {
257 | defer cleanup()
258 |
259 | // Reading JSON file
260 | jsonFile, err := os.Open("data.json")
261 | if err != nil {
262 | panic(err.Error())
263 | }
264 | defer jsonFile.Close()
265 | byteValue, err := ioutil.ReadAll(jsonFile)
266 | if err!=nil {
267 | panic(err.Error())
268 | }
269 | err = json.Unmarshal(byteValue, &element)
270 | if err!=nil {
271 | panic(err.Error())
272 | }
273 |
274 | // Routers
275 | router := mux.NewRouter()
276 | router.HandleFunc("/", allElements).Methods("GET")
277 | router.HandleFunc("/atomicNumber/{atomicNumber}", atomicNumber).Methods("GET")
278 | router.HandleFunc("/atomicName/{atomicName}", atomicName).Methods("GET")
279 | router.HandleFunc("/atomicSymbol/{atomicSymbol}", atomicSymbol).Methods("GET")
280 | router.HandleFunc("/atomicBonding/{atomicBonding}", atomicBonding).Methods("GET")
281 | router.HandleFunc("/atomicGroup/{atomicGroup}", atomicGroup).Methods("GET")
282 | router.HandleFunc("/atomicState/{atomicState}", atomicState).Methods("GET")
283 |
284 | //CORS Headers
285 | headersOk := handlers.AllowedHeaders([]string{"Authorization"})
286 | originsOk := handlers.AllowedOrigins([]string{"*"})
287 | methodsOk := handlers.AllowedMethods([]string{"GET"})
288 |
289 | //Starting Server
290 | port := ":" + os.Getenv("PORT")
291 | err = http.ListenAndServe(port, handlers.CORS(originsOk, headersOk, methodsOk)(router))
292 | if err != nil {
293 | panic(err.Error())
294 | }
295 | }
296 |
--------------------------------------------------------------------------------
/vendor/github.com/gorilla/handlers/.travis.yml:
--------------------------------------------------------------------------------
1 | language: go
2 | sudo: false
3 |
4 | matrix:
5 | include:
6 | - go: 1.4
7 | - go: 1.5
8 | - go: 1.6
9 | - go: 1.7
10 | - go: 1.8
11 | - go: tip
12 | allow_failures:
13 | - go: tip
14 |
15 | script:
16 | - go get -t -v ./...
17 | - diff -u <(echo -n) <(gofmt -d .)
18 | - go vet $(go list ./... | grep -v /vendor/)
19 | - go test -v -race ./...
20 |
21 |
--------------------------------------------------------------------------------
/vendor/github.com/gorilla/handlers/LICENSE:
--------------------------------------------------------------------------------
1 | Copyright (c) 2013 The Gorilla Handlers Authors. All rights reserved.
2 |
3 | Redistribution and use in source and binary forms, with or without
4 | modification, are permitted provided that the following conditions are met:
5 |
6 | Redistributions of source code must retain the above copyright notice, this
7 | list of conditions and the following disclaimer.
8 |
9 | Redistributions in binary form must reproduce the above copyright notice,
10 | this list of conditions and the following disclaimer in the documentation
11 | and/or other materials provided with the distribution.
12 |
13 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
14 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
15 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
16 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
17 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
18 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
19 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
20 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
21 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
22 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
23 |
--------------------------------------------------------------------------------
/vendor/github.com/gorilla/handlers/README.md:
--------------------------------------------------------------------------------
1 | gorilla/handlers
2 | ================
3 | [](https://godoc.org/github.com/gorilla/handlers) [](https://travis-ci.org/gorilla/handlers)
4 | [](https://sourcegraph.com/github.com/gorilla/handlers?badge)
5 |
6 |
7 | Package handlers is a collection of handlers (aka "HTTP middleware") for use
8 | with Go's `net/http` package (or any framework supporting `http.Handler`), including:
9 |
10 | * [**LoggingHandler**](https://godoc.org/github.com/gorilla/handlers#LoggingHandler) for logging HTTP requests in the Apache [Common Log
11 | Format](http://httpd.apache.org/docs/2.2/logs.html#common).
12 | * [**CombinedLoggingHandler**](https://godoc.org/github.com/gorilla/handlers#CombinedLoggingHandler) for logging HTTP requests in the Apache [Combined Log
13 | Format](http://httpd.apache.org/docs/2.2/logs.html#combined) commonly used by
14 | both Apache and nginx.
15 | * [**CompressHandler**](https://godoc.org/github.com/gorilla/handlers#CompressHandler) for gzipping responses.
16 | * [**ContentTypeHandler**](https://godoc.org/github.com/gorilla/handlers#ContentTypeHandler) for validating requests against a list of accepted
17 | content types.
18 | * [**MethodHandler**](https://godoc.org/github.com/gorilla/handlers#MethodHandler) for matching HTTP methods against handlers in a
19 | `map[string]http.Handler`
20 | * [**ProxyHeaders**](https://godoc.org/github.com/gorilla/handlers#ProxyHeaders) for populating `r.RemoteAddr` and `r.URL.Scheme` based on the
21 | `X-Forwarded-For`, `X-Real-IP`, `X-Forwarded-Proto` and RFC7239 `Forwarded`
22 | headers when running a Go server behind a HTTP reverse proxy.
23 | * [**CanonicalHost**](https://godoc.org/github.com/gorilla/handlers#CanonicalHost) for re-directing to the preferred host when handling multiple
24 | domains (i.e. multiple CNAME aliases).
25 | * [**RecoveryHandler**](https://godoc.org/github.com/gorilla/handlers#RecoveryHandler) for recovering from unexpected panics.
26 |
27 | Other handlers are documented [on the Gorilla
28 | website](https://www.gorillatoolkit.org/pkg/handlers).
29 |
30 | ## Example
31 |
32 | A simple example using `handlers.LoggingHandler` and `handlers.CompressHandler`:
33 |
34 | ```go
35 | import (
36 | "net/http"
37 | "github.com/gorilla/handlers"
38 | )
39 |
40 | func main() {
41 | r := http.NewServeMux()
42 |
43 | // Only log requests to our admin dashboard to stdout
44 | r.Handle("/admin", handlers.LoggingHandler(os.Stdout, http.HandlerFunc(ShowAdminDashboard)))
45 | r.HandleFunc("/", ShowIndex)
46 |
47 | // Wrap our server with our gzip handler to gzip compress all responses.
48 | http.ListenAndServe(":8000", handlers.CompressHandler(r))
49 | }
50 | ```
51 |
52 | ## License
53 |
54 | BSD licensed. See the included LICENSE file for details.
55 |
56 |
--------------------------------------------------------------------------------
/vendor/github.com/gorilla/handlers/canonical.go:
--------------------------------------------------------------------------------
1 | package handlers
2 |
3 | import (
4 | "net/http"
5 | "net/url"
6 | "strings"
7 | )
8 |
9 | type canonical struct {
10 | h http.Handler
11 | domain string
12 | code int
13 | }
14 |
15 | // CanonicalHost is HTTP middleware that re-directs requests to the canonical
16 | // domain. It accepts a domain and a status code (e.g. 301 or 302) and
17 | // re-directs clients to this domain. The existing request path is maintained.
18 | //
19 | // Note: If the provided domain is considered invalid by url.Parse or otherwise
20 | // returns an empty scheme or host, clients are not re-directed.
21 | //
22 | // Example:
23 | //
24 | // r := mux.NewRouter()
25 | // canonical := handlers.CanonicalHost("http://www.gorillatoolkit.org", 302)
26 | // r.HandleFunc("/route", YourHandler)
27 | //
28 | // log.Fatal(http.ListenAndServe(":7000", canonical(r)))
29 | //
30 | func CanonicalHost(domain string, code int) func(h http.Handler) http.Handler {
31 | fn := func(h http.Handler) http.Handler {
32 | return canonical{h, domain, code}
33 | }
34 |
35 | return fn
36 | }
37 |
38 | func (c canonical) ServeHTTP(w http.ResponseWriter, r *http.Request) {
39 | dest, err := url.Parse(c.domain)
40 | if err != nil {
41 | // Call the next handler if the provided domain fails to parse.
42 | c.h.ServeHTTP(w, r)
43 | return
44 | }
45 |
46 | if dest.Scheme == "" || dest.Host == "" {
47 | // Call the next handler if the scheme or host are empty.
48 | // Note that url.Parse won't fail on in this case.
49 | c.h.ServeHTTP(w, r)
50 | return
51 | }
52 |
53 | if !strings.EqualFold(cleanHost(r.Host), dest.Host) {
54 | // Re-build the destination URL
55 | dest := dest.Scheme + "://" + dest.Host + r.URL.Path
56 | if r.URL.RawQuery != "" {
57 | dest += "?" + r.URL.RawQuery
58 | }
59 | http.Redirect(w, r, dest, c.code)
60 | return
61 | }
62 |
63 | c.h.ServeHTTP(w, r)
64 | }
65 |
66 | // cleanHost cleans invalid Host headers by stripping anything after '/' or ' '.
67 | // This is backported from Go 1.5 (in response to issue #11206) and attempts to
68 | // mitigate malformed Host headers that do not match the format in RFC7230.
69 | func cleanHost(in string) string {
70 | if i := strings.IndexAny(in, " /"); i != -1 {
71 | return in[:i]
72 | }
73 | return in
74 | }
75 |
--------------------------------------------------------------------------------
/vendor/github.com/gorilla/handlers/compress.go:
--------------------------------------------------------------------------------
1 | // Copyright 2013 The Gorilla Authors. All rights reserved.
2 | // Use of this source code is governed by a BSD-style
3 | // license that can be found in the LICENSE file.
4 |
5 | package handlers
6 |
7 | import (
8 | "compress/flate"
9 | "compress/gzip"
10 | "io"
11 | "net/http"
12 | "strings"
13 | )
14 |
15 | type compressResponseWriter struct {
16 | io.Writer
17 | http.ResponseWriter
18 | http.Hijacker
19 | http.Flusher
20 | http.CloseNotifier
21 | }
22 |
23 | func (w *compressResponseWriter) WriteHeader(c int) {
24 | w.ResponseWriter.Header().Del("Content-Length")
25 | w.ResponseWriter.WriteHeader(c)
26 | }
27 |
28 | func (w *compressResponseWriter) Header() http.Header {
29 | return w.ResponseWriter.Header()
30 | }
31 |
32 | func (w *compressResponseWriter) Write(b []byte) (int, error) {
33 | h := w.ResponseWriter.Header()
34 | if h.Get("Content-Type") == "" {
35 | h.Set("Content-Type", http.DetectContentType(b))
36 | }
37 | h.Del("Content-Length")
38 |
39 | return w.Writer.Write(b)
40 | }
41 |
42 | type flusher interface {
43 | Flush() error
44 | }
45 |
46 | func (w *compressResponseWriter) Flush() {
47 | // Flush compressed data if compressor supports it.
48 | if f, ok := w.Writer.(flusher); ok {
49 | f.Flush()
50 | }
51 | // Flush HTTP response.
52 | if w.Flusher != nil {
53 | w.Flusher.Flush()
54 | }
55 | }
56 |
57 | // CompressHandler gzip compresses HTTP responses for clients that support it
58 | // via the 'Accept-Encoding' header.
59 | //
60 | // Compressing TLS traffic may leak the page contents to an attacker if the
61 | // page contains user input: http://security.stackexchange.com/a/102015/12208
62 | func CompressHandler(h http.Handler) http.Handler {
63 | return CompressHandlerLevel(h, gzip.DefaultCompression)
64 | }
65 |
66 | // CompressHandlerLevel gzip compresses HTTP responses with specified compression level
67 | // for clients that support it via the 'Accept-Encoding' header.
68 | //
69 | // The compression level should be gzip.DefaultCompression, gzip.NoCompression,
70 | // or any integer value between gzip.BestSpeed and gzip.BestCompression inclusive.
71 | // gzip.DefaultCompression is used in case of invalid compression level.
72 | func CompressHandlerLevel(h http.Handler, level int) http.Handler {
73 | if level < gzip.DefaultCompression || level > gzip.BestCompression {
74 | level = gzip.DefaultCompression
75 | }
76 |
77 | return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
78 | L:
79 | for _, enc := range strings.Split(r.Header.Get("Accept-Encoding"), ",") {
80 | switch strings.TrimSpace(enc) {
81 | case "gzip":
82 | w.Header().Set("Content-Encoding", "gzip")
83 | w.Header().Add("Vary", "Accept-Encoding")
84 |
85 | gw, _ := gzip.NewWriterLevel(w, level)
86 | defer gw.Close()
87 |
88 | h, hok := w.(http.Hijacker)
89 | if !hok { /* w is not Hijacker... oh well... */
90 | h = nil
91 | }
92 |
93 | f, fok := w.(http.Flusher)
94 | if !fok {
95 | f = nil
96 | }
97 |
98 | cn, cnok := w.(http.CloseNotifier)
99 | if !cnok {
100 | cn = nil
101 | }
102 |
103 | w = &compressResponseWriter{
104 | Writer: gw,
105 | ResponseWriter: w,
106 | Hijacker: h,
107 | Flusher: f,
108 | CloseNotifier: cn,
109 | }
110 |
111 | break L
112 | case "deflate":
113 | w.Header().Set("Content-Encoding", "deflate")
114 | w.Header().Add("Vary", "Accept-Encoding")
115 |
116 | fw, _ := flate.NewWriter(w, level)
117 | defer fw.Close()
118 |
119 | h, hok := w.(http.Hijacker)
120 | if !hok { /* w is not Hijacker... oh well... */
121 | h = nil
122 | }
123 |
124 | f, fok := w.(http.Flusher)
125 | if !fok {
126 | f = nil
127 | }
128 |
129 | cn, cnok := w.(http.CloseNotifier)
130 | if !cnok {
131 | cn = nil
132 | }
133 |
134 | w = &compressResponseWriter{
135 | Writer: fw,
136 | ResponseWriter: w,
137 | Hijacker: h,
138 | Flusher: f,
139 | CloseNotifier: cn,
140 | }
141 |
142 | break L
143 | }
144 | }
145 |
146 | h.ServeHTTP(w, r)
147 | })
148 | }
149 |
--------------------------------------------------------------------------------
/vendor/github.com/gorilla/handlers/cors.go:
--------------------------------------------------------------------------------
1 | package handlers
2 |
3 | import (
4 | "net/http"
5 | "strconv"
6 | "strings"
7 | )
8 |
9 | // CORSOption represents a functional option for configuring the CORS middleware.
10 | type CORSOption func(*cors) error
11 |
12 | type cors struct {
13 | h http.Handler
14 | allowedHeaders []string
15 | allowedMethods []string
16 | allowedOrigins []string
17 | allowedOriginValidator OriginValidator
18 | exposedHeaders []string
19 | maxAge int
20 | ignoreOptions bool
21 | allowCredentials bool
22 | optionStatusCode int
23 | }
24 |
25 | // OriginValidator takes an origin string and returns whether or not that origin is allowed.
26 | type OriginValidator func(string) bool
27 |
28 | var (
29 | defaultCorsOptionStatusCode = 200
30 | defaultCorsMethods = []string{"GET", "HEAD", "POST"}
31 | defaultCorsHeaders = []string{"Accept", "Accept-Language", "Content-Language", "Origin"}
32 | // (WebKit/Safari v9 sends the Origin header by default in AJAX requests)
33 | )
34 |
35 | const (
36 | corsOptionMethod string = "OPTIONS"
37 | corsAllowOriginHeader string = "Access-Control-Allow-Origin"
38 | corsExposeHeadersHeader string = "Access-Control-Expose-Headers"
39 | corsMaxAgeHeader string = "Access-Control-Max-Age"
40 | corsAllowMethodsHeader string = "Access-Control-Allow-Methods"
41 | corsAllowHeadersHeader string = "Access-Control-Allow-Headers"
42 | corsAllowCredentialsHeader string = "Access-Control-Allow-Credentials"
43 | corsRequestMethodHeader string = "Access-Control-Request-Method"
44 | corsRequestHeadersHeader string = "Access-Control-Request-Headers"
45 | corsOriginHeader string = "Origin"
46 | corsVaryHeader string = "Vary"
47 | corsOriginMatchAll string = "*"
48 | )
49 |
50 | func (ch *cors) ServeHTTP(w http.ResponseWriter, r *http.Request) {
51 | origin := r.Header.Get(corsOriginHeader)
52 | if !ch.isOriginAllowed(origin) {
53 | if r.Method != corsOptionMethod || ch.ignoreOptions {
54 | ch.h.ServeHTTP(w, r)
55 | }
56 |
57 | return
58 | }
59 |
60 | if r.Method == corsOptionMethod {
61 | if ch.ignoreOptions {
62 | ch.h.ServeHTTP(w, r)
63 | return
64 | }
65 |
66 | if _, ok := r.Header[corsRequestMethodHeader]; !ok {
67 | w.WriteHeader(http.StatusBadRequest)
68 | return
69 | }
70 |
71 | method := r.Header.Get(corsRequestMethodHeader)
72 | if !ch.isMatch(method, ch.allowedMethods) {
73 | w.WriteHeader(http.StatusMethodNotAllowed)
74 | return
75 | }
76 |
77 | requestHeaders := strings.Split(r.Header.Get(corsRequestHeadersHeader), ",")
78 | allowedHeaders := []string{}
79 | for _, v := range requestHeaders {
80 | canonicalHeader := http.CanonicalHeaderKey(strings.TrimSpace(v))
81 | if canonicalHeader == "" || ch.isMatch(canonicalHeader, defaultCorsHeaders) {
82 | continue
83 | }
84 |
85 | if !ch.isMatch(canonicalHeader, ch.allowedHeaders) {
86 | w.WriteHeader(http.StatusForbidden)
87 | return
88 | }
89 |
90 | allowedHeaders = append(allowedHeaders, canonicalHeader)
91 | }
92 |
93 | if len(allowedHeaders) > 0 {
94 | w.Header().Set(corsAllowHeadersHeader, strings.Join(allowedHeaders, ","))
95 | }
96 |
97 | if ch.maxAge > 0 {
98 | w.Header().Set(corsMaxAgeHeader, strconv.Itoa(ch.maxAge))
99 | }
100 |
101 | if !ch.isMatch(method, defaultCorsMethods) {
102 | w.Header().Set(corsAllowMethodsHeader, method)
103 | }
104 | } else {
105 | if len(ch.exposedHeaders) > 0 {
106 | w.Header().Set(corsExposeHeadersHeader, strings.Join(ch.exposedHeaders, ","))
107 | }
108 | }
109 |
110 | if ch.allowCredentials {
111 | w.Header().Set(corsAllowCredentialsHeader, "true")
112 | }
113 |
114 | if len(ch.allowedOrigins) > 1 {
115 | w.Header().Set(corsVaryHeader, corsOriginHeader)
116 | }
117 |
118 | returnOrigin := origin
119 | if ch.allowedOriginValidator == nil && len(ch.allowedOrigins) == 0 {
120 | returnOrigin = "*"
121 | } else {
122 | for _, o := range ch.allowedOrigins {
123 | // A configuration of * is different than explicitly setting an allowed
124 | // origin. Returning arbitrary origin headers in an access control allow
125 | // origin header is unsafe and is not required by any use case.
126 | if o == corsOriginMatchAll {
127 | returnOrigin = "*"
128 | break
129 | }
130 | }
131 | }
132 | w.Header().Set(corsAllowOriginHeader, returnOrigin)
133 |
134 | if r.Method == corsOptionMethod {
135 | w.WriteHeader(ch.optionStatusCode)
136 | return
137 | }
138 | ch.h.ServeHTTP(w, r)
139 | }
140 |
141 | // CORS provides Cross-Origin Resource Sharing middleware.
142 | // Example:
143 | //
144 | // import (
145 | // "net/http"
146 | //
147 | // "github.com/gorilla/handlers"
148 | // "github.com/gorilla/mux"
149 | // )
150 | //
151 | // func main() {
152 | // r := mux.NewRouter()
153 | // r.HandleFunc("/users", UserEndpoint)
154 | // r.HandleFunc("/projects", ProjectEndpoint)
155 | //
156 | // // Apply the CORS middleware to our top-level router, with the defaults.
157 | // http.ListenAndServe(":8000", handlers.CORS()(r))
158 | // }
159 | //
160 | func CORS(opts ...CORSOption) func(http.Handler) http.Handler {
161 | return func(h http.Handler) http.Handler {
162 | ch := parseCORSOptions(opts...)
163 | ch.h = h
164 | return ch
165 | }
166 | }
167 |
168 | func parseCORSOptions(opts ...CORSOption) *cors {
169 | ch := &cors{
170 | allowedMethods: defaultCorsMethods,
171 | allowedHeaders: defaultCorsHeaders,
172 | allowedOrigins: []string{},
173 | optionStatusCode: defaultCorsOptionStatusCode,
174 | }
175 |
176 | for _, option := range opts {
177 | option(ch)
178 | }
179 |
180 | return ch
181 | }
182 |
183 | //
184 | // Functional options for configuring CORS.
185 | //
186 |
187 | // AllowedHeaders adds the provided headers to the list of allowed headers in a
188 | // CORS request.
189 | // This is an append operation so the headers Accept, Accept-Language,
190 | // and Content-Language are always allowed.
191 | // Content-Type must be explicitly declared if accepting Content-Types other than
192 | // application/x-www-form-urlencoded, multipart/form-data, or text/plain.
193 | func AllowedHeaders(headers []string) CORSOption {
194 | return func(ch *cors) error {
195 | for _, v := range headers {
196 | normalizedHeader := http.CanonicalHeaderKey(strings.TrimSpace(v))
197 | if normalizedHeader == "" {
198 | continue
199 | }
200 |
201 | if !ch.isMatch(normalizedHeader, ch.allowedHeaders) {
202 | ch.allowedHeaders = append(ch.allowedHeaders, normalizedHeader)
203 | }
204 | }
205 |
206 | return nil
207 | }
208 | }
209 |
210 | // AllowedMethods can be used to explicitly allow methods in the
211 | // Access-Control-Allow-Methods header.
212 | // This is a replacement operation so you must also
213 | // pass GET, HEAD, and POST if you wish to support those methods.
214 | func AllowedMethods(methods []string) CORSOption {
215 | return func(ch *cors) error {
216 | ch.allowedMethods = []string{}
217 | for _, v := range methods {
218 | normalizedMethod := strings.ToUpper(strings.TrimSpace(v))
219 | if normalizedMethod == "" {
220 | continue
221 | }
222 |
223 | if !ch.isMatch(normalizedMethod, ch.allowedMethods) {
224 | ch.allowedMethods = append(ch.allowedMethods, normalizedMethod)
225 | }
226 | }
227 |
228 | return nil
229 | }
230 | }
231 |
232 | // AllowedOrigins sets the allowed origins for CORS requests, as used in the
233 | // 'Allow-Access-Control-Origin' HTTP header.
234 | // Note: Passing in a []string{"*"} will allow any domain.
235 | func AllowedOrigins(origins []string) CORSOption {
236 | return func(ch *cors) error {
237 | for _, v := range origins {
238 | if v == corsOriginMatchAll {
239 | ch.allowedOrigins = []string{corsOriginMatchAll}
240 | return nil
241 | }
242 | }
243 |
244 | ch.allowedOrigins = origins
245 | return nil
246 | }
247 | }
248 |
249 | // AllowedOriginValidator sets a function for evaluating allowed origins in CORS requests, represented by the
250 | // 'Allow-Access-Control-Origin' HTTP header.
251 | func AllowedOriginValidator(fn OriginValidator) CORSOption {
252 | return func(ch *cors) error {
253 | ch.allowedOriginValidator = fn
254 | return nil
255 | }
256 | }
257 |
258 | // OptionStatusCode sets a custom status code on the OPTIONS requests.
259 | // Default behaviour sets it to 200 to reflect best practices. This is option is not mandatory
260 | // and can be used if you need a custom status code (i.e 204).
261 | //
262 | // More informations on the spec:
263 | // https://fetch.spec.whatwg.org/#cors-preflight-fetch
264 | func OptionStatusCode(code int) CORSOption {
265 | return func(ch *cors) error {
266 | ch.optionStatusCode = code
267 | return nil
268 | }
269 | }
270 |
271 | // ExposedHeaders can be used to specify headers that are available
272 | // and will not be stripped out by the user-agent.
273 | func ExposedHeaders(headers []string) CORSOption {
274 | return func(ch *cors) error {
275 | ch.exposedHeaders = []string{}
276 | for _, v := range headers {
277 | normalizedHeader := http.CanonicalHeaderKey(strings.TrimSpace(v))
278 | if normalizedHeader == "" {
279 | continue
280 | }
281 |
282 | if !ch.isMatch(normalizedHeader, ch.exposedHeaders) {
283 | ch.exposedHeaders = append(ch.exposedHeaders, normalizedHeader)
284 | }
285 | }
286 |
287 | return nil
288 | }
289 | }
290 |
291 | // MaxAge determines the maximum age (in seconds) between preflight requests. A
292 | // maximum of 10 minutes is allowed. An age above this value will default to 10
293 | // minutes.
294 | func MaxAge(age int) CORSOption {
295 | return func(ch *cors) error {
296 | // Maximum of 10 minutes.
297 | if age > 600 {
298 | age = 600
299 | }
300 |
301 | ch.maxAge = age
302 | return nil
303 | }
304 | }
305 |
306 | // IgnoreOptions causes the CORS middleware to ignore OPTIONS requests, instead
307 | // passing them through to the next handler. This is useful when your application
308 | // or framework has a pre-existing mechanism for responding to OPTIONS requests.
309 | func IgnoreOptions() CORSOption {
310 | return func(ch *cors) error {
311 | ch.ignoreOptions = true
312 | return nil
313 | }
314 | }
315 |
316 | // AllowCredentials can be used to specify that the user agent may pass
317 | // authentication details along with the request.
318 | func AllowCredentials() CORSOption {
319 | return func(ch *cors) error {
320 | ch.allowCredentials = true
321 | return nil
322 | }
323 | }
324 |
325 | func (ch *cors) isOriginAllowed(origin string) bool {
326 | if origin == "" {
327 | return false
328 | }
329 |
330 | if ch.allowedOriginValidator != nil {
331 | return ch.allowedOriginValidator(origin)
332 | }
333 |
334 | if len(ch.allowedOrigins) == 0 {
335 | return true
336 | }
337 |
338 | for _, allowedOrigin := range ch.allowedOrigins {
339 | if allowedOrigin == origin || allowedOrigin == corsOriginMatchAll {
340 | return true
341 | }
342 | }
343 |
344 | return false
345 | }
346 |
347 | func (ch *cors) isMatch(needle string, haystack []string) bool {
348 | for _, v := range haystack {
349 | if v == needle {
350 | return true
351 | }
352 | }
353 |
354 | return false
355 | }
356 |
--------------------------------------------------------------------------------
/vendor/github.com/gorilla/handlers/doc.go:
--------------------------------------------------------------------------------
1 | /*
2 | Package handlers is a collection of handlers (aka "HTTP middleware") for use
3 | with Go's net/http package (or any framework supporting http.Handler).
4 |
5 | The package includes handlers for logging in standardised formats, compressing
6 | HTTP responses, validating content types and other useful tools for manipulating
7 | requests and responses.
8 | */
9 | package handlers
10 |
--------------------------------------------------------------------------------
/vendor/github.com/gorilla/handlers/go.mod:
--------------------------------------------------------------------------------
1 | module github.com/gorilla/handlers
2 |
--------------------------------------------------------------------------------
/vendor/github.com/gorilla/handlers/handlers.go:
--------------------------------------------------------------------------------
1 | // Copyright 2013 The Gorilla Authors. All rights reserved.
2 | // Use of this source code is governed by a BSD-style
3 | // license that can be found in the LICENSE file.
4 |
5 | package handlers
6 |
7 | import (
8 | "bufio"
9 | "fmt"
10 | "net"
11 | "net/http"
12 | "sort"
13 | "strings"
14 | )
15 |
16 | // MethodHandler is an http.Handler that dispatches to a handler whose key in the
17 | // MethodHandler's map matches the name of the HTTP request's method, eg: GET
18 | //
19 | // If the request's method is OPTIONS and OPTIONS is not a key in the map then
20 | // the handler responds with a status of 200 and sets the Allow header to a
21 | // comma-separated list of available methods.
22 | //
23 | // If the request's method doesn't match any of its keys the handler responds
24 | // with a status of HTTP 405 "Method Not Allowed" and sets the Allow header to a
25 | // comma-separated list of available methods.
26 | type MethodHandler map[string]http.Handler
27 |
28 | func (h MethodHandler) ServeHTTP(w http.ResponseWriter, req *http.Request) {
29 | if handler, ok := h[req.Method]; ok {
30 | handler.ServeHTTP(w, req)
31 | } else {
32 | allow := []string{}
33 | for k := range h {
34 | allow = append(allow, k)
35 | }
36 | sort.Strings(allow)
37 | w.Header().Set("Allow", strings.Join(allow, ", "))
38 | if req.Method == "OPTIONS" {
39 | w.WriteHeader(http.StatusOK)
40 | } else {
41 | http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
42 | }
43 | }
44 | }
45 |
46 | // responseLogger is wrapper of http.ResponseWriter that keeps track of its HTTP
47 | // status code and body size
48 | type responseLogger struct {
49 | w http.ResponseWriter
50 | status int
51 | size int
52 | }
53 |
54 | func (l *responseLogger) Header() http.Header {
55 | return l.w.Header()
56 | }
57 |
58 | func (l *responseLogger) Write(b []byte) (int, error) {
59 | size, err := l.w.Write(b)
60 | l.size += size
61 | return size, err
62 | }
63 |
64 | func (l *responseLogger) WriteHeader(s int) {
65 | l.w.WriteHeader(s)
66 | l.status = s
67 | }
68 |
69 | func (l *responseLogger) Status() int {
70 | return l.status
71 | }
72 |
73 | func (l *responseLogger) Size() int {
74 | return l.size
75 | }
76 |
77 | func (l *responseLogger) Flush() {
78 | f, ok := l.w.(http.Flusher)
79 | if ok {
80 | f.Flush()
81 | }
82 | }
83 |
84 | type hijackLogger struct {
85 | responseLogger
86 | }
87 |
88 | func (l *hijackLogger) Hijack() (net.Conn, *bufio.ReadWriter, error) {
89 | h := l.responseLogger.w.(http.Hijacker)
90 | conn, rw, err := h.Hijack()
91 | if err == nil && l.responseLogger.status == 0 {
92 | // The status will be StatusSwitchingProtocols if there was no error and
93 | // WriteHeader has not been called yet
94 | l.responseLogger.status = http.StatusSwitchingProtocols
95 | }
96 | return conn, rw, err
97 | }
98 |
99 | type closeNotifyWriter struct {
100 | loggingResponseWriter
101 | http.CloseNotifier
102 | }
103 |
104 | type hijackCloseNotifier struct {
105 | loggingResponseWriter
106 | http.Hijacker
107 | http.CloseNotifier
108 | }
109 |
110 | // isContentType validates the Content-Type header matches the supplied
111 | // contentType. That is, its type and subtype match.
112 | func isContentType(h http.Header, contentType string) bool {
113 | ct := h.Get("Content-Type")
114 | if i := strings.IndexRune(ct, ';'); i != -1 {
115 | ct = ct[0:i]
116 | }
117 | return ct == contentType
118 | }
119 |
120 | // ContentTypeHandler wraps and returns a http.Handler, validating the request
121 | // content type is compatible with the contentTypes list. It writes a HTTP 415
122 | // error if that fails.
123 | //
124 | // Only PUT, POST, and PATCH requests are considered.
125 | func ContentTypeHandler(h http.Handler, contentTypes ...string) http.Handler {
126 | return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
127 | if !(r.Method == "PUT" || r.Method == "POST" || r.Method == "PATCH") {
128 | h.ServeHTTP(w, r)
129 | return
130 | }
131 |
132 | for _, ct := range contentTypes {
133 | if isContentType(r.Header, ct) {
134 | h.ServeHTTP(w, r)
135 | return
136 | }
137 | }
138 | http.Error(w, fmt.Sprintf("Unsupported content type %q; expected one of %q", r.Header.Get("Content-Type"), contentTypes), http.StatusUnsupportedMediaType)
139 | })
140 | }
141 |
142 | const (
143 | // HTTPMethodOverrideHeader is a commonly used
144 | // http header to override a request method.
145 | HTTPMethodOverrideHeader = "X-HTTP-Method-Override"
146 | // HTTPMethodOverrideFormKey is a commonly used
147 | // HTML form key to override a request method.
148 | HTTPMethodOverrideFormKey = "_method"
149 | )
150 |
151 | // HTTPMethodOverrideHandler wraps and returns a http.Handler which checks for
152 | // the X-HTTP-Method-Override header or the _method form key, and overrides (if
153 | // valid) request.Method with its value.
154 | //
155 | // This is especially useful for HTTP clients that don't support many http verbs.
156 | // It isn't secure to override e.g a GET to a POST, so only POST requests are
157 | // considered. Likewise, the override method can only be a "write" method: PUT,
158 | // PATCH or DELETE.
159 | //
160 | // Form method takes precedence over header method.
161 | func HTTPMethodOverrideHandler(h http.Handler) http.Handler {
162 | return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
163 | if r.Method == "POST" {
164 | om := r.FormValue(HTTPMethodOverrideFormKey)
165 | if om == "" {
166 | om = r.Header.Get(HTTPMethodOverrideHeader)
167 | }
168 | if om == "PUT" || om == "PATCH" || om == "DELETE" {
169 | r.Method = om
170 | }
171 | }
172 | h.ServeHTTP(w, r)
173 | })
174 | }
175 |
--------------------------------------------------------------------------------
/vendor/github.com/gorilla/handlers/handlers_go18.go:
--------------------------------------------------------------------------------
1 | // +build go1.8
2 |
3 | package handlers
4 |
5 | import (
6 | "fmt"
7 | "net/http"
8 | )
9 |
10 | type loggingResponseWriter interface {
11 | commonLoggingResponseWriter
12 | http.Pusher
13 | }
14 |
15 | func (l *responseLogger) Push(target string, opts *http.PushOptions) error {
16 | p, ok := l.w.(http.Pusher)
17 | if !ok {
18 | return fmt.Errorf("responseLogger does not implement http.Pusher")
19 | }
20 | return p.Push(target, opts)
21 | }
22 |
23 | func (c *compressResponseWriter) Push(target string, opts *http.PushOptions) error {
24 | p, ok := c.ResponseWriter.(http.Pusher)
25 | if !ok {
26 | return fmt.Errorf("compressResponseWriter does not implement http.Pusher")
27 | }
28 | return p.Push(target, opts)
29 | }
30 |
--------------------------------------------------------------------------------
/vendor/github.com/gorilla/handlers/handlers_pre18.go:
--------------------------------------------------------------------------------
1 | // +build !go1.8
2 |
3 | package handlers
4 |
5 | type loggingResponseWriter interface {
6 | commonLoggingResponseWriter
7 | }
8 |
--------------------------------------------------------------------------------
/vendor/github.com/gorilla/handlers/logging.go:
--------------------------------------------------------------------------------
1 | // Copyright 2013 The Gorilla Authors. All rights reserved.
2 | // Use of this source code is governed by a BSD-style
3 | // license that can be found in the LICENSE file.
4 |
5 | package handlers
6 |
7 | import (
8 | "io"
9 | "net"
10 | "net/http"
11 | "net/url"
12 | "strconv"
13 | "time"
14 | "unicode/utf8"
15 | )
16 |
17 | // Logging
18 |
19 | // FormatterParams is the structure any formatter will be handed when time to log comes
20 | type LogFormatterParams struct {
21 | Request *http.Request
22 | URL url.URL
23 | TimeStamp time.Time
24 | StatusCode int
25 | Size int
26 | }
27 |
28 | // LogFormatter gives the signature of the formatter function passed to CustomLoggingHandler
29 | type LogFormatter func(writer io.Writer, params LogFormatterParams)
30 |
31 | // loggingHandler is the http.Handler implementation for LoggingHandlerTo and its
32 | // friends
33 |
34 | type loggingHandler struct {
35 | writer io.Writer
36 | handler http.Handler
37 | formatter LogFormatter
38 | }
39 |
40 | func (h loggingHandler) ServeHTTP(w http.ResponseWriter, req *http.Request) {
41 | t := time.Now()
42 | logger := makeLogger(w)
43 | url := *req.URL
44 |
45 | h.handler.ServeHTTP(logger, req)
46 |
47 | params := LogFormatterParams{
48 | Request: req,
49 | URL: url,
50 | TimeStamp: t,
51 | StatusCode: logger.Status(),
52 | Size: logger.Size(),
53 | }
54 |
55 | h.formatter(h.writer, params)
56 | }
57 |
58 | func makeLogger(w http.ResponseWriter) loggingResponseWriter {
59 | var logger loggingResponseWriter = &responseLogger{w: w, status: http.StatusOK}
60 | if _, ok := w.(http.Hijacker); ok {
61 | logger = &hijackLogger{responseLogger{w: w, status: http.StatusOK}}
62 | }
63 | h, ok1 := logger.(http.Hijacker)
64 | c, ok2 := w.(http.CloseNotifier)
65 | if ok1 && ok2 {
66 | return hijackCloseNotifier{logger, h, c}
67 | }
68 | if ok2 {
69 | return &closeNotifyWriter{logger, c}
70 | }
71 | return logger
72 | }
73 |
74 | type commonLoggingResponseWriter interface {
75 | http.ResponseWriter
76 | http.Flusher
77 | Status() int
78 | Size() int
79 | }
80 |
81 | const lowerhex = "0123456789abcdef"
82 |
83 | func appendQuoted(buf []byte, s string) []byte {
84 | var runeTmp [utf8.UTFMax]byte
85 | for width := 0; len(s) > 0; s = s[width:] {
86 | r := rune(s[0])
87 | width = 1
88 | if r >= utf8.RuneSelf {
89 | r, width = utf8.DecodeRuneInString(s)
90 | }
91 | if width == 1 && r == utf8.RuneError {
92 | buf = append(buf, `\x`...)
93 | buf = append(buf, lowerhex[s[0]>>4])
94 | buf = append(buf, lowerhex[s[0]&0xF])
95 | continue
96 | }
97 | if r == rune('"') || r == '\\' { // always backslashed
98 | buf = append(buf, '\\')
99 | buf = append(buf, byte(r))
100 | continue
101 | }
102 | if strconv.IsPrint(r) {
103 | n := utf8.EncodeRune(runeTmp[:], r)
104 | buf = append(buf, runeTmp[:n]...)
105 | continue
106 | }
107 | switch r {
108 | case '\a':
109 | buf = append(buf, `\a`...)
110 | case '\b':
111 | buf = append(buf, `\b`...)
112 | case '\f':
113 | buf = append(buf, `\f`...)
114 | case '\n':
115 | buf = append(buf, `\n`...)
116 | case '\r':
117 | buf = append(buf, `\r`...)
118 | case '\t':
119 | buf = append(buf, `\t`...)
120 | case '\v':
121 | buf = append(buf, `\v`...)
122 | default:
123 | switch {
124 | case r < ' ':
125 | buf = append(buf, `\x`...)
126 | buf = append(buf, lowerhex[s[0]>>4])
127 | buf = append(buf, lowerhex[s[0]&0xF])
128 | case r > utf8.MaxRune:
129 | r = 0xFFFD
130 | fallthrough
131 | case r < 0x10000:
132 | buf = append(buf, `\u`...)
133 | for s := 12; s >= 0; s -= 4 {
134 | buf = append(buf, lowerhex[r>>uint(s)&0xF])
135 | }
136 | default:
137 | buf = append(buf, `\U`...)
138 | for s := 28; s >= 0; s -= 4 {
139 | buf = append(buf, lowerhex[r>>uint(s)&0xF])
140 | }
141 | }
142 | }
143 | }
144 | return buf
145 |
146 | }
147 |
148 | // buildCommonLogLine builds a log entry for req in Apache Common Log Format.
149 | // ts is the timestamp with which the entry should be logged.
150 | // status and size are used to provide the response HTTP status and size.
151 | func buildCommonLogLine(req *http.Request, url url.URL, ts time.Time, status int, size int) []byte {
152 | username := "-"
153 | if url.User != nil {
154 | if name := url.User.Username(); name != "" {
155 | username = name
156 | }
157 | }
158 |
159 | host, _, err := net.SplitHostPort(req.RemoteAddr)
160 |
161 | if err != nil {
162 | host = req.RemoteAddr
163 | }
164 |
165 | uri := req.RequestURI
166 |
167 | // Requests using the CONNECT method over HTTP/2.0 must use
168 | // the authority field (aka r.Host) to identify the target.
169 | // Refer: https://httpwg.github.io/specs/rfc7540.html#CONNECT
170 | if req.ProtoMajor == 2 && req.Method == "CONNECT" {
171 | uri = req.Host
172 | }
173 | if uri == "" {
174 | uri = url.RequestURI()
175 | }
176 |
177 | buf := make([]byte, 0, 3*(len(host)+len(username)+len(req.Method)+len(uri)+len(req.Proto)+50)/2)
178 | buf = append(buf, host...)
179 | buf = append(buf, " - "...)
180 | buf = append(buf, username...)
181 | buf = append(buf, " ["...)
182 | buf = append(buf, ts.Format("02/Jan/2006:15:04:05 -0700")...)
183 | buf = append(buf, `] "`...)
184 | buf = append(buf, req.Method...)
185 | buf = append(buf, " "...)
186 | buf = appendQuoted(buf, uri)
187 | buf = append(buf, " "...)
188 | buf = append(buf, req.Proto...)
189 | buf = append(buf, `" `...)
190 | buf = append(buf, strconv.Itoa(status)...)
191 | buf = append(buf, " "...)
192 | buf = append(buf, strconv.Itoa(size)...)
193 | return buf
194 | }
195 |
196 | // writeLog writes a log entry for req to w in Apache Common Log Format.
197 | // ts is the timestamp with which the entry should be logged.
198 | // status and size are used to provide the response HTTP status and size.
199 | func writeLog(writer io.Writer, params LogFormatterParams) {
200 | buf := buildCommonLogLine(params.Request, params.URL, params.TimeStamp, params.StatusCode, params.Size)
201 | buf = append(buf, '\n')
202 | writer.Write(buf)
203 | }
204 |
205 | // writeCombinedLog writes a log entry for req to w in Apache Combined Log Format.
206 | // ts is the timestamp with which the entry should be logged.
207 | // status and size are used to provide the response HTTP status and size.
208 | func writeCombinedLog(writer io.Writer, params LogFormatterParams) {
209 | buf := buildCommonLogLine(params.Request, params.URL, params.TimeStamp, params.StatusCode, params.Size)
210 | buf = append(buf, ` "`...)
211 | buf = appendQuoted(buf, params.Request.Referer())
212 | buf = append(buf, `" "`...)
213 | buf = appendQuoted(buf, params.Request.UserAgent())
214 | buf = append(buf, '"', '\n')
215 | writer.Write(buf)
216 | }
217 |
218 | // CombinedLoggingHandler return a http.Handler that wraps h and logs requests to out in
219 | // Apache Combined Log Format.
220 | //
221 | // See http://httpd.apache.org/docs/2.2/logs.html#combined for a description of this format.
222 | //
223 | // LoggingHandler always sets the ident field of the log to -
224 | func CombinedLoggingHandler(out io.Writer, h http.Handler) http.Handler {
225 | return loggingHandler{out, h, writeCombinedLog}
226 | }
227 |
228 | // LoggingHandler return a http.Handler that wraps h and logs requests to out in
229 | // Apache Common Log Format (CLF).
230 | //
231 | // See http://httpd.apache.org/docs/2.2/logs.html#common for a description of this format.
232 | //
233 | // LoggingHandler always sets the ident field of the log to -
234 | //
235 | // Example:
236 | //
237 | // r := mux.NewRouter()
238 | // r.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
239 | // w.Write([]byte("This is a catch-all route"))
240 | // })
241 | // loggedRouter := handlers.LoggingHandler(os.Stdout, r)
242 | // http.ListenAndServe(":1123", loggedRouter)
243 | //
244 | func LoggingHandler(out io.Writer, h http.Handler) http.Handler {
245 | return loggingHandler{out, h, writeLog}
246 | }
247 |
248 | // CustomLoggingHandler provides a way to supply a custom log formatter
249 | // while taking advantage of the mechanisms in this package
250 | func CustomLoggingHandler(out io.Writer, h http.Handler, f LogFormatter) http.Handler {
251 | return loggingHandler{out, h, f}
252 | }
253 |
--------------------------------------------------------------------------------
/vendor/github.com/gorilla/handlers/proxy_headers.go:
--------------------------------------------------------------------------------
1 | package handlers
2 |
3 | import (
4 | "net/http"
5 | "regexp"
6 | "strings"
7 | )
8 |
9 | var (
10 | // De-facto standard header keys.
11 | xForwardedFor = http.CanonicalHeaderKey("X-Forwarded-For")
12 | xForwardedHost = http.CanonicalHeaderKey("X-Forwarded-Host")
13 | xForwardedProto = http.CanonicalHeaderKey("X-Forwarded-Proto")
14 | xForwardedScheme = http.CanonicalHeaderKey("X-Forwarded-Scheme")
15 | xRealIP = http.CanonicalHeaderKey("X-Real-IP")
16 | )
17 |
18 | var (
19 | // RFC7239 defines a new "Forwarded: " header designed to replace the
20 | // existing use of X-Forwarded-* headers.
21 | // e.g. Forwarded: for=192.0.2.60;proto=https;by=203.0.113.43
22 | forwarded = http.CanonicalHeaderKey("Forwarded")
23 | // Allows for a sub-match of the first value after 'for=' to the next
24 | // comma, semi-colon or space. The match is case-insensitive.
25 | forRegex = regexp.MustCompile(`(?i)(?:for=)([^(;|,| )]+)`)
26 | // Allows for a sub-match for the first instance of scheme (http|https)
27 | // prefixed by 'proto='. The match is case-insensitive.
28 | protoRegex = regexp.MustCompile(`(?i)(?:proto=)(https|http)`)
29 | )
30 |
31 | // ProxyHeaders inspects common reverse proxy headers and sets the corresponding
32 | // fields in the HTTP request struct. These are X-Forwarded-For and X-Real-IP
33 | // for the remote (client) IP address, X-Forwarded-Proto or X-Forwarded-Scheme
34 | // for the scheme (http|https) and the RFC7239 Forwarded header, which may
35 | // include both client IPs and schemes.
36 | //
37 | // NOTE: This middleware should only be used when behind a reverse
38 | // proxy like nginx, HAProxy or Apache. Reverse proxies that don't (or are
39 | // configured not to) strip these headers from client requests, or where these
40 | // headers are accepted "as is" from a remote client (e.g. when Go is not behind
41 | // a proxy), can manifest as a vulnerability if your application uses these
42 | // headers for validating the 'trustworthiness' of a request.
43 | func ProxyHeaders(h http.Handler) http.Handler {
44 | fn := func(w http.ResponseWriter, r *http.Request) {
45 | // Set the remote IP with the value passed from the proxy.
46 | if fwd := getIP(r); fwd != "" {
47 | r.RemoteAddr = fwd
48 | }
49 |
50 | // Set the scheme (proto) with the value passed from the proxy.
51 | if scheme := getScheme(r); scheme != "" {
52 | r.URL.Scheme = scheme
53 | }
54 | // Set the host with the value passed by the proxy
55 | if r.Header.Get(xForwardedHost) != "" {
56 | r.Host = r.Header.Get(xForwardedHost)
57 | }
58 | // Call the next handler in the chain.
59 | h.ServeHTTP(w, r)
60 | }
61 |
62 | return http.HandlerFunc(fn)
63 | }
64 |
65 | // getIP retrieves the IP from the X-Forwarded-For, X-Real-IP and RFC7239
66 | // Forwarded headers (in that order).
67 | func getIP(r *http.Request) string {
68 | var addr string
69 |
70 | if fwd := r.Header.Get(xForwardedFor); fwd != "" {
71 | // Only grab the first (client) address. Note that '192.168.0.1,
72 | // 10.1.1.1' is a valid key for X-Forwarded-For where addresses after
73 | // the first may represent forwarding proxies earlier in the chain.
74 | s := strings.Index(fwd, ", ")
75 | if s == -1 {
76 | s = len(fwd)
77 | }
78 | addr = fwd[:s]
79 | } else if fwd := r.Header.Get(xRealIP); fwd != "" {
80 | // X-Real-IP should only contain one IP address (the client making the
81 | // request).
82 | addr = fwd
83 | } else if fwd := r.Header.Get(forwarded); fwd != "" {
84 | // match should contain at least two elements if the protocol was
85 | // specified in the Forwarded header. The first element will always be
86 | // the 'for=' capture, which we ignore. In the case of multiple IP
87 | // addresses (for=8.8.8.8, 8.8.4.4,172.16.1.20 is valid) we only
88 | // extract the first, which should be the client IP.
89 | if match := forRegex.FindStringSubmatch(fwd); len(match) > 1 {
90 | // IPv6 addresses in Forwarded headers are quoted-strings. We strip
91 | // these quotes.
92 | addr = strings.Trim(match[1], `"`)
93 | }
94 | }
95 |
96 | return addr
97 | }
98 |
99 | // getScheme retrieves the scheme from the X-Forwarded-Proto and RFC7239
100 | // Forwarded headers (in that order).
101 | func getScheme(r *http.Request) string {
102 | var scheme string
103 |
104 | // Retrieve the scheme from X-Forwarded-Proto.
105 | if proto := r.Header.Get(xForwardedProto); proto != "" {
106 | scheme = strings.ToLower(proto)
107 | } else if proto = r.Header.Get(xForwardedScheme); proto != "" {
108 | scheme = strings.ToLower(proto)
109 | } else if proto = r.Header.Get(forwarded); proto != "" {
110 | // match should contain at least two elements if the protocol was
111 | // specified in the Forwarded header. The first element will always be
112 | // the 'proto=' capture, which we ignore. In the case of multiple proto
113 | // parameters (invalid) we only extract the first.
114 | if match := protoRegex.FindStringSubmatch(proto); len(match) > 1 {
115 | scheme = strings.ToLower(match[1])
116 | }
117 | }
118 |
119 | return scheme
120 | }
121 |
--------------------------------------------------------------------------------
/vendor/github.com/gorilla/handlers/recovery.go:
--------------------------------------------------------------------------------
1 | package handlers
2 |
3 | import (
4 | "log"
5 | "net/http"
6 | "runtime/debug"
7 | )
8 |
9 | // RecoveryHandlerLogger is an interface used by the recovering handler to print logs.
10 | type RecoveryHandlerLogger interface {
11 | Println(...interface{})
12 | }
13 |
14 | type recoveryHandler struct {
15 | handler http.Handler
16 | logger RecoveryHandlerLogger
17 | printStack bool
18 | }
19 |
20 | // RecoveryOption provides a functional approach to define
21 | // configuration for a handler; such as setting the logging
22 | // whether or not to print strack traces on panic.
23 | type RecoveryOption func(http.Handler)
24 |
25 | func parseRecoveryOptions(h http.Handler, opts ...RecoveryOption) http.Handler {
26 | for _, option := range opts {
27 | option(h)
28 | }
29 |
30 | return h
31 | }
32 |
33 | // RecoveryHandler is HTTP middleware that recovers from a panic,
34 | // logs the panic, writes http.StatusInternalServerError, and
35 | // continues to the next handler.
36 | //
37 | // Example:
38 | //
39 | // r := mux.NewRouter()
40 | // r.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
41 | // panic("Unexpected error!")
42 | // })
43 | //
44 | // http.ListenAndServe(":1123", handlers.RecoveryHandler()(r))
45 | func RecoveryHandler(opts ...RecoveryOption) func(h http.Handler) http.Handler {
46 | return func(h http.Handler) http.Handler {
47 | r := &recoveryHandler{handler: h}
48 | return parseRecoveryOptions(r, opts...)
49 | }
50 | }
51 |
52 | // RecoveryLogger is a functional option to override
53 | // the default logger
54 | func RecoveryLogger(logger RecoveryHandlerLogger) RecoveryOption {
55 | return func(h http.Handler) {
56 | r := h.(*recoveryHandler)
57 | r.logger = logger
58 | }
59 | }
60 |
61 | // PrintRecoveryStack is a functional option to enable
62 | // or disable printing stack traces on panic.
63 | func PrintRecoveryStack(print bool) RecoveryOption {
64 | return func(h http.Handler) {
65 | r := h.(*recoveryHandler)
66 | r.printStack = print
67 | }
68 | }
69 |
70 | func (h recoveryHandler) ServeHTTP(w http.ResponseWriter, req *http.Request) {
71 | defer func() {
72 | if err := recover(); err != nil {
73 | w.WriteHeader(http.StatusInternalServerError)
74 | h.log(err)
75 | }
76 | }()
77 |
78 | h.handler.ServeHTTP(w, req)
79 | }
80 |
81 | func (h recoveryHandler) log(v ...interface{}) {
82 | if h.logger != nil {
83 | h.logger.Println(v...)
84 | } else {
85 | log.Println(v...)
86 | }
87 |
88 | if h.printStack {
89 | debug.PrintStack()
90 | }
91 | }
92 |
--------------------------------------------------------------------------------
/vendor/github.com/gorilla/mux/.travis.yml:
--------------------------------------------------------------------------------
1 | language: go
2 |
3 |
4 | matrix:
5 | include:
6 | - go: 1.7.x
7 | - go: 1.8.x
8 | - go: 1.9.x
9 | - go: 1.10.x
10 | - go: 1.11.x
11 | - go: 1.x
12 | env: LATEST=true
13 | - go: tip
14 | allow_failures:
15 | - go: tip
16 |
17 | install:
18 | - # Skip
19 |
20 | script:
21 | - go get -t -v ./...
22 | - diff -u <(echo -n) <(gofmt -d .)
23 | - if [[ "$LATEST" = true ]]; then go vet .; fi
24 | - go test -v -race ./...
25 |
--------------------------------------------------------------------------------
/vendor/github.com/gorilla/mux/AUTHORS:
--------------------------------------------------------------------------------
1 | # This is the official list of gorilla/mux authors for copyright purposes.
2 | #
3 | # Please keep the list sorted.
4 |
5 | Google LLC (https://opensource.google.com/)
6 | Kamil Kisielk
7 | Matt Silverlock
8 | Rodrigo Moraes (https://github.com/moraes)
9 |
--------------------------------------------------------------------------------
/vendor/github.com/gorilla/mux/ISSUE_TEMPLATE.md:
--------------------------------------------------------------------------------
1 | **What version of Go are you running?** (Paste the output of `go version`)
2 |
3 |
4 | **What version of gorilla/mux are you at?** (Paste the output of `git rev-parse HEAD` inside `$GOPATH/src/github.com/gorilla/mux`)
5 |
6 |
7 | **Describe your problem** (and what you have tried so far)
8 |
9 |
10 | **Paste a minimal, runnable, reproduction of your issue below** (use backticks to format it)
11 |
12 |
--------------------------------------------------------------------------------
/vendor/github.com/gorilla/mux/LICENSE:
--------------------------------------------------------------------------------
1 | Copyright (c) 2012-2018 The Gorilla Authors. All rights reserved.
2 |
3 | Redistribution and use in source and binary forms, with or without
4 | modification, are permitted provided that the following conditions are
5 | met:
6 |
7 | * Redistributions of source code must retain the above copyright
8 | notice, this list of conditions and the following disclaimer.
9 | * Redistributions in binary form must reproduce the above
10 | copyright notice, this list of conditions and the following disclaimer
11 | in the documentation and/or other materials provided with the
12 | distribution.
13 | * Neither the name of Google Inc. nor the names of its
14 | contributors may be used to endorse or promote products derived from
15 | this software without specific prior written permission.
16 |
17 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
18 | "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
19 | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
20 | A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
21 | OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
22 | SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
23 | LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
24 | DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
25 | THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
26 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
27 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
28 |
--------------------------------------------------------------------------------
/vendor/github.com/gorilla/mux/README.md:
--------------------------------------------------------------------------------
1 | # gorilla/mux
2 |
3 | [](https://godoc.org/github.com/gorilla/mux)
4 | [](https://travis-ci.org/gorilla/mux)
5 | [](https://sourcegraph.com/github.com/gorilla/mux?badge)
6 |
7 | 
8 |
9 | https://www.gorillatoolkit.org/pkg/mux
10 |
11 | Package `gorilla/mux` implements a request router and dispatcher for matching incoming requests to
12 | their respective handler.
13 |
14 | The name mux stands for "HTTP request multiplexer". Like the standard `http.ServeMux`, `mux.Router` matches incoming requests against a list of registered routes and calls a handler for the route that matches the URL or other conditions. The main features are:
15 |
16 | * It implements the `http.Handler` interface so it is compatible with the standard `http.ServeMux`.
17 | * Requests can be matched based on URL host, path, path prefix, schemes, header and query values, HTTP methods or using custom matchers.
18 | * URL hosts, paths and query values can have variables with an optional regular expression.
19 | * Registered URLs can be built, or "reversed", which helps maintaining references to resources.
20 | * Routes can be used as subrouters: nested routes are only tested if the parent route matches. This is useful to define groups of routes that share common conditions like a host, a path prefix or other repeated attributes. As a bonus, this optimizes request matching.
21 |
22 | ---
23 |
24 | * [Install](#install)
25 | * [Examples](#examples)
26 | * [Matching Routes](#matching-routes)
27 | * [Static Files](#static-files)
28 | * [Registered URLs](#registered-urls)
29 | * [Walking Routes](#walking-routes)
30 | * [Graceful Shutdown](#graceful-shutdown)
31 | * [Middleware](#middleware)
32 | * [Testing Handlers](#testing-handlers)
33 | * [Full Example](#full-example)
34 |
35 | ---
36 |
37 | ## Install
38 |
39 | With a [correctly configured](https://golang.org/doc/install#testing) Go toolchain:
40 |
41 | ```sh
42 | go get -u github.com/gorilla/mux
43 | ```
44 |
45 | ## Examples
46 |
47 | Let's start registering a couple of URL paths and handlers:
48 |
49 | ```go
50 | func main() {
51 | r := mux.NewRouter()
52 | r.HandleFunc("/", HomeHandler)
53 | r.HandleFunc("/products", ProductsHandler)
54 | r.HandleFunc("/articles", ArticlesHandler)
55 | http.Handle("/", r)
56 | }
57 | ```
58 |
59 | Here we register three routes mapping URL paths to handlers. This is equivalent to how `http.HandleFunc()` works: if an incoming request URL matches one of the paths, the corresponding handler is called passing (`http.ResponseWriter`, `*http.Request`) as parameters.
60 |
61 | Paths can have variables. They are defined using the format `{name}` or `{name:pattern}`. If a regular expression pattern is not defined, the matched variable will be anything until the next slash. For example:
62 |
63 | ```go
64 | r := mux.NewRouter()
65 | r.HandleFunc("/products/{key}", ProductHandler)
66 | r.HandleFunc("/articles/{category}/", ArticlesCategoryHandler)
67 | r.HandleFunc("/articles/{category}/{id:[0-9]+}", ArticleHandler)
68 | ```
69 |
70 | The names are used to create a map of route variables which can be retrieved calling `mux.Vars()`:
71 |
72 | ```go
73 | func ArticlesCategoryHandler(w http.ResponseWriter, r *http.Request) {
74 | vars := mux.Vars(r)
75 | w.WriteHeader(http.StatusOK)
76 | fmt.Fprintf(w, "Category: %v\n", vars["category"])
77 | }
78 | ```
79 |
80 | And this is all you need to know about the basic usage. More advanced options are explained below.
81 |
82 | ### Matching Routes
83 |
84 | Routes can also be restricted to a domain or subdomain. Just define a host pattern to be matched. They can also have variables:
85 |
86 | ```go
87 | r := mux.NewRouter()
88 | // Only matches if domain is "www.example.com".
89 | r.Host("www.example.com")
90 | // Matches a dynamic subdomain.
91 | r.Host("{subdomain:[a-z]+}.example.com")
92 | ```
93 |
94 | There are several other matchers that can be added. To match path prefixes:
95 |
96 | ```go
97 | r.PathPrefix("/products/")
98 | ```
99 |
100 | ...or HTTP methods:
101 |
102 | ```go
103 | r.Methods("GET", "POST")
104 | ```
105 |
106 | ...or URL schemes:
107 |
108 | ```go
109 | r.Schemes("https")
110 | ```
111 |
112 | ...or header values:
113 |
114 | ```go
115 | r.Headers("X-Requested-With", "XMLHttpRequest")
116 | ```
117 |
118 | ...or query values:
119 |
120 | ```go
121 | r.Queries("key", "value")
122 | ```
123 |
124 | ...or to use a custom matcher function:
125 |
126 | ```go
127 | r.MatcherFunc(func(r *http.Request, rm *RouteMatch) bool {
128 | return r.ProtoMajor == 0
129 | })
130 | ```
131 |
132 | ...and finally, it is possible to combine several matchers in a single route:
133 |
134 | ```go
135 | r.HandleFunc("/products", ProductsHandler).
136 | Host("www.example.com").
137 | Methods("GET").
138 | Schemes("http")
139 | ```
140 |
141 | Routes are tested in the order they were added to the router. If two routes match, the first one wins:
142 |
143 | ```go
144 | r := mux.NewRouter()
145 | r.HandleFunc("/specific", specificHandler)
146 | r.PathPrefix("/").Handler(catchAllHandler)
147 | ```
148 |
149 | Setting the same matching conditions again and again can be boring, so we have a way to group several routes that share the same requirements. We call it "subrouting".
150 |
151 | For example, let's say we have several URLs that should only match when the host is `www.example.com`. Create a route for that host and get a "subrouter" from it:
152 |
153 | ```go
154 | r := mux.NewRouter()
155 | s := r.Host("www.example.com").Subrouter()
156 | ```
157 |
158 | Then register routes in the subrouter:
159 |
160 | ```go
161 | s.HandleFunc("/products/", ProductsHandler)
162 | s.HandleFunc("/products/{key}", ProductHandler)
163 | s.HandleFunc("/articles/{category}/{id:[0-9]+}", ArticleHandler)
164 | ```
165 |
166 | The three URL paths we registered above will only be tested if the domain is `www.example.com`, because the subrouter is tested first. This is not only convenient, but also optimizes request matching. You can create subrouters combining any attribute matchers accepted by a route.
167 |
168 | Subrouters can be used to create domain or path "namespaces": you define subrouters in a central place and then parts of the app can register its paths relatively to a given subrouter.
169 |
170 | There's one more thing about subroutes. When a subrouter has a path prefix, the inner routes use it as base for their paths:
171 |
172 | ```go
173 | r := mux.NewRouter()
174 | s := r.PathPrefix("/products").Subrouter()
175 | // "/products/"
176 | s.HandleFunc("/", ProductsHandler)
177 | // "/products/{key}/"
178 | s.HandleFunc("/{key}/", ProductHandler)
179 | // "/products/{key}/details"
180 | s.HandleFunc("/{key}/details", ProductDetailsHandler)
181 | ```
182 |
183 |
184 | ### Static Files
185 |
186 | Note that the path provided to `PathPrefix()` represents a "wildcard": calling
187 | `PathPrefix("/static/").Handler(...)` means that the handler will be passed any
188 | request that matches "/static/\*". This makes it easy to serve static files with mux:
189 |
190 | ```go
191 | func main() {
192 | var dir string
193 |
194 | flag.StringVar(&dir, "dir", ".", "the directory to serve files from. Defaults to the current dir")
195 | flag.Parse()
196 | r := mux.NewRouter()
197 |
198 | // This will serve files under http://localhost:8000/static/
199 | r.PathPrefix("/static/").Handler(http.StripPrefix("/static/", http.FileServer(http.Dir(dir))))
200 |
201 | srv := &http.Server{
202 | Handler: r,
203 | Addr: "127.0.0.1:8000",
204 | // Good practice: enforce timeouts for servers you create!
205 | WriteTimeout: 15 * time.Second,
206 | ReadTimeout: 15 * time.Second,
207 | }
208 |
209 | log.Fatal(srv.ListenAndServe())
210 | }
211 | ```
212 |
213 | ### Registered URLs
214 |
215 | Now let's see how to build registered URLs.
216 |
217 | Routes can be named. All routes that define a name can have their URLs built, or "reversed". We define a name calling `Name()` on a route. For example:
218 |
219 | ```go
220 | r := mux.NewRouter()
221 | r.HandleFunc("/articles/{category}/{id:[0-9]+}", ArticleHandler).
222 | Name("article")
223 | ```
224 |
225 | To build a URL, get the route and call the `URL()` method, passing a sequence of key/value pairs for the route variables. For the previous route, we would do:
226 |
227 | ```go
228 | url, err := r.Get("article").URL("category", "technology", "id", "42")
229 | ```
230 |
231 | ...and the result will be a `url.URL` with the following path:
232 |
233 | ```
234 | "/articles/technology/42"
235 | ```
236 |
237 | This also works for host and query value variables:
238 |
239 | ```go
240 | r := mux.NewRouter()
241 | r.Host("{subdomain}.example.com").
242 | Path("/articles/{category}/{id:[0-9]+}").
243 | Queries("filter", "{filter}").
244 | HandlerFunc(ArticleHandler).
245 | Name("article")
246 |
247 | // url.String() will be "http://news.example.com/articles/technology/42?filter=gorilla"
248 | url, err := r.Get("article").URL("subdomain", "news",
249 | "category", "technology",
250 | "id", "42",
251 | "filter", "gorilla")
252 | ```
253 |
254 | All variables defined in the route are required, and their values must conform to the corresponding patterns. These requirements guarantee that a generated URL will always match a registered route -- the only exception is for explicitly defined "build-only" routes which never match.
255 |
256 | Regex support also exists for matching Headers within a route. For example, we could do:
257 |
258 | ```go
259 | r.HeadersRegexp("Content-Type", "application/(text|json)")
260 | ```
261 |
262 | ...and the route will match both requests with a Content-Type of `application/json` as well as `application/text`
263 |
264 | There's also a way to build only the URL host or path for a route: use the methods `URLHost()` or `URLPath()` instead. For the previous route, we would do:
265 |
266 | ```go
267 | // "http://news.example.com/"
268 | host, err := r.Get("article").URLHost("subdomain", "news")
269 |
270 | // "/articles/technology/42"
271 | path, err := r.Get("article").URLPath("category", "technology", "id", "42")
272 | ```
273 |
274 | And if you use subrouters, host and path defined separately can be built as well:
275 |
276 | ```go
277 | r := mux.NewRouter()
278 | s := r.Host("{subdomain}.example.com").Subrouter()
279 | s.Path("/articles/{category}/{id:[0-9]+}").
280 | HandlerFunc(ArticleHandler).
281 | Name("article")
282 |
283 | // "http://news.example.com/articles/technology/42"
284 | url, err := r.Get("article").URL("subdomain", "news",
285 | "category", "technology",
286 | "id", "42")
287 | ```
288 |
289 | ### Walking Routes
290 |
291 | The `Walk` function on `mux.Router` can be used to visit all of the routes that are registered on a router. For example,
292 | the following prints all of the registered routes:
293 |
294 | ```go
295 | package main
296 |
297 | import (
298 | "fmt"
299 | "net/http"
300 | "strings"
301 |
302 | "github.com/gorilla/mux"
303 | )
304 |
305 | func handler(w http.ResponseWriter, r *http.Request) {
306 | return
307 | }
308 |
309 | func main() {
310 | r := mux.NewRouter()
311 | r.HandleFunc("/", handler)
312 | r.HandleFunc("/products", handler).Methods("POST")
313 | r.HandleFunc("/articles", handler).Methods("GET")
314 | r.HandleFunc("/articles/{id}", handler).Methods("GET", "PUT")
315 | r.HandleFunc("/authors", handler).Queries("surname", "{surname}")
316 | err := r.Walk(func(route *mux.Route, router *mux.Router, ancestors []*mux.Route) error {
317 | pathTemplate, err := route.GetPathTemplate()
318 | if err == nil {
319 | fmt.Println("ROUTE:", pathTemplate)
320 | }
321 | pathRegexp, err := route.GetPathRegexp()
322 | if err == nil {
323 | fmt.Println("Path regexp:", pathRegexp)
324 | }
325 | queriesTemplates, err := route.GetQueriesTemplates()
326 | if err == nil {
327 | fmt.Println("Queries templates:", strings.Join(queriesTemplates, ","))
328 | }
329 | queriesRegexps, err := route.GetQueriesRegexp()
330 | if err == nil {
331 | fmt.Println("Queries regexps:", strings.Join(queriesRegexps, ","))
332 | }
333 | methods, err := route.GetMethods()
334 | if err == nil {
335 | fmt.Println("Methods:", strings.Join(methods, ","))
336 | }
337 | fmt.Println()
338 | return nil
339 | })
340 |
341 | if err != nil {
342 | fmt.Println(err)
343 | }
344 |
345 | http.Handle("/", r)
346 | }
347 | ```
348 |
349 | ### Graceful Shutdown
350 |
351 | Go 1.8 introduced the ability to [gracefully shutdown](https://golang.org/doc/go1.8#http_shutdown) a `*http.Server`. Here's how to do that alongside `mux`:
352 |
353 | ```go
354 | package main
355 |
356 | import (
357 | "context"
358 | "flag"
359 | "log"
360 | "net/http"
361 | "os"
362 | "os/signal"
363 | "time"
364 |
365 | "github.com/gorilla/mux"
366 | )
367 |
368 | func main() {
369 | var wait time.Duration
370 | flag.DurationVar(&wait, "graceful-timeout", time.Second * 15, "the duration for which the server gracefully wait for existing connections to finish - e.g. 15s or 1m")
371 | flag.Parse()
372 |
373 | r := mux.NewRouter()
374 | // Add your routes as needed
375 |
376 | srv := &http.Server{
377 | Addr: "0.0.0.0:8080",
378 | // Good practice to set timeouts to avoid Slowloris attacks.
379 | WriteTimeout: time.Second * 15,
380 | ReadTimeout: time.Second * 15,
381 | IdleTimeout: time.Second * 60,
382 | Handler: r, // Pass our instance of gorilla/mux in.
383 | }
384 |
385 | // Run our server in a goroutine so that it doesn't block.
386 | go func() {
387 | if err := srv.ListenAndServe(); err != nil {
388 | log.Println(err)
389 | }
390 | }()
391 |
392 | c := make(chan os.Signal, 1)
393 | // We'll accept graceful shutdowns when quit via SIGINT (Ctrl+C)
394 | // SIGKILL, SIGQUIT or SIGTERM (Ctrl+/) will not be caught.
395 | signal.Notify(c, os.Interrupt)
396 |
397 | // Block until we receive our signal.
398 | <-c
399 |
400 | // Create a deadline to wait for.
401 | ctx, cancel := context.WithTimeout(context.Background(), wait)
402 | defer cancel()
403 | // Doesn't block if no connections, but will otherwise wait
404 | // until the timeout deadline.
405 | srv.Shutdown(ctx)
406 | // Optionally, you could run srv.Shutdown in a goroutine and block on
407 | // <-ctx.Done() if your application should wait for other services
408 | // to finalize based on context cancellation.
409 | log.Println("shutting down")
410 | os.Exit(0)
411 | }
412 | ```
413 |
414 | ### Middleware
415 |
416 | Mux supports the addition of middlewares to a [Router](https://godoc.org/github.com/gorilla/mux#Router), which are executed in the order they are added if a match is found, including its subrouters.
417 | Middlewares are (typically) small pieces of code which take one request, do something with it, and pass it down to another middleware or the final handler. Some common use cases for middleware are request logging, header manipulation, or `ResponseWriter` hijacking.
418 |
419 | Mux middlewares are defined using the de facto standard type:
420 |
421 | ```go
422 | type MiddlewareFunc func(http.Handler) http.Handler
423 | ```
424 |
425 | Typically, the returned handler is a closure which does something with the http.ResponseWriter and http.Request passed to it, and then calls the handler passed as parameter to the MiddlewareFunc. This takes advantage of closures being able access variables from the context where they are created, while retaining the signature enforced by the receivers.
426 |
427 | A very basic middleware which logs the URI of the request being handled could be written as:
428 |
429 | ```go
430 | func loggingMiddleware(next http.Handler) http.Handler {
431 | return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
432 | // Do stuff here
433 | log.Println(r.RequestURI)
434 | // Call the next handler, which can be another middleware in the chain, or the final handler.
435 | next.ServeHTTP(w, r)
436 | })
437 | }
438 | ```
439 |
440 | Middlewares can be added to a router using `Router.Use()`:
441 |
442 | ```go
443 | r := mux.NewRouter()
444 | r.HandleFunc("/", handler)
445 | r.Use(loggingMiddleware)
446 | ```
447 |
448 | A more complex authentication middleware, which maps session token to users, could be written as:
449 |
450 | ```go
451 | // Define our struct
452 | type authenticationMiddleware struct {
453 | tokenUsers map[string]string
454 | }
455 |
456 | // Initialize it somewhere
457 | func (amw *authenticationMiddleware) Populate() {
458 | amw.tokenUsers["00000000"] = "user0"
459 | amw.tokenUsers["aaaaaaaa"] = "userA"
460 | amw.tokenUsers["05f717e5"] = "randomUser"
461 | amw.tokenUsers["deadbeef"] = "user0"
462 | }
463 |
464 | // Middleware function, which will be called for each request
465 | func (amw *authenticationMiddleware) Middleware(next http.Handler) http.Handler {
466 | return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
467 | token := r.Header.Get("X-Session-Token")
468 |
469 | if user, found := amw.tokenUsers[token]; found {
470 | // We found the token in our map
471 | log.Printf("Authenticated user %s\n", user)
472 | // Pass down the request to the next middleware (or final handler)
473 | next.ServeHTTP(w, r)
474 | } else {
475 | // Write an error and stop the handler chain
476 | http.Error(w, "Forbidden", http.StatusForbidden)
477 | }
478 | })
479 | }
480 | ```
481 |
482 | ```go
483 | r := mux.NewRouter()
484 | r.HandleFunc("/", handler)
485 |
486 | amw := authenticationMiddleware{}
487 | amw.Populate()
488 |
489 | r.Use(amw.Middleware)
490 | ```
491 |
492 | Note: The handler chain will be stopped if your middleware doesn't call `next.ServeHTTP()` with the corresponding parameters. This can be used to abort a request if the middleware writer wants to. Middlewares _should_ write to `ResponseWriter` if they _are_ going to terminate the request, and they _should not_ write to `ResponseWriter` if they _are not_ going to terminate it.
493 |
494 | ### Testing Handlers
495 |
496 | Testing handlers in a Go web application is straightforward, and _mux_ doesn't complicate this any further. Given two files: `endpoints.go` and `endpoints_test.go`, here's how we'd test an application using _mux_.
497 |
498 | First, our simple HTTP handler:
499 |
500 | ```go
501 | // endpoints.go
502 | package main
503 |
504 | func HealthCheckHandler(w http.ResponseWriter, r *http.Request) {
505 | // A very simple health check.
506 | w.Header().Set("Content-Type", "application/json")
507 | w.WriteHeader(http.StatusOK)
508 |
509 | // In the future we could report back on the status of our DB, or our cache
510 | // (e.g. Redis) by performing a simple PING, and include them in the response.
511 | io.WriteString(w, `{"alive": true}`)
512 | }
513 |
514 | func main() {
515 | r := mux.NewRouter()
516 | r.HandleFunc("/health", HealthCheckHandler)
517 |
518 | log.Fatal(http.ListenAndServe("localhost:8080", r))
519 | }
520 | ```
521 |
522 | Our test code:
523 |
524 | ```go
525 | // endpoints_test.go
526 | package main
527 |
528 | import (
529 | "net/http"
530 | "net/http/httptest"
531 | "testing"
532 | )
533 |
534 | func TestHealthCheckHandler(t *testing.T) {
535 | // Create a request to pass to our handler. We don't have any query parameters for now, so we'll
536 | // pass 'nil' as the third parameter.
537 | req, err := http.NewRequest("GET", "/health", nil)
538 | if err != nil {
539 | t.Fatal(err)
540 | }
541 |
542 | // We create a ResponseRecorder (which satisfies http.ResponseWriter) to record the response.
543 | rr := httptest.NewRecorder()
544 | handler := http.HandlerFunc(HealthCheckHandler)
545 |
546 | // Our handlers satisfy http.Handler, so we can call their ServeHTTP method
547 | // directly and pass in our Request and ResponseRecorder.
548 | handler.ServeHTTP(rr, req)
549 |
550 | // Check the status code is what we expect.
551 | if status := rr.Code; status != http.StatusOK {
552 | t.Errorf("handler returned wrong status code: got %v want %v",
553 | status, http.StatusOK)
554 | }
555 |
556 | // Check the response body is what we expect.
557 | expected := `{"alive": true}`
558 | if rr.Body.String() != expected {
559 | t.Errorf("handler returned unexpected body: got %v want %v",
560 | rr.Body.String(), expected)
561 | }
562 | }
563 | ```
564 |
565 | In the case that our routes have [variables](#examples), we can pass those in the request. We could write
566 | [table-driven tests](https://dave.cheney.net/2013/06/09/writing-table-driven-tests-in-go) to test multiple
567 | possible route variables as needed.
568 |
569 | ```go
570 | // endpoints.go
571 | func main() {
572 | r := mux.NewRouter()
573 | // A route with a route variable:
574 | r.HandleFunc("/metrics/{type}", MetricsHandler)
575 |
576 | log.Fatal(http.ListenAndServe("localhost:8080", r))
577 | }
578 | ```
579 |
580 | Our test file, with a table-driven test of `routeVariables`:
581 |
582 | ```go
583 | // endpoints_test.go
584 | func TestMetricsHandler(t *testing.T) {
585 | tt := []struct{
586 | routeVariable string
587 | shouldPass bool
588 | }{
589 | {"goroutines", true},
590 | {"heap", true},
591 | {"counters", true},
592 | {"queries", true},
593 | {"adhadaeqm3k", false},
594 | }
595 |
596 | for _, tc := range tt {
597 | path := fmt.Sprintf("/metrics/%s", tc.routeVariable)
598 | req, err := http.NewRequest("GET", path, nil)
599 | if err != nil {
600 | t.Fatal(err)
601 | }
602 |
603 | rr := httptest.NewRecorder()
604 |
605 | // Need to create a router that we can pass the request through so that the vars will be added to the context
606 | router := mux.NewRouter()
607 | router.HandleFunc("/metrics/{type}", MetricsHandler)
608 | router.ServeHTTP(rr, req)
609 |
610 | // In this case, our MetricsHandler returns a non-200 response
611 | // for a route variable it doesn't know about.
612 | if rr.Code == http.StatusOK && !tc.shouldPass {
613 | t.Errorf("handler should have failed on routeVariable %s: got %v want %v",
614 | tc.routeVariable, rr.Code, http.StatusOK)
615 | }
616 | }
617 | }
618 | ```
619 |
620 | ## Full Example
621 |
622 | Here's a complete, runnable example of a small `mux` based server:
623 |
624 | ```go
625 | package main
626 |
627 | import (
628 | "net/http"
629 | "log"
630 | "github.com/gorilla/mux"
631 | )
632 |
633 | func YourHandler(w http.ResponseWriter, r *http.Request) {
634 | w.Write([]byte("Gorilla!\n"))
635 | }
636 |
637 | func main() {
638 | r := mux.NewRouter()
639 | // Routes consist of a path and a handler function.
640 | r.HandleFunc("/", YourHandler)
641 |
642 | // Bind to a port and pass our router in
643 | log.Fatal(http.ListenAndServe(":8000", r))
644 | }
645 | ```
646 |
647 | ## License
648 |
649 | BSD licensed. See the LICENSE file for details.
650 |
--------------------------------------------------------------------------------
/vendor/github.com/gorilla/mux/context.go:
--------------------------------------------------------------------------------
1 | package mux
2 |
3 | import (
4 | "context"
5 | "net/http"
6 | )
7 |
8 | func contextGet(r *http.Request, key interface{}) interface{} {
9 | return r.Context().Value(key)
10 | }
11 |
12 | func contextSet(r *http.Request, key, val interface{}) *http.Request {
13 | if val == nil {
14 | return r
15 | }
16 |
17 | return r.WithContext(context.WithValue(r.Context(), key, val))
18 | }
19 |
--------------------------------------------------------------------------------
/vendor/github.com/gorilla/mux/doc.go:
--------------------------------------------------------------------------------
1 | // Copyright 2012 The Gorilla Authors. All rights reserved.
2 | // Use of this source code is governed by a BSD-style
3 | // license that can be found in the LICENSE file.
4 |
5 | /*
6 | Package mux implements a request router and dispatcher.
7 |
8 | The name mux stands for "HTTP request multiplexer". Like the standard
9 | http.ServeMux, mux.Router matches incoming requests against a list of
10 | registered routes and calls a handler for the route that matches the URL
11 | or other conditions. The main features are:
12 |
13 | * Requests can be matched based on URL host, path, path prefix, schemes,
14 | header and query values, HTTP methods or using custom matchers.
15 | * URL hosts, paths and query values can have variables with an optional
16 | regular expression.
17 | * Registered URLs can be built, or "reversed", which helps maintaining
18 | references to resources.
19 | * Routes can be used as subrouters: nested routes are only tested if the
20 | parent route matches. This is useful to define groups of routes that
21 | share common conditions like a host, a path prefix or other repeated
22 | attributes. As a bonus, this optimizes request matching.
23 | * It implements the http.Handler interface so it is compatible with the
24 | standard http.ServeMux.
25 |
26 | Let's start registering a couple of URL paths and handlers:
27 |
28 | func main() {
29 | r := mux.NewRouter()
30 | r.HandleFunc("/", HomeHandler)
31 | r.HandleFunc("/products", ProductsHandler)
32 | r.HandleFunc("/articles", ArticlesHandler)
33 | http.Handle("/", r)
34 | }
35 |
36 | Here we register three routes mapping URL paths to handlers. This is
37 | equivalent to how http.HandleFunc() works: if an incoming request URL matches
38 | one of the paths, the corresponding handler is called passing
39 | (http.ResponseWriter, *http.Request) as parameters.
40 |
41 | Paths can have variables. They are defined using the format {name} or
42 | {name:pattern}. If a regular expression pattern is not defined, the matched
43 | variable will be anything until the next slash. For example:
44 |
45 | r := mux.NewRouter()
46 | r.HandleFunc("/products/{key}", ProductHandler)
47 | r.HandleFunc("/articles/{category}/", ArticlesCategoryHandler)
48 | r.HandleFunc("/articles/{category}/{id:[0-9]+}", ArticleHandler)
49 |
50 | Groups can be used inside patterns, as long as they are non-capturing (?:re). For example:
51 |
52 | r.HandleFunc("/articles/{category}/{sort:(?:asc|desc|new)}", ArticlesCategoryHandler)
53 |
54 | The names are used to create a map of route variables which can be retrieved
55 | calling mux.Vars():
56 |
57 | vars := mux.Vars(request)
58 | category := vars["category"]
59 |
60 | Note that if any capturing groups are present, mux will panic() during parsing. To prevent
61 | this, convert any capturing groups to non-capturing, e.g. change "/{sort:(asc|desc)}" to
62 | "/{sort:(?:asc|desc)}". This is a change from prior versions which behaved unpredictably
63 | when capturing groups were present.
64 |
65 | And this is all you need to know about the basic usage. More advanced options
66 | are explained below.
67 |
68 | Routes can also be restricted to a domain or subdomain. Just define a host
69 | pattern to be matched. They can also have variables:
70 |
71 | r := mux.NewRouter()
72 | // Only matches if domain is "www.example.com".
73 | r.Host("www.example.com")
74 | // Matches a dynamic subdomain.
75 | r.Host("{subdomain:[a-z]+}.domain.com")
76 |
77 | There are several other matchers that can be added. To match path prefixes:
78 |
79 | r.PathPrefix("/products/")
80 |
81 | ...or HTTP methods:
82 |
83 | r.Methods("GET", "POST")
84 |
85 | ...or URL schemes:
86 |
87 | r.Schemes("https")
88 |
89 | ...or header values:
90 |
91 | r.Headers("X-Requested-With", "XMLHttpRequest")
92 |
93 | ...or query values:
94 |
95 | r.Queries("key", "value")
96 |
97 | ...or to use a custom matcher function:
98 |
99 | r.MatcherFunc(func(r *http.Request, rm *RouteMatch) bool {
100 | return r.ProtoMajor == 0
101 | })
102 |
103 | ...and finally, it is possible to combine several matchers in a single route:
104 |
105 | r.HandleFunc("/products", ProductsHandler).
106 | Host("www.example.com").
107 | Methods("GET").
108 | Schemes("http")
109 |
110 | Setting the same matching conditions again and again can be boring, so we have
111 | a way to group several routes that share the same requirements.
112 | We call it "subrouting".
113 |
114 | For example, let's say we have several URLs that should only match when the
115 | host is "www.example.com". Create a route for that host and get a "subrouter"
116 | from it:
117 |
118 | r := mux.NewRouter()
119 | s := r.Host("www.example.com").Subrouter()
120 |
121 | Then register routes in the subrouter:
122 |
123 | s.HandleFunc("/products/", ProductsHandler)
124 | s.HandleFunc("/products/{key}", ProductHandler)
125 | s.HandleFunc("/articles/{category}/{id:[0-9]+}"), ArticleHandler)
126 |
127 | The three URL paths we registered above will only be tested if the domain is
128 | "www.example.com", because the subrouter is tested first. This is not
129 | only convenient, but also optimizes request matching. You can create
130 | subrouters combining any attribute matchers accepted by a route.
131 |
132 | Subrouters can be used to create domain or path "namespaces": you define
133 | subrouters in a central place and then parts of the app can register its
134 | paths relatively to a given subrouter.
135 |
136 | There's one more thing about subroutes. When a subrouter has a path prefix,
137 | the inner routes use it as base for their paths:
138 |
139 | r := mux.NewRouter()
140 | s := r.PathPrefix("/products").Subrouter()
141 | // "/products/"
142 | s.HandleFunc("/", ProductsHandler)
143 | // "/products/{key}/"
144 | s.HandleFunc("/{key}/", ProductHandler)
145 | // "/products/{key}/details"
146 | s.HandleFunc("/{key}/details", ProductDetailsHandler)
147 |
148 | Note that the path provided to PathPrefix() represents a "wildcard": calling
149 | PathPrefix("/static/").Handler(...) means that the handler will be passed any
150 | request that matches "/static/*". This makes it easy to serve static files with mux:
151 |
152 | func main() {
153 | var dir string
154 |
155 | flag.StringVar(&dir, "dir", ".", "the directory to serve files from. Defaults to the current dir")
156 | flag.Parse()
157 | r := mux.NewRouter()
158 |
159 | // This will serve files under http://localhost:8000/static/
160 | r.PathPrefix("/static/").Handler(http.StripPrefix("/static/", http.FileServer(http.Dir(dir))))
161 |
162 | srv := &http.Server{
163 | Handler: r,
164 | Addr: "127.0.0.1:8000",
165 | // Good practice: enforce timeouts for servers you create!
166 | WriteTimeout: 15 * time.Second,
167 | ReadTimeout: 15 * time.Second,
168 | }
169 |
170 | log.Fatal(srv.ListenAndServe())
171 | }
172 |
173 | Now let's see how to build registered URLs.
174 |
175 | Routes can be named. All routes that define a name can have their URLs built,
176 | or "reversed". We define a name calling Name() on a route. For example:
177 |
178 | r := mux.NewRouter()
179 | r.HandleFunc("/articles/{category}/{id:[0-9]+}", ArticleHandler).
180 | Name("article")
181 |
182 | To build a URL, get the route and call the URL() method, passing a sequence of
183 | key/value pairs for the route variables. For the previous route, we would do:
184 |
185 | url, err := r.Get("article").URL("category", "technology", "id", "42")
186 |
187 | ...and the result will be a url.URL with the following path:
188 |
189 | "/articles/technology/42"
190 |
191 | This also works for host and query value variables:
192 |
193 | r := mux.NewRouter()
194 | r.Host("{subdomain}.domain.com").
195 | Path("/articles/{category}/{id:[0-9]+}").
196 | Queries("filter", "{filter}").
197 | HandlerFunc(ArticleHandler).
198 | Name("article")
199 |
200 | // url.String() will be "http://news.domain.com/articles/technology/42?filter=gorilla"
201 | url, err := r.Get("article").URL("subdomain", "news",
202 | "category", "technology",
203 | "id", "42",
204 | "filter", "gorilla")
205 |
206 | All variables defined in the route are required, and their values must
207 | conform to the corresponding patterns. These requirements guarantee that a
208 | generated URL will always match a registered route -- the only exception is
209 | for explicitly defined "build-only" routes which never match.
210 |
211 | Regex support also exists for matching Headers within a route. For example, we could do:
212 |
213 | r.HeadersRegexp("Content-Type", "application/(text|json)")
214 |
215 | ...and the route will match both requests with a Content-Type of `application/json` as well as
216 | `application/text`
217 |
218 | There's also a way to build only the URL host or path for a route:
219 | use the methods URLHost() or URLPath() instead. For the previous route,
220 | we would do:
221 |
222 | // "http://news.domain.com/"
223 | host, err := r.Get("article").URLHost("subdomain", "news")
224 |
225 | // "/articles/technology/42"
226 | path, err := r.Get("article").URLPath("category", "technology", "id", "42")
227 |
228 | And if you use subrouters, host and path defined separately can be built
229 | as well:
230 |
231 | r := mux.NewRouter()
232 | s := r.Host("{subdomain}.domain.com").Subrouter()
233 | s.Path("/articles/{category}/{id:[0-9]+}").
234 | HandlerFunc(ArticleHandler).
235 | Name("article")
236 |
237 | // "http://news.domain.com/articles/technology/42"
238 | url, err := r.Get("article").URL("subdomain", "news",
239 | "category", "technology",
240 | "id", "42")
241 |
242 | Mux supports the addition of middlewares to a Router, which are executed in the order they are added if a match is found, including its subrouters. Middlewares are (typically) small pieces of code which take one request, do something with it, and pass it down to another middleware or the final handler. Some common use cases for middleware are request logging, header manipulation, or ResponseWriter hijacking.
243 |
244 | type MiddlewareFunc func(http.Handler) http.Handler
245 |
246 | Typically, the returned handler is a closure which does something with the http.ResponseWriter and http.Request passed to it, and then calls the handler passed as parameter to the MiddlewareFunc (closures can access variables from the context where they are created).
247 |
248 | A very basic middleware which logs the URI of the request being handled could be written as:
249 |
250 | func simpleMw(next http.Handler) http.Handler {
251 | return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
252 | // Do stuff here
253 | log.Println(r.RequestURI)
254 | // Call the next handler, which can be another middleware in the chain, or the final handler.
255 | next.ServeHTTP(w, r)
256 | })
257 | }
258 |
259 | Middlewares can be added to a router using `Router.Use()`:
260 |
261 | r := mux.NewRouter()
262 | r.HandleFunc("/", handler)
263 | r.Use(simpleMw)
264 |
265 | A more complex authentication middleware, which maps session token to users, could be written as:
266 |
267 | // Define our struct
268 | type authenticationMiddleware struct {
269 | tokenUsers map[string]string
270 | }
271 |
272 | // Initialize it somewhere
273 | func (amw *authenticationMiddleware) Populate() {
274 | amw.tokenUsers["00000000"] = "user0"
275 | amw.tokenUsers["aaaaaaaa"] = "userA"
276 | amw.tokenUsers["05f717e5"] = "randomUser"
277 | amw.tokenUsers["deadbeef"] = "user0"
278 | }
279 |
280 | // Middleware function, which will be called for each request
281 | func (amw *authenticationMiddleware) Middleware(next http.Handler) http.Handler {
282 | return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
283 | token := r.Header.Get("X-Session-Token")
284 |
285 | if user, found := amw.tokenUsers[token]; found {
286 | // We found the token in our map
287 | log.Printf("Authenticated user %s\n", user)
288 | next.ServeHTTP(w, r)
289 | } else {
290 | http.Error(w, "Forbidden", http.StatusForbidden)
291 | }
292 | })
293 | }
294 |
295 | r := mux.NewRouter()
296 | r.HandleFunc("/", handler)
297 |
298 | amw := authenticationMiddleware{}
299 | amw.Populate()
300 |
301 | r.Use(amw.Middleware)
302 |
303 | Note: The handler chain will be stopped if your middleware doesn't call `next.ServeHTTP()` with the corresponding parameters. This can be used to abort a request if the middleware writer wants to.
304 |
305 | */
306 | package mux
307 |
--------------------------------------------------------------------------------
/vendor/github.com/gorilla/mux/go.mod:
--------------------------------------------------------------------------------
1 | module github.com/gorilla/mux
2 |
--------------------------------------------------------------------------------
/vendor/github.com/gorilla/mux/middleware.go:
--------------------------------------------------------------------------------
1 | package mux
2 |
3 | import (
4 | "net/http"
5 | "strings"
6 | )
7 |
8 | // MiddlewareFunc is a function which receives an http.Handler and returns another http.Handler.
9 | // Typically, the returned handler is a closure which does something with the http.ResponseWriter and http.Request passed
10 | // to it, and then calls the handler passed as parameter to the MiddlewareFunc.
11 | type MiddlewareFunc func(http.Handler) http.Handler
12 |
13 | // middleware interface is anything which implements a MiddlewareFunc named Middleware.
14 | type middleware interface {
15 | Middleware(handler http.Handler) http.Handler
16 | }
17 |
18 | // Middleware allows MiddlewareFunc to implement the middleware interface.
19 | func (mw MiddlewareFunc) Middleware(handler http.Handler) http.Handler {
20 | return mw(handler)
21 | }
22 |
23 | // Use appends a MiddlewareFunc to the chain. Middleware can be used to intercept or otherwise modify requests and/or responses, and are executed in the order that they are applied to the Router.
24 | func (r *Router) Use(mwf ...MiddlewareFunc) {
25 | for _, fn := range mwf {
26 | r.middlewares = append(r.middlewares, fn)
27 | }
28 | }
29 |
30 | // useInterface appends a middleware to the chain. Middleware can be used to intercept or otherwise modify requests and/or responses, and are executed in the order that they are applied to the Router.
31 | func (r *Router) useInterface(mw middleware) {
32 | r.middlewares = append(r.middlewares, mw)
33 | }
34 |
35 | // CORSMethodMiddleware sets the Access-Control-Allow-Methods response header
36 | // on a request, by matching routes based only on paths. It also handles
37 | // OPTIONS requests, by settings Access-Control-Allow-Methods, and then
38 | // returning without calling the next http handler.
39 | func CORSMethodMiddleware(r *Router) MiddlewareFunc {
40 | return func(next http.Handler) http.Handler {
41 | return http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
42 | var allMethods []string
43 |
44 | err := r.Walk(func(route *Route, _ *Router, _ []*Route) error {
45 | for _, m := range route.matchers {
46 | if _, ok := m.(*routeRegexp); ok {
47 | if m.Match(req, &RouteMatch{}) {
48 | methods, err := route.GetMethods()
49 | if err != nil {
50 | return err
51 | }
52 |
53 | allMethods = append(allMethods, methods...)
54 | }
55 | break
56 | }
57 | }
58 | return nil
59 | })
60 |
61 | if err == nil {
62 | w.Header().Set("Access-Control-Allow-Methods", strings.Join(append(allMethods, "OPTIONS"), ","))
63 |
64 | if req.Method == "OPTIONS" {
65 | return
66 | }
67 | }
68 |
69 | next.ServeHTTP(w, req)
70 | })
71 | }
72 | }
73 |
--------------------------------------------------------------------------------
/vendor/github.com/gorilla/mux/mux.go:
--------------------------------------------------------------------------------
1 | // Copyright 2012 The Gorilla Authors. All rights reserved.
2 | // Use of this source code is governed by a BSD-style
3 | // license that can be found in the LICENSE file.
4 |
5 | package mux
6 |
7 | import (
8 | "errors"
9 | "fmt"
10 | "net/http"
11 | "path"
12 | "regexp"
13 | )
14 |
15 | var (
16 | // ErrMethodMismatch is returned when the method in the request does not match
17 | // the method defined against the route.
18 | ErrMethodMismatch = errors.New("method is not allowed")
19 | // ErrNotFound is returned when no route match is found.
20 | ErrNotFound = errors.New("no matching route was found")
21 | )
22 |
23 | // NewRouter returns a new router instance.
24 | func NewRouter() *Router {
25 | return &Router{namedRoutes: make(map[string]*Route)}
26 | }
27 |
28 | // Router registers routes to be matched and dispatches a handler.
29 | //
30 | // It implements the http.Handler interface, so it can be registered to serve
31 | // requests:
32 | //
33 | // var router = mux.NewRouter()
34 | //
35 | // func main() {
36 | // http.Handle("/", router)
37 | // }
38 | //
39 | // Or, for Google App Engine, register it in a init() function:
40 | //
41 | // func init() {
42 | // http.Handle("/", router)
43 | // }
44 | //
45 | // This will send all incoming requests to the router.
46 | type Router struct {
47 | // Configurable Handler to be used when no route matches.
48 | NotFoundHandler http.Handler
49 |
50 | // Configurable Handler to be used when the request method does not match the route.
51 | MethodNotAllowedHandler http.Handler
52 |
53 | // Routes to be matched, in order.
54 | routes []*Route
55 |
56 | // Routes by name for URL building.
57 | namedRoutes map[string]*Route
58 |
59 | // If true, do not clear the request context after handling the request.
60 | //
61 | // Deprecated: No effect when go1.7+ is used, since the context is stored
62 | // on the request itself.
63 | KeepContext bool
64 |
65 | // Slice of middlewares to be called after a match is found
66 | middlewares []middleware
67 |
68 | // configuration shared with `Route`
69 | routeConf
70 | }
71 |
72 | // common route configuration shared between `Router` and `Route`
73 | type routeConf struct {
74 | // If true, "/path/foo%2Fbar/to" will match the path "/path/{var}/to"
75 | useEncodedPath bool
76 |
77 | // If true, when the path pattern is "/path/", accessing "/path" will
78 | // redirect to the former and vice versa.
79 | strictSlash bool
80 |
81 | // If true, when the path pattern is "/path//to", accessing "/path//to"
82 | // will not redirect
83 | skipClean bool
84 |
85 | // Manager for the variables from host and path.
86 | regexp routeRegexpGroup
87 |
88 | // List of matchers.
89 | matchers []matcher
90 |
91 | // The scheme used when building URLs.
92 | buildScheme string
93 |
94 | buildVarsFunc BuildVarsFunc
95 | }
96 |
97 | // returns an effective deep copy of `routeConf`
98 | func copyRouteConf(r routeConf) routeConf {
99 | c := r
100 |
101 | if r.regexp.path != nil {
102 | c.regexp.path = copyRouteRegexp(r.regexp.path)
103 | }
104 |
105 | if r.regexp.host != nil {
106 | c.regexp.host = copyRouteRegexp(r.regexp.host)
107 | }
108 |
109 | c.regexp.queries = make([]*routeRegexp, 0, len(r.regexp.queries))
110 | for _, q := range r.regexp.queries {
111 | c.regexp.queries = append(c.regexp.queries, copyRouteRegexp(q))
112 | }
113 |
114 | c.matchers = make([]matcher, 0, len(r.matchers))
115 | for _, m := range r.matchers {
116 | c.matchers = append(c.matchers, m)
117 | }
118 |
119 | return c
120 | }
121 |
122 | func copyRouteRegexp(r *routeRegexp) *routeRegexp {
123 | c := *r
124 | return &c
125 | }
126 |
127 | // Match attempts to match the given request against the router's registered routes.
128 | //
129 | // If the request matches a route of this router or one of its subrouters the Route,
130 | // Handler, and Vars fields of the the match argument are filled and this function
131 | // returns true.
132 | //
133 | // If the request does not match any of this router's or its subrouters' routes
134 | // then this function returns false. If available, a reason for the match failure
135 | // will be filled in the match argument's MatchErr field. If the match failure type
136 | // (eg: not found) has a registered handler, the handler is assigned to the Handler
137 | // field of the match argument.
138 | func (r *Router) Match(req *http.Request, match *RouteMatch) bool {
139 | for _, route := range r.routes {
140 | if route.Match(req, match) {
141 | // Build middleware chain if no error was found
142 | if match.MatchErr == nil {
143 | for i := len(r.middlewares) - 1; i >= 0; i-- {
144 | match.Handler = r.middlewares[i].Middleware(match.Handler)
145 | }
146 | }
147 | return true
148 | }
149 | }
150 |
151 | if match.MatchErr == ErrMethodMismatch {
152 | if r.MethodNotAllowedHandler != nil {
153 | match.Handler = r.MethodNotAllowedHandler
154 | return true
155 | }
156 |
157 | return false
158 | }
159 |
160 | // Closest match for a router (includes sub-routers)
161 | if r.NotFoundHandler != nil {
162 | match.Handler = r.NotFoundHandler
163 | match.MatchErr = ErrNotFound
164 | return true
165 | }
166 |
167 | match.MatchErr = ErrNotFound
168 | return false
169 | }
170 |
171 | // ServeHTTP dispatches the handler registered in the matched route.
172 | //
173 | // When there is a match, the route variables can be retrieved calling
174 | // mux.Vars(request).
175 | func (r *Router) ServeHTTP(w http.ResponseWriter, req *http.Request) {
176 | if !r.skipClean {
177 | path := req.URL.Path
178 | if r.useEncodedPath {
179 | path = req.URL.EscapedPath()
180 | }
181 | // Clean path to canonical form and redirect.
182 | if p := cleanPath(path); p != path {
183 |
184 | // Added 3 lines (Philip Schlump) - It was dropping the query string and #whatever from query.
185 | // This matches with fix in go 1.2 r.c. 4 for same problem. Go Issue:
186 | // http://code.google.com/p/go/issues/detail?id=5252
187 | url := *req.URL
188 | url.Path = p
189 | p = url.String()
190 |
191 | w.Header().Set("Location", p)
192 | w.WriteHeader(http.StatusMovedPermanently)
193 | return
194 | }
195 | }
196 | var match RouteMatch
197 | var handler http.Handler
198 | if r.Match(req, &match) {
199 | handler = match.Handler
200 | req = setVars(req, match.Vars)
201 | req = setCurrentRoute(req, match.Route)
202 | }
203 |
204 | if handler == nil && match.MatchErr == ErrMethodMismatch {
205 | handler = methodNotAllowedHandler()
206 | }
207 |
208 | if handler == nil {
209 | handler = http.NotFoundHandler()
210 | }
211 |
212 | handler.ServeHTTP(w, req)
213 | }
214 |
215 | // Get returns a route registered with the given name.
216 | func (r *Router) Get(name string) *Route {
217 | return r.namedRoutes[name]
218 | }
219 |
220 | // GetRoute returns a route registered with the given name. This method
221 | // was renamed to Get() and remains here for backwards compatibility.
222 | func (r *Router) GetRoute(name string) *Route {
223 | return r.namedRoutes[name]
224 | }
225 |
226 | // StrictSlash defines the trailing slash behavior for new routes. The initial
227 | // value is false.
228 | //
229 | // When true, if the route path is "/path/", accessing "/path" will perform a redirect
230 | // to the former and vice versa. In other words, your application will always
231 | // see the path as specified in the route.
232 | //
233 | // When false, if the route path is "/path", accessing "/path/" will not match
234 | // this route and vice versa.
235 | //
236 | // The re-direct is a HTTP 301 (Moved Permanently). Note that when this is set for
237 | // routes with a non-idempotent method (e.g. POST, PUT), the subsequent re-directed
238 | // request will be made as a GET by most clients. Use middleware or client settings
239 | // to modify this behaviour as needed.
240 | //
241 | // Special case: when a route sets a path prefix using the PathPrefix() method,
242 | // strict slash is ignored for that route because the redirect behavior can't
243 | // be determined from a prefix alone. However, any subrouters created from that
244 | // route inherit the original StrictSlash setting.
245 | func (r *Router) StrictSlash(value bool) *Router {
246 | r.strictSlash = value
247 | return r
248 | }
249 |
250 | // SkipClean defines the path cleaning behaviour for new routes. The initial
251 | // value is false. Users should be careful about which routes are not cleaned
252 | //
253 | // When true, if the route path is "/path//to", it will remain with the double
254 | // slash. This is helpful if you have a route like: /fetch/http://xkcd.com/534/
255 | //
256 | // When false, the path will be cleaned, so /fetch/http://xkcd.com/534/ will
257 | // become /fetch/http/xkcd.com/534
258 | func (r *Router) SkipClean(value bool) *Router {
259 | r.skipClean = value
260 | return r
261 | }
262 |
263 | // UseEncodedPath tells the router to match the encoded original path
264 | // to the routes.
265 | // For eg. "/path/foo%2Fbar/to" will match the path "/path/{var}/to".
266 | //
267 | // If not called, the router will match the unencoded path to the routes.
268 | // For eg. "/path/foo%2Fbar/to" will match the path "/path/foo/bar/to"
269 | func (r *Router) UseEncodedPath() *Router {
270 | r.useEncodedPath = true
271 | return r
272 | }
273 |
274 | // ----------------------------------------------------------------------------
275 | // Route factories
276 | // ----------------------------------------------------------------------------
277 |
278 | // NewRoute registers an empty route.
279 | func (r *Router) NewRoute() *Route {
280 | // initialize a route with a copy of the parent router's configuration
281 | route := &Route{routeConf: copyRouteConf(r.routeConf), namedRoutes: r.namedRoutes}
282 | r.routes = append(r.routes, route)
283 | return route
284 | }
285 |
286 | // Name registers a new route with a name.
287 | // See Route.Name().
288 | func (r *Router) Name(name string) *Route {
289 | return r.NewRoute().Name(name)
290 | }
291 |
292 | // Handle registers a new route with a matcher for the URL path.
293 | // See Route.Path() and Route.Handler().
294 | func (r *Router) Handle(path string, handler http.Handler) *Route {
295 | return r.NewRoute().Path(path).Handler(handler)
296 | }
297 |
298 | // HandleFunc registers a new route with a matcher for the URL path.
299 | // See Route.Path() and Route.HandlerFunc().
300 | func (r *Router) HandleFunc(path string, f func(http.ResponseWriter,
301 | *http.Request)) *Route {
302 | return r.NewRoute().Path(path).HandlerFunc(f)
303 | }
304 |
305 | // Headers registers a new route with a matcher for request header values.
306 | // See Route.Headers().
307 | func (r *Router) Headers(pairs ...string) *Route {
308 | return r.NewRoute().Headers(pairs...)
309 | }
310 |
311 | // Host registers a new route with a matcher for the URL host.
312 | // See Route.Host().
313 | func (r *Router) Host(tpl string) *Route {
314 | return r.NewRoute().Host(tpl)
315 | }
316 |
317 | // MatcherFunc registers a new route with a custom matcher function.
318 | // See Route.MatcherFunc().
319 | func (r *Router) MatcherFunc(f MatcherFunc) *Route {
320 | return r.NewRoute().MatcherFunc(f)
321 | }
322 |
323 | // Methods registers a new route with a matcher for HTTP methods.
324 | // See Route.Methods().
325 | func (r *Router) Methods(methods ...string) *Route {
326 | return r.NewRoute().Methods(methods...)
327 | }
328 |
329 | // Path registers a new route with a matcher for the URL path.
330 | // See Route.Path().
331 | func (r *Router) Path(tpl string) *Route {
332 | return r.NewRoute().Path(tpl)
333 | }
334 |
335 | // PathPrefix registers a new route with a matcher for the URL path prefix.
336 | // See Route.PathPrefix().
337 | func (r *Router) PathPrefix(tpl string) *Route {
338 | return r.NewRoute().PathPrefix(tpl)
339 | }
340 |
341 | // Queries registers a new route with a matcher for URL query values.
342 | // See Route.Queries().
343 | func (r *Router) Queries(pairs ...string) *Route {
344 | return r.NewRoute().Queries(pairs...)
345 | }
346 |
347 | // Schemes registers a new route with a matcher for URL schemes.
348 | // See Route.Schemes().
349 | func (r *Router) Schemes(schemes ...string) *Route {
350 | return r.NewRoute().Schemes(schemes...)
351 | }
352 |
353 | // BuildVarsFunc registers a new route with a custom function for modifying
354 | // route variables before building a URL.
355 | func (r *Router) BuildVarsFunc(f BuildVarsFunc) *Route {
356 | return r.NewRoute().BuildVarsFunc(f)
357 | }
358 |
359 | // Walk walks the router and all its sub-routers, calling walkFn for each route
360 | // in the tree. The routes are walked in the order they were added. Sub-routers
361 | // are explored depth-first.
362 | func (r *Router) Walk(walkFn WalkFunc) error {
363 | return r.walk(walkFn, []*Route{})
364 | }
365 |
366 | // SkipRouter is used as a return value from WalkFuncs to indicate that the
367 | // router that walk is about to descend down to should be skipped.
368 | var SkipRouter = errors.New("skip this router")
369 |
370 | // WalkFunc is the type of the function called for each route visited by Walk.
371 | // At every invocation, it is given the current route, and the current router,
372 | // and a list of ancestor routes that lead to the current route.
373 | type WalkFunc func(route *Route, router *Router, ancestors []*Route) error
374 |
375 | func (r *Router) walk(walkFn WalkFunc, ancestors []*Route) error {
376 | for _, t := range r.routes {
377 | err := walkFn(t, r, ancestors)
378 | if err == SkipRouter {
379 | continue
380 | }
381 | if err != nil {
382 | return err
383 | }
384 | for _, sr := range t.matchers {
385 | if h, ok := sr.(*Router); ok {
386 | ancestors = append(ancestors, t)
387 | err := h.walk(walkFn, ancestors)
388 | if err != nil {
389 | return err
390 | }
391 | ancestors = ancestors[:len(ancestors)-1]
392 | }
393 | }
394 | if h, ok := t.handler.(*Router); ok {
395 | ancestors = append(ancestors, t)
396 | err := h.walk(walkFn, ancestors)
397 | if err != nil {
398 | return err
399 | }
400 | ancestors = ancestors[:len(ancestors)-1]
401 | }
402 | }
403 | return nil
404 | }
405 |
406 | // ----------------------------------------------------------------------------
407 | // Context
408 | // ----------------------------------------------------------------------------
409 |
410 | // RouteMatch stores information about a matched route.
411 | type RouteMatch struct {
412 | Route *Route
413 | Handler http.Handler
414 | Vars map[string]string
415 |
416 | // MatchErr is set to appropriate matching error
417 | // It is set to ErrMethodMismatch if there is a mismatch in
418 | // the request method and route method
419 | MatchErr error
420 | }
421 |
422 | type contextKey int
423 |
424 | const (
425 | varsKey contextKey = iota
426 | routeKey
427 | )
428 |
429 | // Vars returns the route variables for the current request, if any.
430 | func Vars(r *http.Request) map[string]string {
431 | if rv := contextGet(r, varsKey); rv != nil {
432 | return rv.(map[string]string)
433 | }
434 | return nil
435 | }
436 |
437 | // CurrentRoute returns the matched route for the current request, if any.
438 | // This only works when called inside the handler of the matched route
439 | // because the matched route is stored in the request context which is cleared
440 | // after the handler returns, unless the KeepContext option is set on the
441 | // Router.
442 | func CurrentRoute(r *http.Request) *Route {
443 | if rv := contextGet(r, routeKey); rv != nil {
444 | return rv.(*Route)
445 | }
446 | return nil
447 | }
448 |
449 | func setVars(r *http.Request, val interface{}) *http.Request {
450 | return contextSet(r, varsKey, val)
451 | }
452 |
453 | func setCurrentRoute(r *http.Request, val interface{}) *http.Request {
454 | return contextSet(r, routeKey, val)
455 | }
456 |
457 | // ----------------------------------------------------------------------------
458 | // Helpers
459 | // ----------------------------------------------------------------------------
460 |
461 | // cleanPath returns the canonical path for p, eliminating . and .. elements.
462 | // Borrowed from the net/http package.
463 | func cleanPath(p string) string {
464 | if p == "" {
465 | return "/"
466 | }
467 | if p[0] != '/' {
468 | p = "/" + p
469 | }
470 | np := path.Clean(p)
471 | // path.Clean removes trailing slash except for root;
472 | // put the trailing slash back if necessary.
473 | if p[len(p)-1] == '/' && np != "/" {
474 | np += "/"
475 | }
476 |
477 | return np
478 | }
479 |
480 | // uniqueVars returns an error if two slices contain duplicated strings.
481 | func uniqueVars(s1, s2 []string) error {
482 | for _, v1 := range s1 {
483 | for _, v2 := range s2 {
484 | if v1 == v2 {
485 | return fmt.Errorf("mux: duplicated route variable %q", v2)
486 | }
487 | }
488 | }
489 | return nil
490 | }
491 |
492 | // checkPairs returns the count of strings passed in, and an error if
493 | // the count is not an even number.
494 | func checkPairs(pairs ...string) (int, error) {
495 | length := len(pairs)
496 | if length%2 != 0 {
497 | return length, fmt.Errorf(
498 | "mux: number of parameters must be multiple of 2, got %v", pairs)
499 | }
500 | return length, nil
501 | }
502 |
503 | // mapFromPairsToString converts variadic string parameters to a
504 | // string to string map.
505 | func mapFromPairsToString(pairs ...string) (map[string]string, error) {
506 | length, err := checkPairs(pairs...)
507 | if err != nil {
508 | return nil, err
509 | }
510 | m := make(map[string]string, length/2)
511 | for i := 0; i < length; i += 2 {
512 | m[pairs[i]] = pairs[i+1]
513 | }
514 | return m, nil
515 | }
516 |
517 | // mapFromPairsToRegex converts variadic string parameters to a
518 | // string to regex map.
519 | func mapFromPairsToRegex(pairs ...string) (map[string]*regexp.Regexp, error) {
520 | length, err := checkPairs(pairs...)
521 | if err != nil {
522 | return nil, err
523 | }
524 | m := make(map[string]*regexp.Regexp, length/2)
525 | for i := 0; i < length; i += 2 {
526 | regex, err := regexp.Compile(pairs[i+1])
527 | if err != nil {
528 | return nil, err
529 | }
530 | m[pairs[i]] = regex
531 | }
532 | return m, nil
533 | }
534 |
535 | // matchInArray returns true if the given string value is in the array.
536 | func matchInArray(arr []string, value string) bool {
537 | for _, v := range arr {
538 | if v == value {
539 | return true
540 | }
541 | }
542 | return false
543 | }
544 |
545 | // matchMapWithString returns true if the given key/value pairs exist in a given map.
546 | func matchMapWithString(toCheck map[string]string, toMatch map[string][]string, canonicalKey bool) bool {
547 | for k, v := range toCheck {
548 | // Check if key exists.
549 | if canonicalKey {
550 | k = http.CanonicalHeaderKey(k)
551 | }
552 | if values := toMatch[k]; values == nil {
553 | return false
554 | } else if v != "" {
555 | // If value was defined as an empty string we only check that the
556 | // key exists. Otherwise we also check for equality.
557 | valueExists := false
558 | for _, value := range values {
559 | if v == value {
560 | valueExists = true
561 | break
562 | }
563 | }
564 | if !valueExists {
565 | return false
566 | }
567 | }
568 | }
569 | return true
570 | }
571 |
572 | // matchMapWithRegex returns true if the given key/value pairs exist in a given map compiled against
573 | // the given regex
574 | func matchMapWithRegex(toCheck map[string]*regexp.Regexp, toMatch map[string][]string, canonicalKey bool) bool {
575 | for k, v := range toCheck {
576 | // Check if key exists.
577 | if canonicalKey {
578 | k = http.CanonicalHeaderKey(k)
579 | }
580 | if values := toMatch[k]; values == nil {
581 | return false
582 | } else if v != nil {
583 | // If value was defined as an empty string we only check that the
584 | // key exists. Otherwise we also check for equality.
585 | valueExists := false
586 | for _, value := range values {
587 | if v.MatchString(value) {
588 | valueExists = true
589 | break
590 | }
591 | }
592 | if !valueExists {
593 | return false
594 | }
595 | }
596 | }
597 | return true
598 | }
599 |
600 | // methodNotAllowed replies to the request with an HTTP status code 405.
601 | func methodNotAllowed(w http.ResponseWriter, r *http.Request) {
602 | w.WriteHeader(http.StatusMethodNotAllowed)
603 | }
604 |
605 | // methodNotAllowedHandler returns a simple request handler
606 | // that replies to each request with a status code 405.
607 | func methodNotAllowedHandler() http.Handler { return http.HandlerFunc(methodNotAllowed) }
608 |
--------------------------------------------------------------------------------
/vendor/github.com/gorilla/mux/regexp.go:
--------------------------------------------------------------------------------
1 | // Copyright 2012 The Gorilla Authors. All rights reserved.
2 | // Use of this source code is governed by a BSD-style
3 | // license that can be found in the LICENSE file.
4 |
5 | package mux
6 |
7 | import (
8 | "bytes"
9 | "fmt"
10 | "net/http"
11 | "net/url"
12 | "regexp"
13 | "strconv"
14 | "strings"
15 | )
16 |
17 | type routeRegexpOptions struct {
18 | strictSlash bool
19 | useEncodedPath bool
20 | }
21 |
22 | type regexpType int
23 |
24 | const (
25 | regexpTypePath regexpType = 0
26 | regexpTypeHost regexpType = 1
27 | regexpTypePrefix regexpType = 2
28 | regexpTypeQuery regexpType = 3
29 | )
30 |
31 | // newRouteRegexp parses a route template and returns a routeRegexp,
32 | // used to match a host, a path or a query string.
33 | //
34 | // It will extract named variables, assemble a regexp to be matched, create
35 | // a "reverse" template to build URLs and compile regexps to validate variable
36 | // values used in URL building.
37 | //
38 | // Previously we accepted only Python-like identifiers for variable
39 | // names ([a-zA-Z_][a-zA-Z0-9_]*), but currently the only restriction is that
40 | // name and pattern can't be empty, and names can't contain a colon.
41 | func newRouteRegexp(tpl string, typ regexpType, options routeRegexpOptions) (*routeRegexp, error) {
42 | // Check if it is well-formed.
43 | idxs, errBraces := braceIndices(tpl)
44 | if errBraces != nil {
45 | return nil, errBraces
46 | }
47 | // Backup the original.
48 | template := tpl
49 | // Now let's parse it.
50 | defaultPattern := "[^/]+"
51 | if typ == regexpTypeQuery {
52 | defaultPattern = ".*"
53 | } else if typ == regexpTypeHost {
54 | defaultPattern = "[^.]+"
55 | }
56 | // Only match strict slash if not matching
57 | if typ != regexpTypePath {
58 | options.strictSlash = false
59 | }
60 | // Set a flag for strictSlash.
61 | endSlash := false
62 | if options.strictSlash && strings.HasSuffix(tpl, "/") {
63 | tpl = tpl[:len(tpl)-1]
64 | endSlash = true
65 | }
66 | varsN := make([]string, len(idxs)/2)
67 | varsR := make([]*regexp.Regexp, len(idxs)/2)
68 | pattern := bytes.NewBufferString("")
69 | pattern.WriteByte('^')
70 | reverse := bytes.NewBufferString("")
71 | var end int
72 | var err error
73 | for i := 0; i < len(idxs); i += 2 {
74 | // Set all values we are interested in.
75 | raw := tpl[end:idxs[i]]
76 | end = idxs[i+1]
77 | parts := strings.SplitN(tpl[idxs[i]+1:end-1], ":", 2)
78 | name := parts[0]
79 | patt := defaultPattern
80 | if len(parts) == 2 {
81 | patt = parts[1]
82 | }
83 | // Name or pattern can't be empty.
84 | if name == "" || patt == "" {
85 | return nil, fmt.Errorf("mux: missing name or pattern in %q",
86 | tpl[idxs[i]:end])
87 | }
88 | // Build the regexp pattern.
89 | fmt.Fprintf(pattern, "%s(?P<%s>%s)", regexp.QuoteMeta(raw), varGroupName(i/2), patt)
90 |
91 | // Build the reverse template.
92 | fmt.Fprintf(reverse, "%s%%s", raw)
93 |
94 | // Append variable name and compiled pattern.
95 | varsN[i/2] = name
96 | varsR[i/2], err = regexp.Compile(fmt.Sprintf("^%s$", patt))
97 | if err != nil {
98 | return nil, err
99 | }
100 | }
101 | // Add the remaining.
102 | raw := tpl[end:]
103 | pattern.WriteString(regexp.QuoteMeta(raw))
104 | if options.strictSlash {
105 | pattern.WriteString("[/]?")
106 | }
107 | if typ == regexpTypeQuery {
108 | // Add the default pattern if the query value is empty
109 | if queryVal := strings.SplitN(template, "=", 2)[1]; queryVal == "" {
110 | pattern.WriteString(defaultPattern)
111 | }
112 | }
113 | if typ != regexpTypePrefix {
114 | pattern.WriteByte('$')
115 | }
116 | reverse.WriteString(raw)
117 | if endSlash {
118 | reverse.WriteByte('/')
119 | }
120 | // Compile full regexp.
121 | reg, errCompile := regexp.Compile(pattern.String())
122 | if errCompile != nil {
123 | return nil, errCompile
124 | }
125 |
126 | // Check for capturing groups which used to work in older versions
127 | if reg.NumSubexp() != len(idxs)/2 {
128 | panic(fmt.Sprintf("route %s contains capture groups in its regexp. ", template) +
129 | "Only non-capturing groups are accepted: e.g. (?:pattern) instead of (pattern)")
130 | }
131 |
132 | // Done!
133 | return &routeRegexp{
134 | template: template,
135 | regexpType: typ,
136 | options: options,
137 | regexp: reg,
138 | reverse: reverse.String(),
139 | varsN: varsN,
140 | varsR: varsR,
141 | }, nil
142 | }
143 |
144 | // routeRegexp stores a regexp to match a host or path and information to
145 | // collect and validate route variables.
146 | type routeRegexp struct {
147 | // The unmodified template.
148 | template string
149 | // The type of match
150 | regexpType regexpType
151 | // Options for matching
152 | options routeRegexpOptions
153 | // Expanded regexp.
154 | regexp *regexp.Regexp
155 | // Reverse template.
156 | reverse string
157 | // Variable names.
158 | varsN []string
159 | // Variable regexps (validators).
160 | varsR []*regexp.Regexp
161 | }
162 |
163 | // Match matches the regexp against the URL host or path.
164 | func (r *routeRegexp) Match(req *http.Request, match *RouteMatch) bool {
165 | if r.regexpType != regexpTypeHost {
166 | if r.regexpType == regexpTypeQuery {
167 | return r.matchQueryString(req)
168 | }
169 | path := req.URL.Path
170 | if r.options.useEncodedPath {
171 | path = req.URL.EscapedPath()
172 | }
173 | return r.regexp.MatchString(path)
174 | }
175 |
176 | return r.regexp.MatchString(getHost(req))
177 | }
178 |
179 | // url builds a URL part using the given values.
180 | func (r *routeRegexp) url(values map[string]string) (string, error) {
181 | urlValues := make([]interface{}, len(r.varsN))
182 | for k, v := range r.varsN {
183 | value, ok := values[v]
184 | if !ok {
185 | return "", fmt.Errorf("mux: missing route variable %q", v)
186 | }
187 | if r.regexpType == regexpTypeQuery {
188 | value = url.QueryEscape(value)
189 | }
190 | urlValues[k] = value
191 | }
192 | rv := fmt.Sprintf(r.reverse, urlValues...)
193 | if !r.regexp.MatchString(rv) {
194 | // The URL is checked against the full regexp, instead of checking
195 | // individual variables. This is faster but to provide a good error
196 | // message, we check individual regexps if the URL doesn't match.
197 | for k, v := range r.varsN {
198 | if !r.varsR[k].MatchString(values[v]) {
199 | return "", fmt.Errorf(
200 | "mux: variable %q doesn't match, expected %q", values[v],
201 | r.varsR[k].String())
202 | }
203 | }
204 | }
205 | return rv, nil
206 | }
207 |
208 | // getURLQuery returns a single query parameter from a request URL.
209 | // For a URL with foo=bar&baz=ding, we return only the relevant key
210 | // value pair for the routeRegexp.
211 | func (r *routeRegexp) getURLQuery(req *http.Request) string {
212 | if r.regexpType != regexpTypeQuery {
213 | return ""
214 | }
215 | templateKey := strings.SplitN(r.template, "=", 2)[0]
216 | for key, vals := range req.URL.Query() {
217 | if key == templateKey && len(vals) > 0 {
218 | return key + "=" + vals[0]
219 | }
220 | }
221 | return ""
222 | }
223 |
224 | func (r *routeRegexp) matchQueryString(req *http.Request) bool {
225 | return r.regexp.MatchString(r.getURLQuery(req))
226 | }
227 |
228 | // braceIndices returns the first level curly brace indices from a string.
229 | // It returns an error in case of unbalanced braces.
230 | func braceIndices(s string) ([]int, error) {
231 | var level, idx int
232 | var idxs []int
233 | for i := 0; i < len(s); i++ {
234 | switch s[i] {
235 | case '{':
236 | if level++; level == 1 {
237 | idx = i
238 | }
239 | case '}':
240 | if level--; level == 0 {
241 | idxs = append(idxs, idx, i+1)
242 | } else if level < 0 {
243 | return nil, fmt.Errorf("mux: unbalanced braces in %q", s)
244 | }
245 | }
246 | }
247 | if level != 0 {
248 | return nil, fmt.Errorf("mux: unbalanced braces in %q", s)
249 | }
250 | return idxs, nil
251 | }
252 |
253 | // varGroupName builds a capturing group name for the indexed variable.
254 | func varGroupName(idx int) string {
255 | return "v" + strconv.Itoa(idx)
256 | }
257 |
258 | // ----------------------------------------------------------------------------
259 | // routeRegexpGroup
260 | // ----------------------------------------------------------------------------
261 |
262 | // routeRegexpGroup groups the route matchers that carry variables.
263 | type routeRegexpGroup struct {
264 | host *routeRegexp
265 | path *routeRegexp
266 | queries []*routeRegexp
267 | }
268 |
269 | // setMatch extracts the variables from the URL once a route matches.
270 | func (v routeRegexpGroup) setMatch(req *http.Request, m *RouteMatch, r *Route) {
271 | // Store host variables.
272 | if v.host != nil {
273 | host := getHost(req)
274 | matches := v.host.regexp.FindStringSubmatchIndex(host)
275 | if len(matches) > 0 {
276 | extractVars(host, matches, v.host.varsN, m.Vars)
277 | }
278 | }
279 | path := req.URL.Path
280 | if r.useEncodedPath {
281 | path = req.URL.EscapedPath()
282 | }
283 | // Store path variables.
284 | if v.path != nil {
285 | matches := v.path.regexp.FindStringSubmatchIndex(path)
286 | if len(matches) > 0 {
287 | extractVars(path, matches, v.path.varsN, m.Vars)
288 | // Check if we should redirect.
289 | if v.path.options.strictSlash {
290 | p1 := strings.HasSuffix(path, "/")
291 | p2 := strings.HasSuffix(v.path.template, "/")
292 | if p1 != p2 {
293 | u, _ := url.Parse(req.URL.String())
294 | if p1 {
295 | u.Path = u.Path[:len(u.Path)-1]
296 | } else {
297 | u.Path += "/"
298 | }
299 | m.Handler = http.RedirectHandler(u.String(), http.StatusMovedPermanently)
300 | }
301 | }
302 | }
303 | }
304 | // Store query string variables.
305 | for _, q := range v.queries {
306 | queryURL := q.getURLQuery(req)
307 | matches := q.regexp.FindStringSubmatchIndex(queryURL)
308 | if len(matches) > 0 {
309 | extractVars(queryURL, matches, q.varsN, m.Vars)
310 | }
311 | }
312 | }
313 |
314 | // getHost tries its best to return the request host.
315 | // According to section 14.23 of RFC 2616 the Host header
316 | // can include the port number if the default value of 80 is not used.
317 | func getHost(r *http.Request) string {
318 | if r.URL.IsAbs() {
319 | return r.URL.Host
320 | }
321 | return r.Host
322 | }
323 |
324 | func extractVars(input string, matches []int, names []string, output map[string]string) {
325 | for i, name := range names {
326 | output[name] = input[matches[2*i+2]:matches[2*i+3]]
327 | }
328 | }
329 |
--------------------------------------------------------------------------------
/vendor/github.com/gorilla/mux/route.go:
--------------------------------------------------------------------------------
1 | // Copyright 2012 The Gorilla Authors. All rights reserved.
2 | // Use of this source code is governed by a BSD-style
3 | // license that can be found in the LICENSE file.
4 |
5 | package mux
6 |
7 | import (
8 | "errors"
9 | "fmt"
10 | "net/http"
11 | "net/url"
12 | "regexp"
13 | "strings"
14 | )
15 |
16 | // Route stores information to match a request and build URLs.
17 | type Route struct {
18 | // Request handler for the route.
19 | handler http.Handler
20 | // If true, this route never matches: it is only used to build URLs.
21 | buildOnly bool
22 | // The name used to build URLs.
23 | name string
24 | // Error resulted from building a route.
25 | err error
26 |
27 | // "global" reference to all named routes
28 | namedRoutes map[string]*Route
29 |
30 | // config possibly passed in from `Router`
31 | routeConf
32 | }
33 |
34 | // SkipClean reports whether path cleaning is enabled for this route via
35 | // Router.SkipClean.
36 | func (r *Route) SkipClean() bool {
37 | return r.skipClean
38 | }
39 |
40 | // Match matches the route against the request.
41 | func (r *Route) Match(req *http.Request, match *RouteMatch) bool {
42 | if r.buildOnly || r.err != nil {
43 | return false
44 | }
45 |
46 | var matchErr error
47 |
48 | // Match everything.
49 | for _, m := range r.matchers {
50 | if matched := m.Match(req, match); !matched {
51 | if _, ok := m.(methodMatcher); ok {
52 | matchErr = ErrMethodMismatch
53 | continue
54 | }
55 |
56 | // Ignore ErrNotFound errors. These errors arise from match call
57 | // to Subrouters.
58 | //
59 | // This prevents subsequent matching subrouters from failing to
60 | // run middleware. If not ignored, the middleware would see a
61 | // non-nil MatchErr and be skipped, even when there was a
62 | // matching route.
63 | if match.MatchErr == ErrNotFound {
64 | match.MatchErr = nil
65 | }
66 |
67 | matchErr = nil
68 | return false
69 | }
70 | }
71 |
72 | if matchErr != nil {
73 | match.MatchErr = matchErr
74 | return false
75 | }
76 |
77 | if match.MatchErr == ErrMethodMismatch {
78 | // We found a route which matches request method, clear MatchErr
79 | match.MatchErr = nil
80 | // Then override the mis-matched handler
81 | match.Handler = r.handler
82 | }
83 |
84 | // Yay, we have a match. Let's collect some info about it.
85 | if match.Route == nil {
86 | match.Route = r
87 | }
88 | if match.Handler == nil {
89 | match.Handler = r.handler
90 | }
91 | if match.Vars == nil {
92 | match.Vars = make(map[string]string)
93 | }
94 |
95 | // Set variables.
96 | r.regexp.setMatch(req, match, r)
97 | return true
98 | }
99 |
100 | // ----------------------------------------------------------------------------
101 | // Route attributes
102 | // ----------------------------------------------------------------------------
103 |
104 | // GetError returns an error resulted from building the route, if any.
105 | func (r *Route) GetError() error {
106 | return r.err
107 | }
108 |
109 | // BuildOnly sets the route to never match: it is only used to build URLs.
110 | func (r *Route) BuildOnly() *Route {
111 | r.buildOnly = true
112 | return r
113 | }
114 |
115 | // Handler --------------------------------------------------------------------
116 |
117 | // Handler sets a handler for the route.
118 | func (r *Route) Handler(handler http.Handler) *Route {
119 | if r.err == nil {
120 | r.handler = handler
121 | }
122 | return r
123 | }
124 |
125 | // HandlerFunc sets a handler function for the route.
126 | func (r *Route) HandlerFunc(f func(http.ResponseWriter, *http.Request)) *Route {
127 | return r.Handler(http.HandlerFunc(f))
128 | }
129 |
130 | // GetHandler returns the handler for the route, if any.
131 | func (r *Route) GetHandler() http.Handler {
132 | return r.handler
133 | }
134 |
135 | // Name -----------------------------------------------------------------------
136 |
137 | // Name sets the name for the route, used to build URLs.
138 | // It is an error to call Name more than once on a route.
139 | func (r *Route) Name(name string) *Route {
140 | if r.name != "" {
141 | r.err = fmt.Errorf("mux: route already has name %q, can't set %q",
142 | r.name, name)
143 | }
144 | if r.err == nil {
145 | r.name = name
146 | r.namedRoutes[name] = r
147 | }
148 | return r
149 | }
150 |
151 | // GetName returns the name for the route, if any.
152 | func (r *Route) GetName() string {
153 | return r.name
154 | }
155 |
156 | // ----------------------------------------------------------------------------
157 | // Matchers
158 | // ----------------------------------------------------------------------------
159 |
160 | // matcher types try to match a request.
161 | type matcher interface {
162 | Match(*http.Request, *RouteMatch) bool
163 | }
164 |
165 | // addMatcher adds a matcher to the route.
166 | func (r *Route) addMatcher(m matcher) *Route {
167 | if r.err == nil {
168 | r.matchers = append(r.matchers, m)
169 | }
170 | return r
171 | }
172 |
173 | // addRegexpMatcher adds a host or path matcher and builder to a route.
174 | func (r *Route) addRegexpMatcher(tpl string, typ regexpType) error {
175 | if r.err != nil {
176 | return r.err
177 | }
178 | if typ == regexpTypePath || typ == regexpTypePrefix {
179 | if len(tpl) > 0 && tpl[0] != '/' {
180 | return fmt.Errorf("mux: path must start with a slash, got %q", tpl)
181 | }
182 | if r.regexp.path != nil {
183 | tpl = strings.TrimRight(r.regexp.path.template, "/") + tpl
184 | }
185 | }
186 | rr, err := newRouteRegexp(tpl, typ, routeRegexpOptions{
187 | strictSlash: r.strictSlash,
188 | useEncodedPath: r.useEncodedPath,
189 | })
190 | if err != nil {
191 | return err
192 | }
193 | for _, q := range r.regexp.queries {
194 | if err = uniqueVars(rr.varsN, q.varsN); err != nil {
195 | return err
196 | }
197 | }
198 | if typ == regexpTypeHost {
199 | if r.regexp.path != nil {
200 | if err = uniqueVars(rr.varsN, r.regexp.path.varsN); err != nil {
201 | return err
202 | }
203 | }
204 | r.regexp.host = rr
205 | } else {
206 | if r.regexp.host != nil {
207 | if err = uniqueVars(rr.varsN, r.regexp.host.varsN); err != nil {
208 | return err
209 | }
210 | }
211 | if typ == regexpTypeQuery {
212 | r.regexp.queries = append(r.regexp.queries, rr)
213 | } else {
214 | r.regexp.path = rr
215 | }
216 | }
217 | r.addMatcher(rr)
218 | return nil
219 | }
220 |
221 | // Headers --------------------------------------------------------------------
222 |
223 | // headerMatcher matches the request against header values.
224 | type headerMatcher map[string]string
225 |
226 | func (m headerMatcher) Match(r *http.Request, match *RouteMatch) bool {
227 | return matchMapWithString(m, r.Header, true)
228 | }
229 |
230 | // Headers adds a matcher for request header values.
231 | // It accepts a sequence of key/value pairs to be matched. For example:
232 | //
233 | // r := mux.NewRouter()
234 | // r.Headers("Content-Type", "application/json",
235 | // "X-Requested-With", "XMLHttpRequest")
236 | //
237 | // The above route will only match if both request header values match.
238 | // If the value is an empty string, it will match any value if the key is set.
239 | func (r *Route) Headers(pairs ...string) *Route {
240 | if r.err == nil {
241 | var headers map[string]string
242 | headers, r.err = mapFromPairsToString(pairs...)
243 | return r.addMatcher(headerMatcher(headers))
244 | }
245 | return r
246 | }
247 |
248 | // headerRegexMatcher matches the request against the route given a regex for the header
249 | type headerRegexMatcher map[string]*regexp.Regexp
250 |
251 | func (m headerRegexMatcher) Match(r *http.Request, match *RouteMatch) bool {
252 | return matchMapWithRegex(m, r.Header, true)
253 | }
254 |
255 | // HeadersRegexp accepts a sequence of key/value pairs, where the value has regex
256 | // support. For example:
257 | //
258 | // r := mux.NewRouter()
259 | // r.HeadersRegexp("Content-Type", "application/(text|json)",
260 | // "X-Requested-With", "XMLHttpRequest")
261 | //
262 | // The above route will only match if both the request header matches both regular expressions.
263 | // If the value is an empty string, it will match any value if the key is set.
264 | // Use the start and end of string anchors (^ and $) to match an exact value.
265 | func (r *Route) HeadersRegexp(pairs ...string) *Route {
266 | if r.err == nil {
267 | var headers map[string]*regexp.Regexp
268 | headers, r.err = mapFromPairsToRegex(pairs...)
269 | return r.addMatcher(headerRegexMatcher(headers))
270 | }
271 | return r
272 | }
273 |
274 | // Host -----------------------------------------------------------------------
275 |
276 | // Host adds a matcher for the URL host.
277 | // It accepts a template with zero or more URL variables enclosed by {}.
278 | // Variables can define an optional regexp pattern to be matched:
279 | //
280 | // - {name} matches anything until the next dot.
281 | //
282 | // - {name:pattern} matches the given regexp pattern.
283 | //
284 | // For example:
285 | //
286 | // r := mux.NewRouter()
287 | // r.Host("www.example.com")
288 | // r.Host("{subdomain}.domain.com")
289 | // r.Host("{subdomain:[a-z]+}.domain.com")
290 | //
291 | // Variable names must be unique in a given route. They can be retrieved
292 | // calling mux.Vars(request).
293 | func (r *Route) Host(tpl string) *Route {
294 | r.err = r.addRegexpMatcher(tpl, regexpTypeHost)
295 | return r
296 | }
297 |
298 | // MatcherFunc ----------------------------------------------------------------
299 |
300 | // MatcherFunc is the function signature used by custom matchers.
301 | type MatcherFunc func(*http.Request, *RouteMatch) bool
302 |
303 | // Match returns the match for a given request.
304 | func (m MatcherFunc) Match(r *http.Request, match *RouteMatch) bool {
305 | return m(r, match)
306 | }
307 |
308 | // MatcherFunc adds a custom function to be used as request matcher.
309 | func (r *Route) MatcherFunc(f MatcherFunc) *Route {
310 | return r.addMatcher(f)
311 | }
312 |
313 | // Methods --------------------------------------------------------------------
314 |
315 | // methodMatcher matches the request against HTTP methods.
316 | type methodMatcher []string
317 |
318 | func (m methodMatcher) Match(r *http.Request, match *RouteMatch) bool {
319 | return matchInArray(m, r.Method)
320 | }
321 |
322 | // Methods adds a matcher for HTTP methods.
323 | // It accepts a sequence of one or more methods to be matched, e.g.:
324 | // "GET", "POST", "PUT".
325 | func (r *Route) Methods(methods ...string) *Route {
326 | for k, v := range methods {
327 | methods[k] = strings.ToUpper(v)
328 | }
329 | return r.addMatcher(methodMatcher(methods))
330 | }
331 |
332 | // Path -----------------------------------------------------------------------
333 |
334 | // Path adds a matcher for the URL path.
335 | // It accepts a template with zero or more URL variables enclosed by {}. The
336 | // template must start with a "/".
337 | // Variables can define an optional regexp pattern to be matched:
338 | //
339 | // - {name} matches anything until the next slash.
340 | //
341 | // - {name:pattern} matches the given regexp pattern.
342 | //
343 | // For example:
344 | //
345 | // r := mux.NewRouter()
346 | // r.Path("/products/").Handler(ProductsHandler)
347 | // r.Path("/products/{key}").Handler(ProductsHandler)
348 | // r.Path("/articles/{category}/{id:[0-9]+}").
349 | // Handler(ArticleHandler)
350 | //
351 | // Variable names must be unique in a given route. They can be retrieved
352 | // calling mux.Vars(request).
353 | func (r *Route) Path(tpl string) *Route {
354 | r.err = r.addRegexpMatcher(tpl, regexpTypePath)
355 | return r
356 | }
357 |
358 | // PathPrefix -----------------------------------------------------------------
359 |
360 | // PathPrefix adds a matcher for the URL path prefix. This matches if the given
361 | // template is a prefix of the full URL path. See Route.Path() for details on
362 | // the tpl argument.
363 | //
364 | // Note that it does not treat slashes specially ("/foobar/" will be matched by
365 | // the prefix "/foo") so you may want to use a trailing slash here.
366 | //
367 | // Also note that the setting of Router.StrictSlash() has no effect on routes
368 | // with a PathPrefix matcher.
369 | func (r *Route) PathPrefix(tpl string) *Route {
370 | r.err = r.addRegexpMatcher(tpl, regexpTypePrefix)
371 | return r
372 | }
373 |
374 | // Query ----------------------------------------------------------------------
375 |
376 | // Queries adds a matcher for URL query values.
377 | // It accepts a sequence of key/value pairs. Values may define variables.
378 | // For example:
379 | //
380 | // r := mux.NewRouter()
381 | // r.Queries("foo", "bar", "id", "{id:[0-9]+}")
382 | //
383 | // The above route will only match if the URL contains the defined queries
384 | // values, e.g.: ?foo=bar&id=42.
385 | //
386 | // If the value is an empty string, it will match any value if the key is set.
387 | //
388 | // Variables can define an optional regexp pattern to be matched:
389 | //
390 | // - {name} matches anything until the next slash.
391 | //
392 | // - {name:pattern} matches the given regexp pattern.
393 | func (r *Route) Queries(pairs ...string) *Route {
394 | length := len(pairs)
395 | if length%2 != 0 {
396 | r.err = fmt.Errorf(
397 | "mux: number of parameters must be multiple of 2, got %v", pairs)
398 | return nil
399 | }
400 | for i := 0; i < length; i += 2 {
401 | if r.err = r.addRegexpMatcher(pairs[i]+"="+pairs[i+1], regexpTypeQuery); r.err != nil {
402 | return r
403 | }
404 | }
405 |
406 | return r
407 | }
408 |
409 | // Schemes --------------------------------------------------------------------
410 |
411 | // schemeMatcher matches the request against URL schemes.
412 | type schemeMatcher []string
413 |
414 | func (m schemeMatcher) Match(r *http.Request, match *RouteMatch) bool {
415 | return matchInArray(m, r.URL.Scheme)
416 | }
417 |
418 | // Schemes adds a matcher for URL schemes.
419 | // It accepts a sequence of schemes to be matched, e.g.: "http", "https".
420 | func (r *Route) Schemes(schemes ...string) *Route {
421 | for k, v := range schemes {
422 | schemes[k] = strings.ToLower(v)
423 | }
424 | if len(schemes) > 0 {
425 | r.buildScheme = schemes[0]
426 | }
427 | return r.addMatcher(schemeMatcher(schemes))
428 | }
429 |
430 | // BuildVarsFunc --------------------------------------------------------------
431 |
432 | // BuildVarsFunc is the function signature used by custom build variable
433 | // functions (which can modify route variables before a route's URL is built).
434 | type BuildVarsFunc func(map[string]string) map[string]string
435 |
436 | // BuildVarsFunc adds a custom function to be used to modify build variables
437 | // before a route's URL is built.
438 | func (r *Route) BuildVarsFunc(f BuildVarsFunc) *Route {
439 | if r.buildVarsFunc != nil {
440 | // compose the old and new functions
441 | old := r.buildVarsFunc
442 | r.buildVarsFunc = func(m map[string]string) map[string]string {
443 | return f(old(m))
444 | }
445 | } else {
446 | r.buildVarsFunc = f
447 | }
448 | return r
449 | }
450 |
451 | // Subrouter ------------------------------------------------------------------
452 |
453 | // Subrouter creates a subrouter for the route.
454 | //
455 | // It will test the inner routes only if the parent route matched. For example:
456 | //
457 | // r := mux.NewRouter()
458 | // s := r.Host("www.example.com").Subrouter()
459 | // s.HandleFunc("/products/", ProductsHandler)
460 | // s.HandleFunc("/products/{key}", ProductHandler)
461 | // s.HandleFunc("/articles/{category}/{id:[0-9]+}"), ArticleHandler)
462 | //
463 | // Here, the routes registered in the subrouter won't be tested if the host
464 | // doesn't match.
465 | func (r *Route) Subrouter() *Router {
466 | // initialize a subrouter with a copy of the parent route's configuration
467 | router := &Router{routeConf: copyRouteConf(r.routeConf), namedRoutes: r.namedRoutes}
468 | r.addMatcher(router)
469 | return router
470 | }
471 |
472 | // ----------------------------------------------------------------------------
473 | // URL building
474 | // ----------------------------------------------------------------------------
475 |
476 | // URL builds a URL for the route.
477 | //
478 | // It accepts a sequence of key/value pairs for the route variables. For
479 | // example, given this route:
480 | //
481 | // r := mux.NewRouter()
482 | // r.HandleFunc("/articles/{category}/{id:[0-9]+}", ArticleHandler).
483 | // Name("article")
484 | //
485 | // ...a URL for it can be built using:
486 | //
487 | // url, err := r.Get("article").URL("category", "technology", "id", "42")
488 | //
489 | // ...which will return an url.URL with the following path:
490 | //
491 | // "/articles/technology/42"
492 | //
493 | // This also works for host variables:
494 | //
495 | // r := mux.NewRouter()
496 | // r.Host("{subdomain}.domain.com").
497 | // HandleFunc("/articles/{category}/{id:[0-9]+}", ArticleHandler).
498 | // Name("article")
499 | //
500 | // // url.String() will be "http://news.domain.com/articles/technology/42"
501 | // url, err := r.Get("article").URL("subdomain", "news",
502 | // "category", "technology",
503 | // "id", "42")
504 | //
505 | // All variables defined in the route are required, and their values must
506 | // conform to the corresponding patterns.
507 | func (r *Route) URL(pairs ...string) (*url.URL, error) {
508 | if r.err != nil {
509 | return nil, r.err
510 | }
511 | values, err := r.prepareVars(pairs...)
512 | if err != nil {
513 | return nil, err
514 | }
515 | var scheme, host, path string
516 | queries := make([]string, 0, len(r.regexp.queries))
517 | if r.regexp.host != nil {
518 | if host, err = r.regexp.host.url(values); err != nil {
519 | return nil, err
520 | }
521 | scheme = "http"
522 | if r.buildScheme != "" {
523 | scheme = r.buildScheme
524 | }
525 | }
526 | if r.regexp.path != nil {
527 | if path, err = r.regexp.path.url(values); err != nil {
528 | return nil, err
529 | }
530 | }
531 | for _, q := range r.regexp.queries {
532 | var query string
533 | if query, err = q.url(values); err != nil {
534 | return nil, err
535 | }
536 | queries = append(queries, query)
537 | }
538 | return &url.URL{
539 | Scheme: scheme,
540 | Host: host,
541 | Path: path,
542 | RawQuery: strings.Join(queries, "&"),
543 | }, nil
544 | }
545 |
546 | // URLHost builds the host part of the URL for a route. See Route.URL().
547 | //
548 | // The route must have a host defined.
549 | func (r *Route) URLHost(pairs ...string) (*url.URL, error) {
550 | if r.err != nil {
551 | return nil, r.err
552 | }
553 | if r.regexp.host == nil {
554 | return nil, errors.New("mux: route doesn't have a host")
555 | }
556 | values, err := r.prepareVars(pairs...)
557 | if err != nil {
558 | return nil, err
559 | }
560 | host, err := r.regexp.host.url(values)
561 | if err != nil {
562 | return nil, err
563 | }
564 | u := &url.URL{
565 | Scheme: "http",
566 | Host: host,
567 | }
568 | if r.buildScheme != "" {
569 | u.Scheme = r.buildScheme
570 | }
571 | return u, nil
572 | }
573 |
574 | // URLPath builds the path part of the URL for a route. See Route.URL().
575 | //
576 | // The route must have a path defined.
577 | func (r *Route) URLPath(pairs ...string) (*url.URL, error) {
578 | if r.err != nil {
579 | return nil, r.err
580 | }
581 | if r.regexp.path == nil {
582 | return nil, errors.New("mux: route doesn't have a path")
583 | }
584 | values, err := r.prepareVars(pairs...)
585 | if err != nil {
586 | return nil, err
587 | }
588 | path, err := r.regexp.path.url(values)
589 | if err != nil {
590 | return nil, err
591 | }
592 | return &url.URL{
593 | Path: path,
594 | }, nil
595 | }
596 |
597 | // GetPathTemplate returns the template used to build the
598 | // route match.
599 | // This is useful for building simple REST API documentation and for instrumentation
600 | // against third-party services.
601 | // An error will be returned if the route does not define a path.
602 | func (r *Route) GetPathTemplate() (string, error) {
603 | if r.err != nil {
604 | return "", r.err
605 | }
606 | if r.regexp.path == nil {
607 | return "", errors.New("mux: route doesn't have a path")
608 | }
609 | return r.regexp.path.template, nil
610 | }
611 |
612 | // GetPathRegexp returns the expanded regular expression used to match route path.
613 | // This is useful for building simple REST API documentation and for instrumentation
614 | // against third-party services.
615 | // An error will be returned if the route does not define a path.
616 | func (r *Route) GetPathRegexp() (string, error) {
617 | if r.err != nil {
618 | return "", r.err
619 | }
620 | if r.regexp.path == nil {
621 | return "", errors.New("mux: route does not have a path")
622 | }
623 | return r.regexp.path.regexp.String(), nil
624 | }
625 |
626 | // GetQueriesRegexp returns the expanded regular expressions used to match the
627 | // route queries.
628 | // This is useful for building simple REST API documentation and for instrumentation
629 | // against third-party services.
630 | // An error will be returned if the route does not have queries.
631 | func (r *Route) GetQueriesRegexp() ([]string, error) {
632 | if r.err != nil {
633 | return nil, r.err
634 | }
635 | if r.regexp.queries == nil {
636 | return nil, errors.New("mux: route doesn't have queries")
637 | }
638 | var queries []string
639 | for _, query := range r.regexp.queries {
640 | queries = append(queries, query.regexp.String())
641 | }
642 | return queries, nil
643 | }
644 |
645 | // GetQueriesTemplates returns the templates used to build the
646 | // query matching.
647 | // This is useful for building simple REST API documentation and for instrumentation
648 | // against third-party services.
649 | // An error will be returned if the route does not define queries.
650 | func (r *Route) GetQueriesTemplates() ([]string, error) {
651 | if r.err != nil {
652 | return nil, r.err
653 | }
654 | if r.regexp.queries == nil {
655 | return nil, errors.New("mux: route doesn't have queries")
656 | }
657 | var queries []string
658 | for _, query := range r.regexp.queries {
659 | queries = append(queries, query.template)
660 | }
661 | return queries, nil
662 | }
663 |
664 | // GetMethods returns the methods the route matches against
665 | // This is useful for building simple REST API documentation and for instrumentation
666 | // against third-party services.
667 | // An error will be returned if route does not have methods.
668 | func (r *Route) GetMethods() ([]string, error) {
669 | if r.err != nil {
670 | return nil, r.err
671 | }
672 | for _, m := range r.matchers {
673 | if methods, ok := m.(methodMatcher); ok {
674 | return []string(methods), nil
675 | }
676 | }
677 | return nil, errors.New("mux: route doesn't have methods")
678 | }
679 |
680 | // GetHostTemplate returns the template used to build the
681 | // route match.
682 | // This is useful for building simple REST API documentation and for instrumentation
683 | // against third-party services.
684 | // An error will be returned if the route does not define a host.
685 | func (r *Route) GetHostTemplate() (string, error) {
686 | if r.err != nil {
687 | return "", r.err
688 | }
689 | if r.regexp.host == nil {
690 | return "", errors.New("mux: route doesn't have a host")
691 | }
692 | return r.regexp.host.template, nil
693 | }
694 |
695 | // prepareVars converts the route variable pairs into a map. If the route has a
696 | // BuildVarsFunc, it is invoked.
697 | func (r *Route) prepareVars(pairs ...string) (map[string]string, error) {
698 | m, err := mapFromPairsToString(pairs...)
699 | if err != nil {
700 | return nil, err
701 | }
702 | return r.buildVars(m), nil
703 | }
704 |
705 | func (r *Route) buildVars(m map[string]string) map[string]string {
706 | if r.buildVarsFunc != nil {
707 | m = r.buildVarsFunc(m)
708 | }
709 | return m
710 | }
711 |
--------------------------------------------------------------------------------
/vendor/github.com/gorilla/mux/test_helpers.go:
--------------------------------------------------------------------------------
1 | // Copyright 2012 The Gorilla Authors. All rights reserved.
2 | // Use of this source code is governed by a BSD-style
3 | // license that can be found in the LICENSE file.
4 |
5 | package mux
6 |
7 | import "net/http"
8 |
9 | // SetURLVars sets the URL variables for the given request, to be accessed via
10 | // mux.Vars for testing route behaviour. Arguments are not modified, a shallow
11 | // copy is returned.
12 | //
13 | // This API should only be used for testing purposes; it provides a way to
14 | // inject variables into the request context. Alternatively, URL variables
15 | // can be set by making a route that captures the required variables,
16 | // starting a server and sending the request to that server.
17 | func SetURLVars(r *http.Request, val map[string]string) *http.Request {
18 | return setVars(r, val)
19 | }
20 |
--------------------------------------------------------------------------------