http.go 8.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365
  1. package context
  2. import (
  3. "errors"
  4. "net"
  5. "net/http"
  6. "strings"
  7. "sync"
  8. "time"
  9. log "github.com/Sirupsen/logrus"
  10. "github.com/docker/distribution/uuid"
  11. "github.com/gorilla/mux"
  12. )
  13. // Common errors used with this package.
  14. var (
  15. ErrNoRequestContext = errors.New("no http request in context")
  16. ErrNoResponseWriterContext = errors.New("no http response in context")
  17. )
  18. func parseIP(ipStr string) net.IP {
  19. ip := net.ParseIP(ipStr)
  20. if ip == nil {
  21. log.Warnf("invalid remote IP address: %q", ipStr)
  22. }
  23. return ip
  24. }
  25. // RemoteAddr extracts the remote address of the request, taking into
  26. // account proxy headers.
  27. func RemoteAddr(r *http.Request) string {
  28. if prior := r.Header.Get("X-Forwarded-For"); prior != "" {
  29. proxies := strings.Split(prior, ",")
  30. if len(proxies) > 0 {
  31. remoteAddr := strings.Trim(proxies[0], " ")
  32. if parseIP(remoteAddr) != nil {
  33. return remoteAddr
  34. }
  35. }
  36. }
  37. // X-Real-Ip is less supported, but worth checking in the
  38. // absence of X-Forwarded-For
  39. if realIP := r.Header.Get("X-Real-Ip"); realIP != "" {
  40. if parseIP(realIP) != nil {
  41. return realIP
  42. }
  43. }
  44. return r.RemoteAddr
  45. }
  46. // RemoteIP extracts the remote IP of the request, taking into
  47. // account proxy headers.
  48. func RemoteIP(r *http.Request) string {
  49. addr := RemoteAddr(r)
  50. // Try parsing it as "IP:port"
  51. if ip, _, err := net.SplitHostPort(addr); err == nil {
  52. return ip
  53. }
  54. return addr
  55. }
  56. // WithRequest places the request on the context. The context of the request
  57. // is assigned a unique id, available at "http.request.id". The request itself
  58. // is available at "http.request". Other common attributes are available under
  59. // the prefix "http.request.". If a request is already present on the context,
  60. // this method will panic.
  61. func WithRequest(ctx Context, r *http.Request) Context {
  62. if ctx.Value("http.request") != nil {
  63. // NOTE(stevvooe): This needs to be considered a programming error. It
  64. // is unlikely that we'd want to have more than one request in
  65. // context.
  66. panic("only one request per context")
  67. }
  68. return &httpRequestContext{
  69. Context: ctx,
  70. startedAt: time.Now(),
  71. id: uuid.Generate().String(),
  72. r: r,
  73. }
  74. }
  75. // GetRequest returns the http request in the given context. Returns
  76. // ErrNoRequestContext if the context does not have an http request associated
  77. // with it.
  78. func GetRequest(ctx Context) (*http.Request, error) {
  79. if r, ok := ctx.Value("http.request").(*http.Request); r != nil && ok {
  80. return r, nil
  81. }
  82. return nil, ErrNoRequestContext
  83. }
  84. // GetRequestID attempts to resolve the current request id, if possible. An
  85. // error is return if it is not available on the context.
  86. func GetRequestID(ctx Context) string {
  87. return GetStringValue(ctx, "http.request.id")
  88. }
  89. // WithResponseWriter returns a new context and response writer that makes
  90. // interesting response statistics available within the context.
  91. func WithResponseWriter(ctx Context, w http.ResponseWriter) (Context, http.ResponseWriter) {
  92. irw := instrumentedResponseWriter{
  93. ResponseWriter: w,
  94. Context: ctx,
  95. }
  96. if closeNotifier, ok := w.(http.CloseNotifier); ok {
  97. irwCN := &instrumentedResponseWriterCN{
  98. instrumentedResponseWriter: irw,
  99. CloseNotifier: closeNotifier,
  100. }
  101. return irwCN, irwCN
  102. }
  103. return &irw, &irw
  104. }
  105. // GetResponseWriter returns the http.ResponseWriter from the provided
  106. // context. If not present, ErrNoResponseWriterContext is returned. The
  107. // returned instance provides instrumentation in the context.
  108. func GetResponseWriter(ctx Context) (http.ResponseWriter, error) {
  109. v := ctx.Value("http.response")
  110. rw, ok := v.(http.ResponseWriter)
  111. if !ok || rw == nil {
  112. return nil, ErrNoResponseWriterContext
  113. }
  114. return rw, nil
  115. }
  116. // getVarsFromRequest let's us change request vars implementation for testing
  117. // and maybe future changes.
  118. var getVarsFromRequest = mux.Vars
  119. // WithVars extracts gorilla/mux vars and makes them available on the returned
  120. // context. Variables are available at keys with the prefix "vars.". For
  121. // example, if looking for the variable "name", it can be accessed as
  122. // "vars.name". Implementations that are accessing values need not know that
  123. // the underlying context is implemented with gorilla/mux vars.
  124. func WithVars(ctx Context, r *http.Request) Context {
  125. return &muxVarsContext{
  126. Context: ctx,
  127. vars: getVarsFromRequest(r),
  128. }
  129. }
  130. // GetRequestLogger returns a logger that contains fields from the request in
  131. // the current context. If the request is not available in the context, no
  132. // fields will display. Request loggers can safely be pushed onto the context.
  133. func GetRequestLogger(ctx Context) Logger {
  134. return GetLogger(ctx,
  135. "http.request.id",
  136. "http.request.method",
  137. "http.request.host",
  138. "http.request.uri",
  139. "http.request.referer",
  140. "http.request.useragent",
  141. "http.request.remoteaddr",
  142. "http.request.contenttype")
  143. }
  144. // GetResponseLogger reads the current response stats and builds a logger.
  145. // Because the values are read at call time, pushing a logger returned from
  146. // this function on the context will lead to missing or invalid data. Only
  147. // call this at the end of a request, after the response has been written.
  148. func GetResponseLogger(ctx Context) Logger {
  149. l := getLogrusLogger(ctx,
  150. "http.response.written",
  151. "http.response.status",
  152. "http.response.contenttype")
  153. duration := Since(ctx, "http.request.startedat")
  154. if duration > 0 {
  155. l = l.WithField("http.response.duration", duration.String())
  156. }
  157. return l
  158. }
  159. // httpRequestContext makes information about a request available to context.
  160. type httpRequestContext struct {
  161. Context
  162. startedAt time.Time
  163. id string
  164. r *http.Request
  165. }
  166. // Value returns a keyed element of the request for use in the context. To get
  167. // the request itself, query "request". For other components, access them as
  168. // "request.<component>". For example, r.RequestURI
  169. func (ctx *httpRequestContext) Value(key interface{}) interface{} {
  170. if keyStr, ok := key.(string); ok {
  171. if keyStr == "http.request" {
  172. return ctx.r
  173. }
  174. if !strings.HasPrefix(keyStr, "http.request.") {
  175. goto fallback
  176. }
  177. parts := strings.Split(keyStr, ".")
  178. if len(parts) != 3 {
  179. goto fallback
  180. }
  181. switch parts[2] {
  182. case "uri":
  183. return ctx.r.RequestURI
  184. case "remoteaddr":
  185. return RemoteAddr(ctx.r)
  186. case "method":
  187. return ctx.r.Method
  188. case "host":
  189. return ctx.r.Host
  190. case "referer":
  191. referer := ctx.r.Referer()
  192. if referer != "" {
  193. return referer
  194. }
  195. case "useragent":
  196. return ctx.r.UserAgent()
  197. case "id":
  198. return ctx.id
  199. case "startedat":
  200. return ctx.startedAt
  201. case "contenttype":
  202. ct := ctx.r.Header.Get("Content-Type")
  203. if ct != "" {
  204. return ct
  205. }
  206. }
  207. }
  208. fallback:
  209. return ctx.Context.Value(key)
  210. }
  211. type muxVarsContext struct {
  212. Context
  213. vars map[string]string
  214. }
  215. func (ctx *muxVarsContext) Value(key interface{}) interface{} {
  216. if keyStr, ok := key.(string); ok {
  217. if keyStr == "vars" {
  218. return ctx.vars
  219. }
  220. if strings.HasPrefix(keyStr, "vars.") {
  221. keyStr = strings.TrimPrefix(keyStr, "vars.")
  222. }
  223. if v, ok := ctx.vars[keyStr]; ok {
  224. return v
  225. }
  226. }
  227. return ctx.Context.Value(key)
  228. }
  229. // instrumentedResponseWriterCN provides response writer information in a
  230. // context. It implements http.CloseNotifier so that users can detect
  231. // early disconnects.
  232. type instrumentedResponseWriterCN struct {
  233. instrumentedResponseWriter
  234. http.CloseNotifier
  235. }
  236. // instrumentedResponseWriter provides response writer information in a
  237. // context. This variant is only used in the case where CloseNotifier is not
  238. // implemented by the parent ResponseWriter.
  239. type instrumentedResponseWriter struct {
  240. http.ResponseWriter
  241. Context
  242. mu sync.Mutex
  243. status int
  244. written int64
  245. }
  246. func (irw *instrumentedResponseWriter) Write(p []byte) (n int, err error) {
  247. n, err = irw.ResponseWriter.Write(p)
  248. irw.mu.Lock()
  249. irw.written += int64(n)
  250. // Guess the likely status if not set.
  251. if irw.status == 0 {
  252. irw.status = http.StatusOK
  253. }
  254. irw.mu.Unlock()
  255. return
  256. }
  257. func (irw *instrumentedResponseWriter) WriteHeader(status int) {
  258. irw.ResponseWriter.WriteHeader(status)
  259. irw.mu.Lock()
  260. irw.status = status
  261. irw.mu.Unlock()
  262. }
  263. func (irw *instrumentedResponseWriter) Flush() {
  264. if flusher, ok := irw.ResponseWriter.(http.Flusher); ok {
  265. flusher.Flush()
  266. }
  267. }
  268. func (irw *instrumentedResponseWriter) Value(key interface{}) interface{} {
  269. if keyStr, ok := key.(string); ok {
  270. if keyStr == "http.response" {
  271. return irw
  272. }
  273. if !strings.HasPrefix(keyStr, "http.response.") {
  274. goto fallback
  275. }
  276. parts := strings.Split(keyStr, ".")
  277. if len(parts) != 3 {
  278. goto fallback
  279. }
  280. irw.mu.Lock()
  281. defer irw.mu.Unlock()
  282. switch parts[2] {
  283. case "written":
  284. return irw.written
  285. case "status":
  286. return irw.status
  287. case "contenttype":
  288. contentType := irw.Header().Get("Content-Type")
  289. if contentType != "" {
  290. return contentType
  291. }
  292. }
  293. }
  294. fallback:
  295. return irw.Context.Value(key)
  296. }
  297. func (irw *instrumentedResponseWriterCN) Value(key interface{}) interface{} {
  298. if keyStr, ok := key.(string); ok {
  299. if keyStr == "http.response" {
  300. return irw
  301. }
  302. }
  303. return irw.instrumentedResponseWriter.Value(key)
  304. }