Zdravko Bozakov | 958d81c | 2019-12-13 22:09:48 +0100 | [diff] [blame] | 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) } |