123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399 |
- // 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"
- "io"
- "net"
- "net/http"
- "net/url"
- "sort"
- "strconv"
- "strings"
- "time"
- "unicode/utf8"
- )
- // MethodHandler is an http.Handler that dispatches to a handler whose key in the
- // MethodHandler's map matches the name of the HTTP request's method, eg: GET
- //
- // 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)
- }
- }
- }
- // loggingHandler is the http.Handler implementation for LoggingHandlerTo and its
- // friends
- type loggingHandler struct {
- writer io.Writer
- handler http.Handler
- }
- // combinedLoggingHandler is the http.Handler implementation for LoggingHandlerTo
- // and its friends
- type combinedLoggingHandler struct {
- writer io.Writer
- handler http.Handler
- }
- func (h loggingHandler) ServeHTTP(w http.ResponseWriter, req *http.Request) {
- t := time.Now()
- logger := makeLogger(w)
- url := *req.URL
- h.handler.ServeHTTP(logger, req)
- writeLog(h.writer, req, url, t, logger.Status(), logger.Size())
- }
- func (h combinedLoggingHandler) ServeHTTP(w http.ResponseWriter, req *http.Request) {
- t := time.Now()
- logger := makeLogger(w)
- url := *req.URL
- h.handler.ServeHTTP(logger, req)
- writeCombinedLog(h.writer, req, url, t, logger.Status(), logger.Size())
- }
- func makeLogger(w http.ResponseWriter) loggingResponseWriter {
- var logger loggingResponseWriter = &responseLogger{w: w, status: http.StatusOK}
- if _, ok := w.(http.Hijacker); ok {
- logger = &hijackLogger{responseLogger{w: w, status: http.StatusOK}}
- }
- h, ok1 := logger.(http.Hijacker)
- c, ok2 := w.(http.CloseNotifier)
- if ok1 && ok2 {
- return hijackCloseNotifier{logger, h, c}
- }
- if ok2 {
- return &closeNotifyWriter{logger, c}
- }
- return logger
- }
- type commonLoggingResponseWriter interface {
- http.ResponseWriter
- http.Flusher
- Status() int
- Size() int
- }
- // responseLogger is wrapper of http.ResponseWriter that keeps track of its HTTP
- // status code and body size
- 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
- }
- const lowerhex = "0123456789abcdef"
- func appendQuoted(buf []byte, s string) []byte {
- var runeTmp [utf8.UTFMax]byte
- for width := 0; len(s) > 0; s = s[width:] {
- r := rune(s[0])
- width = 1
- if r >= utf8.RuneSelf {
- r, width = utf8.DecodeRuneInString(s)
- }
- if width == 1 && r == utf8.RuneError {
- buf = append(buf, `\x`...)
- buf = append(buf, lowerhex[s[0]>>4])
- buf = append(buf, lowerhex[s[0]&0xF])
- continue
- }
- if r == rune('"') || r == '\\' { // always backslashed
- buf = append(buf, '\\')
- buf = append(buf, byte(r))
- continue
- }
- if strconv.IsPrint(r) {
- n := utf8.EncodeRune(runeTmp[:], r)
- buf = append(buf, runeTmp[:n]...)
- continue
- }
- switch r {
- case '\a':
- buf = append(buf, `\a`...)
- case '\b':
- buf = append(buf, `\b`...)
- case '\f':
- buf = append(buf, `\f`...)
- case '\n':
- buf = append(buf, `\n`...)
- case '\r':
- buf = append(buf, `\r`...)
- case '\t':
- buf = append(buf, `\t`...)
- case '\v':
- buf = append(buf, `\v`...)
- default:
- switch {
- case r < ' ':
- buf = append(buf, `\x`...)
- buf = append(buf, lowerhex[s[0]>>4])
- buf = append(buf, lowerhex[s[0]&0xF])
- case r > utf8.MaxRune:
- r = 0xFFFD
- fallthrough
- case r < 0x10000:
- buf = append(buf, `\u`...)
- for s := 12; s >= 0; s -= 4 {
- buf = append(buf, lowerhex[r>>uint(s)&0xF])
- }
- default:
- buf = append(buf, `\U`...)
- for s := 28; s >= 0; s -= 4 {
- buf = append(buf, lowerhex[r>>uint(s)&0xF])
- }
- }
- }
- }
- return buf
- }
- // buildCommonLogLine builds a log entry for req in Apache Common Log Format.
- // ts is the timestamp with which the entry should be logged.
- // status and size are used to provide the response HTTP status and size.
- func buildCommonLogLine(req *http.Request, url url.URL, ts time.Time, status int, size int) []byte {
- username := "-"
- if url.User != nil {
- if name := url.User.Username(); name != "" {
- username = name
- }
- }
- host, _, err := net.SplitHostPort(req.RemoteAddr)
- if err != nil {
- host = req.RemoteAddr
- }
- uri := req.RequestURI
- // Requests using the CONNECT method over HTTP/2.0 must use
- // the authority field (aka r.Host) to identify the target.
- // Refer: https://httpwg.github.io/specs/rfc7540.html#CONNECT
- if req.ProtoMajor == 2 && req.Method == "CONNECT" {
- uri = req.Host
- }
- if uri == "" {
- uri = url.RequestURI()
- }
- buf := make([]byte, 0, 3*(len(host)+len(username)+len(req.Method)+len(uri)+len(req.Proto)+50)/2)
- buf = append(buf, host...)
- buf = append(buf, " - "...)
- buf = append(buf, username...)
- buf = append(buf, " ["...)
- buf = append(buf, ts.Format("02/Jan/2006:15:04:05 -0700")...)
- buf = append(buf, `] "`...)
- buf = append(buf, req.Method...)
- buf = append(buf, " "...)
- buf = appendQuoted(buf, uri)
- buf = append(buf, " "...)
- buf = append(buf, req.Proto...)
- buf = append(buf, `" `...)
- buf = append(buf, strconv.Itoa(status)...)
- buf = append(buf, " "...)
- buf = append(buf, strconv.Itoa(size)...)
- return buf
- }
- // writeLog writes a log entry for req to w in Apache Common Log Format.
- // ts is the timestamp with which the entry should be logged.
- // status and size are used to provide the response HTTP status and size.
- func writeLog(w io.Writer, req *http.Request, url url.URL, ts time.Time, status, size int) {
- buf := buildCommonLogLine(req, url, ts, status, size)
- buf = append(buf, '\n')
- w.Write(buf)
- }
- // writeCombinedLog writes a log entry for req to w in Apache Combined Log Format.
- // ts is the timestamp with which the entry should be logged.
- // status and size are used to provide the response HTTP status and size.
- func writeCombinedLog(w io.Writer, req *http.Request, url url.URL, ts time.Time, status, size int) {
- buf := buildCommonLogLine(req, url, ts, status, size)
- buf = append(buf, ` "`...)
- buf = appendQuoted(buf, req.Referer())
- buf = append(buf, `" "`...)
- buf = appendQuoted(buf, req.UserAgent())
- buf = append(buf, '"', '\n')
- w.Write(buf)
- }
- // CombinedLoggingHandler return a http.Handler that wraps h and logs requests to out in
- // Apache Combined Log Format.
- //
- // See http://httpd.apache.org/docs/2.2/logs.html#combined for a description of this format.
- //
- // LoggingHandler always sets the ident field of the log to -
- func CombinedLoggingHandler(out io.Writer, h http.Handler) http.Handler {
- return combinedLoggingHandler{out, h}
- }
- // LoggingHandler return a http.Handler that wraps h and logs requests to out in
- // Apache Common Log Format (CLF).
- //
- // See http://httpd.apache.org/docs/2.2/logs.html#common for a description of this format.
- //
- // LoggingHandler always sets the ident field of the log to -
- //
- // Example:
- //
- // r := mux.NewRouter()
- // r.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
- // w.Write([]byte("This is a catch-all route"))
- // })
- // loggedRouter := handlers.LoggingHandler(os.Stdout, r)
- // http.ListenAndServe(":1123", loggedRouter)
- //
- func LoggingHandler(out io.Writer, h http.Handler) http.Handler {
- return loggingHandler{out, h}
- }
- // 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)
- })
- }
|