package chi import ( "context" "net" "net/http" "strings" ) // URLParam returns the url parameter from a http.Request object. func URLParam(r *http.Request, key string) string { if rctx := RouteContext(r.Context()); rctx != nil { return rctx.URLParam(key) } return "" } // URLParamFromCtx returns the url parameter from a http.Request Context. func URLParamFromCtx(ctx context.Context, key string) string { if rctx := RouteContext(ctx); rctx != nil { return rctx.URLParam(key) } return "" } // RouteContext returns chi's routing Context object from a // http.Request Context. func RouteContext(ctx context.Context) *Context { val, _ := ctx.Value(RouteCtxKey).(*Context) return val } // ServerBaseContext wraps an http.Handler to set the request context to the // `baseCtx`. func ServerBaseContext(baseCtx context.Context, h http.Handler) http.Handler { fn := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { ctx := r.Context() baseCtx := baseCtx // Copy over default net/http server context keys if v, ok := ctx.Value(http.ServerContextKey).(*http.Server); ok { baseCtx = context.WithValue(baseCtx, http.ServerContextKey, v) } if v, ok := ctx.Value(http.LocalAddrContextKey).(net.Addr); ok { baseCtx = context.WithValue(baseCtx, http.LocalAddrContextKey, v) } h.ServeHTTP(w, r.WithContext(baseCtx)) }) return fn } // NewRouteContext returns a new routing Context object. func NewRouteContext() *Context { return &Context{} } var ( // RouteCtxKey is the context.Context key to store the request context. RouteCtxKey = &contextKey{"RouteContext"} ) // Context is the default routing context set on the root node of a // request context to track route patterns, URL parameters and // an optional routing path. type Context struct { Routes Routes // Routing path/method override used during the route search. // See Mux#routeHTTP method. RoutePath string RouteMethod string // Routing pattern stack throughout the lifecycle of the request, // across all connected routers. It is a record of all matching // patterns across a stack of sub-routers. RoutePatterns []string // URLParams are the stack of routeParams captured during the // routing lifecycle across a stack of sub-routers. URLParams RouteParams // The endpoint routing pattern that matched the request URI path // or `RoutePath` of the current sub-router. This value will update // during the lifecycle of a request passing through a stack of // sub-routers. routePattern string // Route parameters matched for the current sub-router. It is // intentionally unexported so it cant be tampered. routeParams RouteParams // methodNotAllowed hint methodNotAllowed bool } // Reset a routing context to its initial state. func (x *Context) Reset() { x.Routes = nil x.RoutePath = "" x.RouteMethod = "" x.RoutePatterns = x.RoutePatterns[:0] x.URLParams.Keys = x.URLParams.Keys[:0] x.URLParams.Values = x.URLParams.Values[:0] x.routePattern = "" x.routeParams.Keys = x.routeParams.Keys[:0] x.routeParams.Values = x.routeParams.Values[:0] x.methodNotAllowed = false } // URLParam returns the corresponding URL parameter value from the request // routing context. func (x *Context) URLParam(key string) string { for k := len(x.URLParams.Keys) - 1; k >= 0; k-- { if x.URLParams.Keys[k] == key { return x.URLParams.Values[k] } } return "" } // RoutePattern builds the routing pattern string for the particular // request, at the particular point during routing. This means, the value // will change throughout the execution of a request in a router. That is // why its advised to only use this value after calling the next handler. // // For example, // // func Instrument(next http.Handler) http.Handler { // return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { // next.ServeHTTP(w, r) // routePattern := chi.RouteContext(r.Context()).RoutePattern() // measure(w, r, routePattern) // }) // } func (x *Context) RoutePattern() string { routePattern := strings.Join(x.RoutePatterns, "") return replaceWildcards(routePattern) } // replaceWildcards takes a route pattern and recursively replaces all // occurrences of "/*/" to "/". func replaceWildcards(p string) string { if strings.Contains(p, "/*/") { return replaceWildcards(strings.Replace(p, "/*/", "/", -1)) } return p } // RouteParams is a structure to track URL routing parameters efficiently. type RouteParams struct { Keys, Values []string } // Add will append a URL parameter to the end of the route param func (s *RouteParams) Add(key, value string) { s.Keys = append(s.Keys, key) s.Values = append(s.Values, value) } // contextKey is a value for use with context.WithValue. It's used as // a pointer so it fits in an interface{} without allocation. This technique // for defining context keys was copied from Go 1.7's new use of context in net/http. type contextKey struct { name string } func (k *contextKey) String() string { return "chi context value " + k.name }