http.go 9.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366
  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. if closeNotifier, ok := w.(http.CloseNotifier); ok {
  93. irwCN := &instrumentedResponseWriterCN{
  94. instrumentedResponseWriter: instrumentedResponseWriter{
  95. ResponseWriter: w,
  96. Context: ctx,
  97. },
  98. CloseNotifier: closeNotifier,
  99. }
  100. return irwCN, irwCN
  101. }
  102. irw := instrumentedResponseWriter{
  103. ResponseWriter: w,
  104. Context: ctx,
  105. }
  106. return &irw, &irw
  107. }
  108. // GetResponseWriter returns the http.ResponseWriter from the provided
  109. // context. If not present, ErrNoResponseWriterContext is returned. The
  110. // returned instance provides instrumentation in the context.
  111. func GetResponseWriter(ctx Context) (http.ResponseWriter, error) {
  112. v := ctx.Value("http.response")
  113. rw, ok := v.(http.ResponseWriter)
  114. if !ok || rw == nil {
  115. return nil, ErrNoResponseWriterContext
  116. }
  117. return rw, nil
  118. }
  119. // getVarsFromRequest let's us change request vars implementation for testing
  120. // and maybe future changes.
  121. var getVarsFromRequest = mux.Vars
  122. // WithVars extracts gorilla/mux vars and makes them available on the returned
  123. // context. Variables are available at keys with the prefix "vars.". For
  124. // example, if looking for the variable "name", it can be accessed as
  125. // "vars.name". Implementations that are accessing values need not know that
  126. // the underlying context is implemented with gorilla/mux vars.
  127. func WithVars(ctx Context, r *http.Request) Context {
  128. return &muxVarsContext{
  129. Context: ctx,
  130. vars: getVarsFromRequest(r),
  131. }
  132. }
  133. // GetRequestLogger returns a logger that contains fields from the request in
  134. // the current context. If the request is not available in the context, no
  135. // fields will display. Request loggers can safely be pushed onto the context.
  136. func GetRequestLogger(ctx Context) Logger {
  137. return GetLogger(ctx,
  138. "http.request.id",
  139. "http.request.method",
  140. "http.request.host",
  141. "http.request.uri",
  142. "http.request.referer",
  143. "http.request.useragent",
  144. "http.request.remoteaddr",
  145. "http.request.contenttype")
  146. }
  147. // GetResponseLogger reads the current response stats and builds a logger.
  148. // Because the values are read at call time, pushing a logger returned from
  149. // this function on the context will lead to missing or invalid data. Only
  150. // call this at the end of a request, after the response has been written.
  151. func GetResponseLogger(ctx Context) Logger {
  152. l := getLogrusLogger(ctx,
  153. "http.response.written",
  154. "http.response.status",
  155. "http.response.contenttype")
  156. duration := Since(ctx, "http.request.startedat")
  157. if duration > 0 {
  158. l = l.WithField("http.response.duration", duration.String())
  159. }
  160. return l
  161. }
  162. // httpRequestContext makes information about a request available to context.
  163. type httpRequestContext struct {
  164. Context
  165. startedAt time.Time
  166. id string
  167. r *http.Request
  168. }
  169. // Value returns a keyed element of the request for use in the context. To get
  170. // the request itself, query "request". For other components, access them as
  171. // "request.<component>". For example, r.RequestURI
  172. func (ctx *httpRequestContext) Value(key interface{}) interface{} {
  173. if keyStr, ok := key.(string); ok {
  174. if keyStr == "http.request" {
  175. return ctx.r
  176. }
  177. if !strings.HasPrefix(keyStr, "http.request.") {
  178. goto fallback
  179. }
  180. parts := strings.Split(keyStr, ".")
  181. if len(parts) != 3 {
  182. goto fallback
  183. }
  184. switch parts[2] {
  185. case "uri":
  186. return ctx.r.RequestURI
  187. case "remoteaddr":
  188. return RemoteAddr(ctx.r)
  189. case "method":
  190. return ctx.r.Method
  191. case "host":
  192. return ctx.r.Host
  193. case "referer":
  194. referer := ctx.r.Referer()
  195. if referer != "" {
  196. return referer
  197. }
  198. case "useragent":
  199. return ctx.r.UserAgent()
  200. case "id":
  201. return ctx.id
  202. case "startedat":
  203. return ctx.startedAt
  204. case "contenttype":
  205. ct := ctx.r.Header.Get("Content-Type")
  206. if ct != "" {
  207. return ct
  208. }
  209. }
  210. }
  211. fallback:
  212. return ctx.Context.Value(key)
  213. }
  214. type muxVarsContext struct {
  215. Context
  216. vars map[string]string
  217. }
  218. func (ctx *muxVarsContext) Value(key interface{}) interface{} {
  219. if keyStr, ok := key.(string); ok {
  220. if keyStr == "vars" {
  221. return ctx.vars
  222. }
  223. if strings.HasPrefix(keyStr, "vars.") {
  224. keyStr = strings.TrimPrefix(keyStr, "vars.")
  225. }
  226. if v, ok := ctx.vars[keyStr]; ok {
  227. return v
  228. }
  229. }
  230. return ctx.Context.Value(key)
  231. }
  232. // instrumentedResponseWriterCN provides response writer information in a
  233. // context. It implements http.CloseNotifier so that users can detect
  234. // early disconnects.
  235. type instrumentedResponseWriterCN struct {
  236. instrumentedResponseWriter
  237. http.CloseNotifier
  238. }
  239. // instrumentedResponseWriter provides response writer information in a
  240. // context. This variant is only used in the case where CloseNotifier is not
  241. // implemented by the parent ResponseWriter.
  242. type instrumentedResponseWriter struct {
  243. http.ResponseWriter
  244. Context
  245. mu sync.Mutex
  246. status int
  247. written int64
  248. }
  249. func (irw *instrumentedResponseWriter) Write(p []byte) (n int, err error) {
  250. n, err = irw.ResponseWriter.Write(p)
  251. irw.mu.Lock()
  252. irw.written += int64(n)
  253. // Guess the likely status if not set.
  254. if irw.status == 0 {
  255. irw.status = http.StatusOK
  256. }
  257. irw.mu.Unlock()
  258. return
  259. }
  260. func (irw *instrumentedResponseWriter) WriteHeader(status int) {
  261. irw.ResponseWriter.WriteHeader(status)
  262. irw.mu.Lock()
  263. irw.status = status
  264. irw.mu.Unlock()
  265. }
  266. func (irw *instrumentedResponseWriter) Flush() {
  267. if flusher, ok := irw.ResponseWriter.(http.Flusher); ok {
  268. flusher.Flush()
  269. }
  270. }
  271. func (irw *instrumentedResponseWriter) Value(key interface{}) interface{} {
  272. if keyStr, ok := key.(string); ok {
  273. if keyStr == "http.response" {
  274. return irw
  275. }
  276. if !strings.HasPrefix(keyStr, "http.response.") {
  277. goto fallback
  278. }
  279. parts := strings.Split(keyStr, ".")
  280. if len(parts) != 3 {
  281. goto fallback
  282. }
  283. irw.mu.Lock()
  284. defer irw.mu.Unlock()
  285. switch parts[2] {
  286. case "written":
  287. return irw.written
  288. case "status":
  289. return irw.status
  290. case "contenttype":
  291. contentType := irw.Header().Get("Content-Type")
  292. if contentType != "" {
  293. return contentType
  294. }
  295. }
  296. }
  297. fallback:
  298. return irw.Context.Value(key)
  299. }
  300. func (irw *instrumentedResponseWriterCN) Value(key interface{}) interface{} {
  301. if keyStr, ok := key.(string); ok {
  302. if keyStr == "http.response" {
  303. return irw
  304. }
  305. }
  306. return irw.instrumentedResponseWriter.Value(key)
  307. }