Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
169 changes: 169 additions & 0 deletions context.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,169 @@
package chi

import (
"context"
"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
}

// 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

// parentCtx is the parent of this one, for using Context as a
// context.Context directly. This is an optimization that saves
// 1 allocation.
parentCtx context.Context

// Routing path/method override used during the route search.
// See Mux#routeHTTP method.
RoutePath string
RouteMethod string

// routePathSource tracks the r.URL.Path value that was used to set
// RoutePath. When a middleware rewrites r.URL.Path, routeHTTP compares
// r.URL.Path against routePathSource to detect the change and
// recompute RoutePath accordingly.
routePathSource string

// mountPrefix records the mount pattern of the current sub-router
// (including trailing slash), so that routeHTTP can strip it from
// r.URL.Path when recomputing RoutePath after a middleware rewrite.
mountPrefix string

// URLParams are the stack of routeParams captured during the
// routing lifecycle across a stack of sub-routers.
URLParams RouteParams

// Route parameters matched for the current sub-router. It is
// intentionally unexported so it can't be tampered.
routeParams 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

// 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

methodsAllowed []methodTyp // allowed methods in case of a 405
methodNotAllowed bool
}

// Reset a routing context to its initial state.
func (x *Context) Reset() {
x.Routes = nil
x.RoutePath = ""
x.RouteMethod = ""
x.routePathSource = ""
x.mountPrefix = ""
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
x.methodsAllowed = x.methodsAllowed[:0]
x.parentCtx = nil
}

// 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 it's advised to only use this value after calling the next handler.
func (x *Context) RoutePattern() string {
if x == nil {
return ""
}
routePattern := strings.Join(x.RoutePatterns, "")
routePattern = replaceWildcards(routePattern)
if routePattern != "/" {
routePattern = strings.TrimSuffix(routePattern, "//")
routePattern = strings.TrimSuffix(routePattern, "/")
}
return routePattern
}

// replaceWildcards takes a route pattern and replaces all occurrences of
// "/*/" with "/". It iteratively runs until no wildcards remain to
// correctly handle consecutive wildcards.
func replaceWildcards(p string) string {
for strings.Contains(p, "/*/") {
p = strings.ReplaceAll(p, "/*/", "/")
}
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
}
3 changes: 3 additions & 0 deletions go.mod
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
module github.com/madalynerlge2/chi

go 1.22
7 changes: 0 additions & 7 deletions main.go

This file was deleted.

50 changes: 50 additions & 0 deletions method.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
package chi

import (
"strings"
)

// methodTyp is a bitfield of supported HTTP methods.
type methodTyp uint

const (
mCONNECT methodTyp = 1 << iota
mDELETE
mGET
mHEAD
mOPTIONS
mPATCH
mPOST
mPUT
mTRACE
mALL = mCONNECT | mDELETE | mGET | mHEAD | mOPTIONS | mPATCH | mPOST | mPUT | mTRACE
mSTUB = 1 << 15
mSTUBOR = mSTUB | mALL
)

var methodMap = map[string]methodTyp{
"CONNECT": mCONNECT,
"DELETE": mDELETE,
"GET": mGET,
"HEAD": mHEAD,
"OPTIONS": mOPTIONS,
"PATCH": mPATCH,
"POST": mPOST,
"PUT": mPUT,
"TRACE": mTRACE,
}

// RegisterMethod adds support for custom HTTP method handlers, available via
// Router#Method and Router#MethodFunc.
func RegisterMethod(method string) {
method = strings.ToUpper(method)
if method == "" {
return
}
if _, ok := methodMap[method]; ok {
return
}
// Find the next available bit position
nextBit := methodTyp(1) << uint(len(methodMap))
methodMap[method] = nextBit
}
54 changes: 54 additions & 0 deletions middleware.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
package chi

import (
"net/http"
"strings"
)

// Middlewares is a list of middleware handler functions
type Middlewares []func(http.Handler) http.Handler

// Chain returns a Middlewares Chain that can be used to wrap a handler.
// The chain is built in reverse order so that the first middleware in the
// slice is the outermost wrapper (runs first).
func Chain(middlewares ...func(http.Handler) http.Handler) Middlewares {
return Middlewares(middlewares)
}

// Handler wraps the given handler with the middleware chain.
func (mws Middlewares) Handler(h http.Handler) http.Handler {
if len(mws) == 0 {
return h
}
for i := len(mws) - 1; i >= 0; i-- {
h = mws[i](h)
}
return h
}

// HandlerFunc wraps the given handler function with the middleware chain.
func (mws Middlewares) HandlerFunc(h http.HandlerFunc) http.Handler {
return mws.Handler(http.HandlerFunc(h))
}

// methodNotAllowedHandler returns a 405 handler with the given allowed methods.
func methodNotAllowedHandler(methodsAllowed ...methodTyp) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
allowed := make([]string, 0, len(methodsAllowed))
for _, m := range methodsAllowed {
allowed = append(allowed, methodTypString(m))
}
w.Header().Set("Allow", strings.Join(allowed, ", "))
w.WriteHeader(http.StatusMethodNotAllowed)
}
}

// methodTypString converts a methodTyp to its string representation.
func methodTypString(m methodTyp) string {
for s, mt := range methodMap {
if mt == m {
return s
}
}
return ""
}
Loading