// Copyright 2013 The Gorilla Authors. All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. package handlers import ( "bufio" "fmt" "net" "net/http" "sort" "strings" ) // MethodHandler is an http.Handler that dispatches to a handler whose key in the // MethodHandler's map matches the name of the HTTP request's method, eg: GET // // If the request's method is OPTIONS and OPTIONS is not a key in the map then // the handler responds with a status of 200 and sets the Allow header to a // comma-separated list of available methods. // // If the request's method doesn't match any of its keys the handler responds // with a status of HTTP 405 "Method Not Allowed" and sets the Allow header to a // comma-separated list of available methods. type MethodHandler map[string]http.Handler func (h MethodHandler) ServeHTTP(w http.ResponseWriter, req *http.Request) { if handler, ok := h[req.Method]; ok { handler.ServeHTTP(w, req) } else { allow := []string{} for k := range h { allow = append(allow, k) } sort.Strings(allow) w.Header().Set("Allow", strings.Join(allow, ", ")) if req.Method == "OPTIONS" { w.WriteHeader(http.StatusOK) } else { http.Error(w, "Method not allowed", http.StatusMethodNotAllowed) } } } // responseLogger is wrapper of http.ResponseWriter that keeps track of its HTTP // status code and body size type responseLogger struct { w http.ResponseWriter status int size int } func (l *responseLogger) Header() http.Header { return l.w.Header() } func (l *responseLogger) Write(b []byte) (int, error) { size, err := l.w.Write(b) l.size += size return size, err } func (l *responseLogger) WriteHeader(s int) { l.w.WriteHeader(s) l.status = s } func (l *responseLogger) Status() int { return l.status } func (l *responseLogger) Size() int { return l.size } func (l *responseLogger) Flush() { f, ok := l.w.(http.Flusher) if ok { f.Flush() } } type hijackLogger struct { responseLogger } func (l *hijackLogger) Hijack() (net.Conn, *bufio.ReadWriter, error) { h := l.responseLogger.w.(http.Hijacker) conn, rw, err := h.Hijack() if err == nil && l.responseLogger.status == 0 { // The status will be StatusSwitchingProtocols if there was no error and // WriteHeader has not been called yet l.responseLogger.status = http.StatusSwitchingProtocols } return conn, rw, err } type closeNotifyWriter struct { loggingResponseWriter http.CloseNotifier } type hijackCloseNotifier struct { loggingResponseWriter http.Hijacker http.CloseNotifier } // isContentType validates the Content-Type header matches the supplied // contentType. That is, its type and subtype match. func isContentType(h http.Header, contentType string) bool { ct := h.Get("Content-Type") if i := strings.IndexRune(ct, ';'); i != -1 { ct = ct[0:i] } return ct == contentType } // ContentTypeHandler wraps and returns a http.Handler, validating the request // content type is compatible with the contentTypes list. It writes a HTTP 415 // error if that fails. // // Only PUT, POST, and PATCH requests are considered. func ContentTypeHandler(h http.Handler, contentTypes ...string) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { if !(r.Method == "PUT" || r.Method == "POST" || r.Method == "PATCH") { h.ServeHTTP(w, r) return } for _, ct := range contentTypes { if isContentType(r.Header, ct) { h.ServeHTTP(w, r) return } } http.Error(w, fmt.Sprintf("Unsupported content type %q; expected one of %q", r.Header.Get("Content-Type"), contentTypes), http.StatusUnsupportedMediaType) }) } const ( // HTTPMethodOverrideHeader is a commonly used // http header to override a request method. HTTPMethodOverrideHeader = "X-HTTP-Method-Override" // HTTPMethodOverrideFormKey is a commonly used // HTML form key to override a request method. HTTPMethodOverrideFormKey = "_method" ) // HTTPMethodOverrideHandler wraps and returns a http.Handler which checks for // the X-HTTP-Method-Override header or the _method form key, and overrides (if // valid) request.Method with its value. // // This is especially useful for HTTP clients that don't support many http verbs. // It isn't secure to override e.g a GET to a POST, so only POST requests are // considered. Likewise, the override method can only be a "write" method: PUT, // PATCH or DELETE. // // Form method takes precedence over header method. func HTTPMethodOverrideHandler(h http.Handler) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { if r.Method == "POST" { om := r.FormValue(HTTPMethodOverrideFormKey) if om == "" { om = r.Header.Get(HTTPMethodOverrideHeader) } if om == "PUT" || om == "PATCH" || om == "DELETE" { r.Method = om } } h.ServeHTTP(w, r) }) }