本站源代码
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

256 lines
7.0KB

  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. package handlers
  5. import (
  6. "io"
  7. "net"
  8. "net/http"
  9. "net/url"
  10. "strconv"
  11. "time"
  12. "unicode/utf8"
  13. )
  14. // Logging
  15. // LogFormatterParams is the structure any formatter will be handed when time to log comes
  16. type LogFormatterParams struct {
  17. Request *http.Request
  18. URL url.URL
  19. TimeStamp time.Time
  20. StatusCode int
  21. Size int
  22. }
  23. // LogFormatter gives the signature of the formatter function passed to CustomLoggingHandler
  24. type LogFormatter func(writer io.Writer, params LogFormatterParams)
  25. // loggingHandler is the http.Handler implementation for LoggingHandlerTo and its
  26. // friends
  27. type loggingHandler struct {
  28. writer io.Writer
  29. handler http.Handler
  30. formatter LogFormatter
  31. }
  32. func (h loggingHandler) ServeHTTP(w http.ResponseWriter, req *http.Request) {
  33. t := time.Now()
  34. logger := makeLogger(w)
  35. url := *req.URL
  36. h.handler.ServeHTTP(logger, req)
  37. if req.MultipartForm != nil {
  38. req.MultipartForm.RemoveAll()
  39. }
  40. params := LogFormatterParams{
  41. Request: req,
  42. URL: url,
  43. TimeStamp: t,
  44. StatusCode: logger.Status(),
  45. Size: logger.Size(),
  46. }
  47. h.formatter(h.writer, params)
  48. }
  49. func makeLogger(w http.ResponseWriter) loggingResponseWriter {
  50. var logger loggingResponseWriter = &responseLogger{w: w, status: http.StatusOK}
  51. if _, ok := w.(http.Hijacker); ok {
  52. logger = &hijackLogger{responseLogger{w: w, status: http.StatusOK}}
  53. }
  54. h, ok1 := logger.(http.Hijacker)
  55. c, ok2 := w.(http.CloseNotifier)
  56. if ok1 && ok2 {
  57. return hijackCloseNotifier{logger, h, c}
  58. }
  59. if ok2 {
  60. return &closeNotifyWriter{logger, c}
  61. }
  62. return logger
  63. }
  64. type commonLoggingResponseWriter interface {
  65. http.ResponseWriter
  66. http.Flusher
  67. Status() int
  68. Size() int
  69. }
  70. const lowerhex = "0123456789abcdef"
  71. func appendQuoted(buf []byte, s string) []byte {
  72. var runeTmp [utf8.UTFMax]byte
  73. for width := 0; len(s) > 0; s = s[width:] {
  74. r := rune(s[0])
  75. width = 1
  76. if r >= utf8.RuneSelf {
  77. r, width = utf8.DecodeRuneInString(s)
  78. }
  79. if width == 1 && r == utf8.RuneError {
  80. buf = append(buf, `\x`...)
  81. buf = append(buf, lowerhex[s[0]>>4])
  82. buf = append(buf, lowerhex[s[0]&0xF])
  83. continue
  84. }
  85. if r == rune('"') || r == '\\' { // always backslashed
  86. buf = append(buf, '\\')
  87. buf = append(buf, byte(r))
  88. continue
  89. }
  90. if strconv.IsPrint(r) {
  91. n := utf8.EncodeRune(runeTmp[:], r)
  92. buf = append(buf, runeTmp[:n]...)
  93. continue
  94. }
  95. switch r {
  96. case '\a':
  97. buf = append(buf, `\a`...)
  98. case '\b':
  99. buf = append(buf, `\b`...)
  100. case '\f':
  101. buf = append(buf, `\f`...)
  102. case '\n':
  103. buf = append(buf, `\n`...)
  104. case '\r':
  105. buf = append(buf, `\r`...)
  106. case '\t':
  107. buf = append(buf, `\t`...)
  108. case '\v':
  109. buf = append(buf, `\v`...)
  110. default:
  111. switch {
  112. case r < ' ':
  113. buf = append(buf, `\x`...)
  114. buf = append(buf, lowerhex[s[0]>>4])
  115. buf = append(buf, lowerhex[s[0]&0xF])
  116. case r > utf8.MaxRune:
  117. r = 0xFFFD
  118. fallthrough
  119. case r < 0x10000:
  120. buf = append(buf, `\u`...)
  121. for s := 12; s >= 0; s -= 4 {
  122. buf = append(buf, lowerhex[r>>uint(s)&0xF])
  123. }
  124. default:
  125. buf = append(buf, `\U`...)
  126. for s := 28; s >= 0; s -= 4 {
  127. buf = append(buf, lowerhex[r>>uint(s)&0xF])
  128. }
  129. }
  130. }
  131. }
  132. return buf
  133. }
  134. // buildCommonLogLine builds a log entry for req in Apache Common Log Format.
  135. // ts is the timestamp with which the entry should be logged.
  136. // status and size are used to provide the response HTTP status and size.
  137. func buildCommonLogLine(req *http.Request, url url.URL, ts time.Time, status int, size int) []byte {
  138. username := "-"
  139. if url.User != nil {
  140. if name := url.User.Username(); name != "" {
  141. username = name
  142. }
  143. }
  144. host, _, err := net.SplitHostPort(req.RemoteAddr)
  145. if err != nil {
  146. host = req.RemoteAddr
  147. }
  148. uri := req.RequestURI
  149. // Requests using the CONNECT method over HTTP/2.0 must use
  150. // the authority field (aka r.Host) to identify the target.
  151. // Refer: https://httpwg.github.io/specs/rfc7540.html#CONNECT
  152. if req.ProtoMajor == 2 && req.Method == "CONNECT" {
  153. uri = req.Host
  154. }
  155. if uri == "" {
  156. uri = url.RequestURI()
  157. }
  158. buf := make([]byte, 0, 3*(len(host)+len(username)+len(req.Method)+len(uri)+len(req.Proto)+50)/2)
  159. buf = append(buf, host...)
  160. buf = append(buf, " - "...)
  161. buf = append(buf, username...)
  162. buf = append(buf, " ["...)
  163. buf = append(buf, ts.Format("02/Jan/2006:15:04:05 -0700")...)
  164. buf = append(buf, `] "`...)
  165. buf = append(buf, req.Method...)
  166. buf = append(buf, " "...)
  167. buf = appendQuoted(buf, uri)
  168. buf = append(buf, " "...)
  169. buf = append(buf, req.Proto...)
  170. buf = append(buf, `" `...)
  171. buf = append(buf, strconv.Itoa(status)...)
  172. buf = append(buf, " "...)
  173. buf = append(buf, strconv.Itoa(size)...)
  174. return buf
  175. }
  176. // writeLog writes a log entry for req to w in Apache Common Log Format.
  177. // ts is the timestamp with which the entry should be logged.
  178. // status and size are used to provide the response HTTP status and size.
  179. func writeLog(writer io.Writer, params LogFormatterParams) {
  180. buf := buildCommonLogLine(params.Request, params.URL, params.TimeStamp, params.StatusCode, params.Size)
  181. buf = append(buf, '\n')
  182. writer.Write(buf)
  183. }
  184. // writeCombinedLog writes a log entry for req to w in Apache Combined Log Format.
  185. // ts is the timestamp with which the entry should be logged.
  186. // status and size are used to provide the response HTTP status and size.
  187. func writeCombinedLog(writer io.Writer, params LogFormatterParams) {
  188. buf := buildCommonLogLine(params.Request, params.URL, params.TimeStamp, params.StatusCode, params.Size)
  189. buf = append(buf, ` "`...)
  190. buf = appendQuoted(buf, params.Request.Referer())
  191. buf = append(buf, `" "`...)
  192. buf = appendQuoted(buf, params.Request.UserAgent())
  193. buf = append(buf, '"', '\n')
  194. writer.Write(buf)
  195. }
  196. // CombinedLoggingHandler return a http.Handler that wraps h and logs requests to out in
  197. // Apache Combined Log Format.
  198. //
  199. // See http://httpd.apache.org/docs/2.2/logs.html#combined for a description of this format.
  200. //
  201. // LoggingHandler always sets the ident field of the log to -
  202. func CombinedLoggingHandler(out io.Writer, h http.Handler) http.Handler {
  203. return loggingHandler{out, h, writeCombinedLog}
  204. }
  205. // LoggingHandler return a http.Handler that wraps h and logs requests to out in
  206. // Apache Common Log Format (CLF).
  207. //
  208. // See http://httpd.apache.org/docs/2.2/logs.html#common for a description of this format.
  209. //
  210. // LoggingHandler always sets the ident field of the log to -
  211. //
  212. // Example:
  213. //
  214. // r := mux.NewRouter()
  215. // r.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
  216. // w.Write([]byte("This is a catch-all route"))
  217. // })
  218. // loggedRouter := handlers.LoggingHandler(os.Stdout, r)
  219. // http.ListenAndServe(":1123", loggedRouter)
  220. //
  221. func LoggingHandler(out io.Writer, h http.Handler) http.Handler {
  222. return loggingHandler{out, h, writeLog}
  223. }
  224. // CustomLoggingHandler provides a way to supply a custom log formatter
  225. // while taking advantage of the mechanisms in this package
  226. func CustomLoggingHandler(out io.Writer, h http.Handler, f LogFormatter) http.Handler {
  227. return loggingHandler{out, h, f}
  228. }
上海开阖软件有限公司 沪ICP备12045867号-1