...

Source file src/github.com/docker/distribution/context/http.go

Documentation: github.com/docker/distribution/context

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

View as plain text