...

Source file src/github.com/gorilla/handlers/handlers.go

Documentation: github.com/gorilla/handlers

     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) Write(b []byte) (int, error) {
    55  	size, err := l.w.Write(b)
    56  	l.size += size
    57  	return size, err
    58  }
    59  
    60  func (l *responseLogger) WriteHeader(s int) {
    61  	l.w.WriteHeader(s)
    62  	l.status = s
    63  }
    64  
    65  func (l *responseLogger) Status() int {
    66  	return l.status
    67  }
    68  
    69  func (l *responseLogger) Size() int {
    70  	return l.size
    71  }
    72  
    73  func (l *responseLogger) Hijack() (net.Conn, *bufio.ReadWriter, error) {
    74  	conn, rw, err := l.w.(http.Hijacker).Hijack()
    75  	if err == nil && l.status == 0 {
    76  		// The status will be StatusSwitchingProtocols if there was no error and
    77  		// WriteHeader has not been called yet
    78  		l.status = http.StatusSwitchingProtocols
    79  	}
    80  	return conn, rw, err
    81  }
    82  
    83  // isContentType validates the Content-Type header matches the supplied
    84  // contentType. That is, its type and subtype match.
    85  func isContentType(h http.Header, contentType string) bool {
    86  	ct := h.Get("Content-Type")
    87  	if i := strings.IndexRune(ct, ';'); i != -1 {
    88  		ct = ct[0:i]
    89  	}
    90  	return ct == contentType
    91  }
    92  
    93  // ContentTypeHandler wraps and returns a http.Handler, validating the request
    94  // content type is compatible with the contentTypes list. It writes a HTTP 415
    95  // error if that fails.
    96  //
    97  // Only PUT, POST, and PATCH requests are considered.
    98  func ContentTypeHandler(h http.Handler, contentTypes ...string) http.Handler {
    99  	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
   100  		if !(r.Method == "PUT" || r.Method == "POST" || r.Method == "PATCH") {
   101  			h.ServeHTTP(w, r)
   102  			return
   103  		}
   104  
   105  		for _, ct := range contentTypes {
   106  			if isContentType(r.Header, ct) {
   107  				h.ServeHTTP(w, r)
   108  				return
   109  			}
   110  		}
   111  		http.Error(w, fmt.Sprintf("Unsupported content type %q; expected one of %q", r.Header.Get("Content-Type"), contentTypes), http.StatusUnsupportedMediaType)
   112  	})
   113  }
   114  
   115  const (
   116  	// HTTPMethodOverrideHeader is a commonly used
   117  	// http header to override a request method.
   118  	HTTPMethodOverrideHeader = "X-HTTP-Method-Override"
   119  	// HTTPMethodOverrideFormKey is a commonly used
   120  	// HTML form key to override a request method.
   121  	HTTPMethodOverrideFormKey = "_method"
   122  )
   123  
   124  // HTTPMethodOverrideHandler wraps and returns a http.Handler which checks for
   125  // the X-HTTP-Method-Override header or the _method form key, and overrides (if
   126  // valid) request.Method with its value.
   127  //
   128  // This is especially useful for HTTP clients that don't support many http verbs.
   129  // It isn't secure to override e.g a GET to a POST, so only POST requests are
   130  // considered.  Likewise, the override method can only be a "write" method: PUT,
   131  // PATCH or DELETE.
   132  //
   133  // Form method takes precedence over header method.
   134  func HTTPMethodOverrideHandler(h http.Handler) http.Handler {
   135  	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
   136  		if r.Method == "POST" {
   137  			om := r.FormValue(HTTPMethodOverrideFormKey)
   138  			if om == "" {
   139  				om = r.Header.Get(HTTPMethodOverrideHeader)
   140  			}
   141  			if om == "PUT" || om == "PATCH" || om == "DELETE" {
   142  				r.Method = om
   143  			}
   144  		}
   145  		h.ServeHTTP(w, r)
   146  	})
   147  }
   148  

View as plain text