pax_global_header00006660000000000000000000000064134231720450014513gustar00rootroot0000000000000052 comment=490b001d03d16a116d7c44d95d216c69a1aae798 goji-2.0.2/000077500000000000000000000000001342317204500124445ustar00rootroot00000000000000goji-2.0.2/.travis.yml000066400000000000000000000002341342317204500145540ustar00rootroot00000000000000go_import_path: goji.io language: go sudo: false go: - "1.7.x" - "1.8.x" - "1.9.x" - "1.10.x" - "1.11.x" script: - go test -cover -race ./... goji-2.0.2/LICENSE000066400000000000000000000021041342317204500134460ustar00rootroot00000000000000Copyright (c) 2015, 2016 Carl Jackson (carl@avtok.com) MIT License Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. goji-2.0.2/README.md000066400000000000000000000060311342317204500137230ustar00rootroot00000000000000Goji ==== [![GoDoc](https://godoc.org/goji.io?status.svg)](https://godoc.org/goji.io) [![Build Status](https://travis-ci.org/goji/goji.svg?branch=master)](https://travis-ci.org/goji/goji) Goji is a HTTP request multiplexer, similar to [`net/http.ServeMux`][servemux]. It compares incoming requests to a list of registered [Patterns][pattern], and dispatches to the [http.Handler][handler] that corresponds to the first matching Pattern. Goji also supports [Middleware][middleware] (composable shared functionality applied to every request) and uses the standard [`context`][context] package to store request-scoped values. [servemux]: https://golang.org/pkg/net/http/#ServeMux [pattern]: https://godoc.org/goji.io#Pattern [handler]: https://golang.org/pkg/net/http/#Handler [middleware]: https://godoc.org/goji.io#Mux.Use [context]: https://golang.org/pkg/context Quick Start ----------- ```go package main import ( "fmt" "net/http" "goji.io" "goji.io/pat" ) func hello(w http.ResponseWriter, r *http.Request) { name := pat.Param(r, "name") fmt.Fprintf(w, "Hello, %s!", name) } func main() { mux := goji.NewMux() mux.HandleFunc(pat.Get("/hello/:name"), hello) http.ListenAndServe("localhost:8000", mux) } ``` Please refer to [Goji's GoDoc Documentation][godoc] for a full API reference. [godoc]: https://godoc.org/goji.io Stability --------- Goji's API was recently updated to use the new `net/http` and `context` integration, and is therefore some of its interfaces are in a state of flux. We don't expect any further changes to the API, and expect to be able to announce API stability soon. Goji is suitable for use in production. Prior to Go 1.7, Goji promised API stability with a different API to the one that is offered today. The author broke this promise, and does not take this breach of trust lightly. While stability is obviously extremely important, the author and community have decided to follow the broader Go community in standardizing on the standard library copy of the `context` package. Users of the old API can find that familiar API on the `net-context` branch. The author promises to maintain both the `net-context` branch and `master` for the forseeable future. Community / Contributing ------------------------ Goji maintains a mailing list, [gojiberries][berries], where you should feel welcome to ask questions about the project (no matter how simple!), to announce projects or libraries built on top of Goji, or to talk about Goji more generally. Goji's author (Carl Jackson) also loves to hear from users directly at his personal email address, which is available on his GitHub profile page. Contributions to Goji are welcome, however please be advised that due to Goji's stability guarantees interface changes are unlikely to be accepted. All interactions in the Goji community will be held to the high standard of the broader Go community's [Code of Conduct][conduct]. [berries]: https://groups.google.com/forum/#!forum/gojiberries [conduct]: https://golang.org/conduct goji-2.0.2/dispatch.go000066400000000000000000000004461342317204500145760ustar00rootroot00000000000000package goji import ( "net/http" "goji.io/internal" ) type dispatch struct{} func (d dispatch) ServeHTTP(w http.ResponseWriter, r *http.Request) { ctx := r.Context() h := ctx.Value(internal.Handler) if h == nil { http.NotFound(w, r) } else { h.(http.Handler).ServeHTTP(w, r) } } goji-2.0.2/dispatch_test.go000066400000000000000000000010571342317204500156340ustar00rootroot00000000000000package goji import ( "context" "net/http" "testing" "goji.io/internal" ) func TestDispatch(t *testing.T) { t.Parallel() var d dispatch w, r := wr() d.ServeHTTP(w, r) if w.Code != 404 { t.Errorf("status: expected %d, got %d", 404, w.Code) } w, r = wr() h := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { w.WriteHeader(123) }) ctx := context.WithValue(context.Background(), internal.Handler, h) r = r.WithContext(ctx) d.ServeHTTP(w, r) if w.Code != 123 { t.Errorf("status: expected %d, got %d", 123, w.Code) } } goji-2.0.2/goji.go000066400000000000000000000057401342317204500137310ustar00rootroot00000000000000/* Package goji is a minimalistic and flexible HTTP request multiplexer. Goji itself has very few features: it is first and foremost a standard set of interfaces for writing web applications. Several subpackages are distributed with Goji to provide standard production-ready implementations of several of the interfaces, however users are also encouraged to implement the interfaces on their own, especially if their needs are unusual. */ package goji import "net/http" /* Pattern determines whether a given request matches some criteria. Goji users looking for a concrete type that implements this interface should consider Goji's "pat" sub-package, which implements a small domain specific language for HTTP routing. Patterns typically only examine a small portion of incoming requests, most commonly the HTTP method and the URL's RawPath. As an optimization, Goji can elide calls to your Pattern for requests it knows cannot match. Pattern authors who wish to take advantage of this functionality (and in some cases an asymptotic performance improvement) can augment their Pattern implementations with any of the following methods: // HTTPMethods returns a set of HTTP methods that this Pattern matches, // or nil if it's not possible to determine which HTTP methods might be // matched. Put another way, requests with HTTP methods not in the // returned set are guaranteed to never match this Pattern. HTTPMethods() map[string]struct{} // PathPrefix returns a string which all RawPaths that match this // Pattern must have as a prefix. Put another way, requests with // RawPaths that do not contain the returned string as a prefix are // guaranteed to never match this Pattern. PathPrefix() string The presence or lack of these performance improvements should be viewed as an implementation detail and are not part of Goji's API compatibility guarantee. It is the responsibility of Pattern authors to ensure that their Match function always returns correct results, even if these optimizations are not performed. All operations on Patterns must be safe for concurrent use by multiple goroutines. */ type Pattern interface { // Match examines the input Request to determine if it matches some // criteria, and if so returns a non-nil output Request. This returned // Request will be passed to the middleware stack and the final Handler. // // Patterns often extract variables from the Request, for instance from // the URL or from HTTP headers. In this case, it is common for the // Request returned from the Match function to be derived from the input // Request using the WithContext function, with a Context that contains // those variable bindings. If no variable bindings are necessary, // another common choice is to return the input Request unchanged. // // Match must not mutate the passed request if it returns nil. // Implementers are also strongly discouraged from mutating the input // Request even in the event of a match; instead, prefer making a copy. Match(*http.Request) *http.Request } goji-2.0.2/handle.go000066400000000000000000000022451342317204500142310ustar00rootroot00000000000000package goji import "net/http" /* Handle adds a new route to the Mux. Requests that match the given Pattern will be dispatched to the given http.Handler. Routing is performed in the order in which routes are added: the first route with a matching Pattern will be used. In particular, Goji guarantees that routing is performed in a manner that is indistinguishable from the following algorithm: // Assume routes is a slice that every call to Handle appends to for _, route := range routes { // For performance, Patterns can opt out of this call to Match. // See the documentation for Pattern for more. if r2 := route.pattern.Match(r); r2 != nil { route.handler.ServeHTTP(w, r2) break } } It is not safe to concurrently register routes from multiple goroutines, or to register routes concurrently with requests. */ func (m *Mux) Handle(p Pattern, h http.Handler) { m.router.add(p, h) } /* HandleFunc adds a new route to the Mux. It is equivalent to calling Handle on a handler wrapped with http.HandlerFunc, and is provided only for convenience. */ func (m *Mux) HandleFunc(p Pattern, h func(http.ResponseWriter, *http.Request)) { m.Handle(p, http.HandlerFunc(h)) } goji-2.0.2/handle_test.go000066400000000000000000000011761342317204500152720ustar00rootroot00000000000000package goji import ( "net/http" "testing" ) func TestHandle(t *testing.T) { t.Parallel() m := NewMux() called := false fn := func(w http.ResponseWriter, r *http.Request) { called = true } m.Handle(boolPattern(true), http.HandlerFunc(fn)) w, r := wr() m.ServeHTTP(w, r) if !called { t.Error("expected handler to be called") } } func TestHandleFunc(t *testing.T) { t.Parallel() m := NewMux() called := false fn := func(w http.ResponseWriter, r *http.Request) { called = true } m.HandleFunc(boolPattern(true), fn) w, r := wr() m.ServeHTTP(w, r) if !called { t.Error("expected handler to be called") } } goji-2.0.2/internal/000077500000000000000000000000001342317204500142605ustar00rootroot00000000000000goji-2.0.2/internal/context.go000066400000000000000000000010541342317204500162730ustar00rootroot00000000000000package internal // ContextKey is a type used for Goji's context.Context keys. type ContextKey int var ( // Path is the context key used to store the path Goji uses for its // PathPrefix optimization. Path interface{} = ContextKey(0) // Pattern is the context key used to store the Pattern that Goji last // matched. Pattern interface{} = ContextKey(1) // Handler is the context key used to store the Handler that Goji last // mached (and will therefore dispatch to at the end of the middleware // stack). Handler interface{} = ContextKey(2) ) goji-2.0.2/internal/internal.go000066400000000000000000000013041342317204500164210ustar00rootroot00000000000000/* Package internal is a private package that allows Goji to expose a less confusing interface to its users. This package must not be used outside of Goji; every piece of its functionality has been exposed by one of Goji's subpackages. The problem this package solves is to allow Goji to internally agree on types and secret values between its packages without introducing import cycles. Goji needs to agree on these types and values in order to organize its public API into audience-specific subpackages (for instance, a package for pattern authors, a package for middleware authors, and a main package for routing users) without exposing implementation details in any of the packages. */ package internal goji-2.0.2/middleware.go000066400000000000000000000046421342317204500151160ustar00rootroot00000000000000package goji import "net/http" /* Use appends a middleware to the Mux's middleware stack. Middleware are composable pieces of functionality that augment http.Handlers. Common examples of middleware include request loggers, authentication checkers, and metrics gatherers. Middleware are evaluated in the reverse order in which they were added, but the resulting http.Handlers execute in "normal" order (i.e., the http.Handler returned by the first Middleware to be added gets called first). For instance, given middleware A, B, and C, added in that order, Goji will behave similarly to this snippet: augmentedHandler := A(B(C(yourHandler))) augmentedHandler.ServeHTTP(w, r) Assuming each of A, B, and C look something like this: func A(inner http.Handler) http.Handler { log.Print("A: called") mw := func(w http.ResponseWriter, r *http.Request) { log.Print("A: before") inner.ServeHTTP(w, r) log.Print("A: after") } return http.HandlerFunc(mw) } we'd expect to see the following in the log: C: called B: called A: called --- A: before B: before C: before yourHandler: called C: after B: after A: after Note that augmentedHandler will called many times, producing the log output below the divider, while the outer middleware functions (the log output above the divider) will only be called a handful of times at application boot. Middleware in Goji is called after routing has been performed. Therefore it is possible to examine any routing information placed into the Request context by Patterns, or to view or modify the http.Handler that will be routed to. Middleware authors should read the documentation for the "middleware" subpackage for more information about how this is done. The http.Handler returned by the given middleware must be safe for concurrent use by multiple goroutines. It is not safe to concurrently register middleware from multiple goroutines, or to register middleware concurrently with requests. */ func (m *Mux) Use(middleware func(http.Handler) http.Handler) { m.middleware = append(m.middleware, middleware) m.buildChain() } // Pre-compile a http.Handler for us to use during dispatch. Yes, this means // that adding middleware is quadratic, but it (a) happens during configuration // time, not at "runtime", and (b) n should ~always be small. func (m *Mux) buildChain() { m.handler = dispatch{} for i := len(m.middleware) - 1; i >= 0; i-- { m.handler = m.middleware[i](m.handler) } } goji-2.0.2/middleware/000077500000000000000000000000001342317204500145615ustar00rootroot00000000000000goji-2.0.2/middleware/middleware.go000066400000000000000000000027361342317204500172350ustar00rootroot00000000000000/* Package middleware contains utilities for Goji Middleware authors. Unless you are writing middleware for your application, you should avoid importing this package. Instead, use the abstractions provided by your middleware package. */ package middleware import ( "context" "net/http" "goji.io" "goji.io/internal" ) /* Pattern returns the most recently matched Pattern, or nil if no pattern was matched. */ func Pattern(ctx context.Context) goji.Pattern { p := ctx.Value(internal.Pattern) if p == nil { return nil } return p.(goji.Pattern) } /* SetPattern returns a new context in which the given Pattern is used as the most recently matched pattern. */ func SetPattern(ctx context.Context, p goji.Pattern) context.Context { return context.WithValue(ctx, internal.Pattern, p) } /* Handler returns the handler corresponding to the most recently matched Pattern, or nil if no pattern was matched. The handler returned by this function is the one that will be dispatched to at the end of the middleware stack. If the returned Handler is nil, http.NotFound will be used instead. */ func Handler(ctx context.Context) http.Handler { h := ctx.Value(internal.Handler) if h == nil { return nil } return h.(http.Handler) } /* SetHandler returns a new context in which the given Handler was most recently matched and which consequently will be dispatched to. */ func SetHandler(ctx context.Context, h http.Handler) context.Context { return context.WithValue(ctx, internal.Handler, h) } goji-2.0.2/middleware/middleware_test.go000066400000000000000000000016201342317204500202630ustar00rootroot00000000000000package middleware import ( "context" "net/http" "testing" ) type testPattern bool func (t testPattern) Match(r *http.Request) *http.Request { if t { return r } return nil } type testHandler struct{} func (t testHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {} func TestPattern(t *testing.T) { t.Parallel() pat := testPattern(true) ctx := SetPattern(context.Background(), pat) if pat2 := Pattern(ctx); pat2 != pat { t.Errorf("got ctx=%v, expected %v", pat2, pat) } if pat2 := Pattern(context.Background()); pat2 != nil { t.Errorf("got ctx=%v, expecte nil", pat2) } } func TestHandler(t *testing.T) { t.Parallel() h := testHandler{} ctx := SetHandler(context.Background(), h) if h2 := Handler(ctx); h2 != h { t.Errorf("got handler=%v, expected %v", h2, h) } if h2 := Handler(context.Background()); h2 != nil { t.Errorf("got handler=%v, expected nil", h2) } } goji-2.0.2/middleware_test.go000066400000000000000000000035051342317204500161520ustar00rootroot00000000000000package goji import ( "net/http" "testing" ) func expectSequence(t *testing.T, ch chan string, seq ...string) { for i, str := range seq { if msg := <-ch; msg != str { t.Errorf("[%d] expected %s, got %s", i, str, msg) } } } func TestMiddleware(t *testing.T) { t.Parallel() m := NewMux() ch := make(chan string, 10) m.Use(func(h http.Handler) http.Handler { fn := func(w http.ResponseWriter, r *http.Request) { ch <- "before one" h.ServeHTTP(w, r) ch <- "after one" } return http.HandlerFunc(fn) }) m.Use(func(h http.Handler) http.Handler { fn := func(w http.ResponseWriter, r *http.Request) { ch <- "before two" h.ServeHTTP(w, r) ch <- "after two" } return http.HandlerFunc(fn) }) m.Handle(boolPattern(true), http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { ch <- "handler" })) m.ServeHTTP(wr()) expectSequence(t, ch, "before one", "before two", "handler", "after two", "after one") } func makeMiddleware(ch chan string, name string) func(http.Handler) http.Handler { return func(h http.Handler) http.Handler { fn := func(w http.ResponseWriter, r *http.Request) { ch <- "before " + name h.ServeHTTP(w, r) ch <- "after " + name } return http.HandlerFunc(fn) } } func TestMiddlewareReconfigure(t *testing.T) { t.Parallel() m := NewMux() ch := make(chan string, 10) m.Use(makeMiddleware(ch, "one")) m.Use(makeMiddleware(ch, "two")) m.Handle(boolPattern(true), http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { ch <- "handler" })) w, r := wr() m.ServeHTTP(w, r) expectSequence(t, ch, "before one", "before two", "handler", "after two", "after one") m.Use(makeMiddleware(ch, "three")) w, r = wr() m.ServeHTTP(w, r) expectSequence(t, ch, "before one", "before two", "before three", "handler", "after three", "after two", "after one") } goji-2.0.2/mux.go000066400000000000000000000041021342317204500136010ustar00rootroot00000000000000package goji import ( "context" "net/http" "goji.io/internal" ) /* Mux is a HTTP multiplexer / router similar to net/http.ServeMux. Muxes multiplex traffic between many http.Handlers by selecting the first applicable Pattern. They then call a common middleware stack, finally passing control to the selected http.Handler. See the documentation on the Handle function for more information about how routing is performed, the documentation on the Pattern type for more information about request matching, and the documentation for the Use method for more about middleware. Muxes cannot be configured concurrently from multiple goroutines, nor can they be configured concurrently with requests. */ type Mux struct { handler http.Handler middleware []func(http.Handler) http.Handler router router root bool } /* NewMux returns a new Mux with no configured middleware or routes. */ func NewMux() *Mux { m := SubMux() m.root = true return m } /* SubMux returns a new Mux with no configured middleware or routes, and which inherits routing information from the passed context. This is especially useful when using one Mux as a http.Handler registered to another "parent" Mux. For example, a common pattern is to organize applications in a way that mirrors the structure of its URLs: a photo-sharing site might have URLs that start with "/users/" and URLs that start with "/albums/", and might be organized using three Muxes: root := NewMux() users := SubMux() root.Handle(pat.New("/users/*"), users) albums := SubMux() root.Handle(pat.New("/albums/*"), albums) // e.g., GET /users/carl users.Handle(pat.Get("/:name"), renderProfile) // e.g., POST /albums/ albums.Handle(pat.Post("/"), newAlbum) */ func SubMux() *Mux { m := &Mux{} m.buildChain() return m } // ServeHTTP implements net/http.Handler. func (m *Mux) ServeHTTP(w http.ResponseWriter, r *http.Request) { if m.root { ctx := r.Context() ctx = context.WithValue(ctx, internal.Path, r.URL.EscapedPath()) r = r.WithContext(ctx) } r = m.router.route(r) m.handler.ServeHTTP(w, r) } var _ http.Handler = &Mux{} goji-2.0.2/mux_test.go000066400000000000000000000016571342317204500146540ustar00rootroot00000000000000package goji import ( "context" "net/http" "testing" "goji.io/internal" ) func TestMuxExistingPath(t *testing.T) { m := NewMux() handler := func(w http.ResponseWriter, r *http.Request) { ctx := r.Context() if path := ctx.Value(internal.Path).(string); path != "/" { t.Errorf("expected path=/, got %q", path) } } m.HandleFunc(boolPattern(true), handler) w, r := wr() ctx := context.WithValue(context.Background(), internal.Path, "/hello") r = r.WithContext(ctx) m.ServeHTTP(w, r) } func TestSubMuxExistingPath(t *testing.T) { m := SubMux() handler := func(w http.ResponseWriter, r *http.Request) { ctx := r.Context() if path := ctx.Value(internal.Path).(string); path != "/hello" { t.Errorf("expected path=/hello, got %q", path) } } m.HandleFunc(boolPattern(true), handler) w, r := wr() ctx := context.WithValue(context.Background(), internal.Path, "/hello") r = r.WithContext(ctx) m.ServeHTTP(w, r) } goji-2.0.2/pat/000077500000000000000000000000001342317204500132305ustar00rootroot00000000000000goji-2.0.2/pat/match.go000066400000000000000000000017361342317204500146620ustar00rootroot00000000000000package pat import ( "context" "sort" "goji.io/internal" "goji.io/pattern" ) type match struct { context.Context pat *Pattern matches []string } func (m match) Value(key interface{}) interface{} { switch key { case pattern.AllVariables: var vs map[pattern.Variable]interface{} if vsi := m.Context.Value(key); vsi == nil { if len(m.pat.pats) == 0 { return nil } vs = make(map[pattern.Variable]interface{}, len(m.matches)) } else { vs = vsi.(map[pattern.Variable]interface{}) } for _, p := range m.pat.pats { vs[p.name] = m.matches[p.idx] } return vs case internal.Path: if len(m.matches) == len(m.pat.pats)+1 { return m.matches[len(m.matches)-1] } return "" } if k, ok := key.(pattern.Variable); ok { i := sort.Search(len(m.pat.pats), func(i int) bool { return m.pat.pats[i].name >= k }) if i < len(m.pat.pats) && m.pat.pats[i].name == k { return m.matches[m.pat.pats[i].idx] } } return m.Context.Value(key) } goji-2.0.2/pat/match_test.go000066400000000000000000000024651342317204500157210ustar00rootroot00000000000000package pat import ( "context" "net/http" "reflect" "testing" "goji.io/pattern" ) func TestExistingContext(t *testing.T) { t.Parallel() pat := New("/hi/:c/:a/:r/:l") req, err := http.NewRequest("GET", "/hi/foo/bar/baz/quux", nil) if err != nil { panic(err) } ctx := context.Background() ctx = pattern.SetPath(ctx, req.URL.EscapedPath()) ctx = context.WithValue(ctx, pattern.AllVariables, map[pattern.Variable]interface{}{ "hello": "world", "c": "nope", }) ctx = context.WithValue(ctx, pattern.Variable("user"), "carl") req = req.WithContext(ctx) req = pat.Match(req) if req == nil { t.Fatalf("expected pattern to match") } ctx = req.Context() expected := map[pattern.Variable]interface{}{ "c": "foo", "a": "bar", "r": "baz", "l": "quux", } for k, v := range expected { if p := Param(req, string(k)); p != v { t.Errorf("expected %s=%q, got %q", k, v, p) } } expected["hello"] = "world" all := ctx.Value(pattern.AllVariables).(map[pattern.Variable]interface{}) if !reflect.DeepEqual(all, expected) { t.Errorf("expected %v, got %v", expected, all) } if path := pattern.Path(ctx); path != "" { t.Errorf("expected path=%q, got %q", "", path) } if user := ctx.Value(pattern.Variable("user")); user != "carl" { t.Errorf("expected user=%q, got %q", "carl", user) } } goji-2.0.2/pat/methods.go000066400000000000000000000026171342317204500152300ustar00rootroot00000000000000package pat /* NewWithMethods returns a Pat route that matches http methods that are provided */ func NewWithMethods(pat string, methods ...string) *Pattern { p := New(pat) methodSet := make(map[string]struct{}, len(methods)) for _, method := range methods { methodSet[method] = struct{}{} } p.methods = methodSet return p } /* Delete returns a Pat route that only matches the DELETE HTTP method. */ func Delete(pat string) *Pattern { return NewWithMethods(pat, "DELETE") } /* Get returns a Pat route that only matches the GET and HEAD HTTP method. HEAD requests are handled transparently by net/http. */ func Get(pat string) *Pattern { return NewWithMethods(pat, "GET", "HEAD") } /* Head returns a Pat route that only matches the HEAD HTTP method. */ func Head(pat string) *Pattern { return NewWithMethods(pat, "HEAD") } /* Options returns a Pat route that only matches the OPTIONS HTTP method. */ func Options(pat string) *Pattern { return NewWithMethods(pat, "OPTIONS") } /* Patch returns a Pat route that only matches the PATCH HTTP method. */ func Patch(pat string) *Pattern { return NewWithMethods(pat, "PATCH") } /* Post returns a Pat route that only matches the POST HTTP method. */ func Post(pat string) *Pattern { return NewWithMethods(pat, "POST") } /* Put returns a Pat route that only matches the PUT HTTP method. */ func Put(pat string) *Pattern { return NewWithMethods(pat, "PUT") } goji-2.0.2/pat/methods_test.go000066400000000000000000000043261342317204500162660ustar00rootroot00000000000000package pat import "testing" func TestNewWithMethods(t *testing.T) { t.Parallel() pat := NewWithMethods("/", "LOCK", "UNLOCK") if pat.Match(mustReq("POST", "/")) != nil { t.Errorf("pattern was LOCK/UNLOCK, but matched POST") } if pat.Match(mustReq("LOCK", "/")) == nil { t.Errorf("pattern didn't match LOCK") } if pat.Match(mustReq("UNLOCK", "/")) == nil { t.Errorf("pattern didn't match UNLOCK") } } func TestDelete(t *testing.T) { t.Parallel() pat := Delete("/") if pat.Match(mustReq("GET", "/")) != nil { t.Errorf("pattern was DELETE, but matched GET") } if pat.Match(mustReq("DELETE", "/")) == nil { t.Errorf("pattern didn't match DELETE") } } func TestGet(t *testing.T) { t.Parallel() pat := Get("/") if pat.Match(mustReq("POST", "/")) != nil { t.Errorf("pattern was GET, but matched POST") } if pat.Match(mustReq("GET", "/")) == nil { t.Errorf("pattern didn't match GET") } if pat.Match(mustReq("HEAD", "/")) == nil { t.Errorf("pattern didn't match HEAD") } } func TestHead(t *testing.T) { t.Parallel() pat := Head("/") if pat.Match(mustReq("GET", "/")) != nil { t.Errorf("pattern was HEAD, but matched GET") } if pat.Match(mustReq("HEAD", "/")) == nil { t.Errorf("pattern didn't match HEAD") } } func TestOptions(t *testing.T) { t.Parallel() pat := Options("/") if pat.Match(mustReq("GET", "/")) != nil { t.Errorf("pattern was OPTIONS, but matched GET") } if pat.Match(mustReq("OPTIONS", "/")) == nil { t.Errorf("pattern didn't match OPTIONS") } } func TestPatch(t *testing.T) { t.Parallel() pat := Patch("/") if pat.Match(mustReq("GET", "/")) != nil { t.Errorf("pattern was PATCH, but matched GET") } if pat.Match(mustReq("PATCH", "/")) == nil { t.Errorf("pattern didn't match PATCH") } } func TestPost(t *testing.T) { t.Parallel() pat := Post("/") if pat.Match(mustReq("GET", "/")) != nil { t.Errorf("pattern was POST, but matched GET") } if pat.Match(mustReq("POST", "/")) == nil { t.Errorf("pattern didn't match POST") } } func TestPut(t *testing.T) { t.Parallel() pat := Put("/") if pat.Match(mustReq("GET", "/")) != nil { t.Errorf("pattern was PUT, but matched GET") } if pat.Match(mustReq("PUT", "/")) == nil { t.Errorf("pattern didn't match PUT") } } goji-2.0.2/pat/pat.go000066400000000000000000000177451342317204500143610ustar00rootroot00000000000000/* Package pat is a URL-matching domain-specific language for Goji. Quick Reference The following table gives an overview of the language this package accepts. See the subsequent sections for a more detailed explanation of what each pattern does. Pattern Matches Does Not Match / / /hello /hello /hello /hi /hello/ /user/:name /user/carl /user/carl/photos /user/alice /user/carl/ /user/ /:file.:ext /data.json /.json /info.txt /data. /data.tar.gz /data.json/download /user/* /user/ /user /user/carl /user/carl/photos Static Paths Most URL paths may be specified directly: the pattern "/hello" matches URLs with precisely that path ("/hello/", for instance, is treated as distinct). Note that this package operates on raw (i.e., escaped) paths (see the documentation for net/url.URL.EscapedPath). In order to match a character that can appear escaped in a URL path, use its percent-encoded form. Named Matches Named matches allow URL paths to contain any value in a particular path segment. Such matches are denoted by a leading ":", for example ":name" in the rule "/user/:name", and permit any non-empty value in that position. For instance, in the previous "/user/:name" example, the path "/user/carl" is matched, while "/user/" or "/user/carl/" (note the trailing slash) are not matched. Pat rules can contain any number of named matches. Named matches set URL variables by comparing pattern names to the segments they matched. In our "/user/:name" example, a request for "/user/carl" would bind the "name" variable to the value "carl". Use the Param function to extract these variables from the request context. Variable names in a single pattern must be unique. Matches are ordinarily delimited by slashes ("/"), but several other characters are accepted as delimiters (with slightly different semantics): the period ("."), semicolon (";"), and comma (",") characters. For instance, given the pattern "/:file.:ext", the request "/data.json" would match, binding "file" to "data" and "ext" to "json". Note that these special characters are treated slightly differently than slashes: the above pattern also matches the path "/data.tar.gz", with "ext" getting set to "tar.gz"; and the pattern "/:file" matches names with dots in them (like "data.json"). Prefix Matches Pat can also match prefixes of routes using wildcards. Prefix wildcard routes end with "/*", and match just the path segments preceding the asterisk. For instance, the pattern "/user/*" will match "/user/" and "/user/carl/photos" but not "/user" (note the lack of a trailing slash). The unmatched suffix, including the leading slash ("/"), are placed into the request context, which allows subsequent routing (e.g., a subrouter) to continue from where this pattern left off. For instance, in the "/user/*" pattern from above, a request for "/user/carl/photos" will consume the "/user" prefix, leaving the path "/carl/photos" for subsequent patterns to handle. A subrouter pattern for "/:name/photos" would match this remaining path segment, for instance. */ package pat import ( "net/http" "regexp" "sort" "strings" "goji.io/pattern" ) type patNames []struct { name pattern.Variable idx int } func (p patNames) Len() int { return len(p) } func (p patNames) Less(i, j int) bool { return p[i].name < p[j].name } func (p patNames) Swap(i, j int) { p[i], p[j] = p[j], p[i] } /* Pattern implements goji.Pattern using a path-matching domain specific language. See the package documentation for more information about the semantics of this object. */ type Pattern struct { raw string methods map[string]struct{} // These are parallel arrays of each pattern string (sans ":"), the // breaks each expect afterwords (used to support e.g., "." dividers), // and the string literals in between every pattern. There is always one // more literal than pattern, and they are interleaved like this: // etc... pats patNames breaks []byte literals []string wildcard bool } // "Break characters" are characters that can end patterns. They are not allowed // to appear in pattern names. "/" was chosen because it is the standard path // separator, and "." was chosen because it often delimits file extensions. ";" // and "," were chosen because Section 3.3 of RFC 3986 suggests their use. const bc = "/.;," var patternRe = regexp.MustCompile(`[` + bc + `]:([^` + bc + `]+)`) /* New returns a new Pattern from the given Pat route. See the package documentation for more information about what syntax is accepted by this function. */ func New(pat string) *Pattern { p := &Pattern{raw: pat} if strings.HasSuffix(pat, "/*") { pat = pat[:len(pat)-1] p.wildcard = true } matches := patternRe.FindAllStringSubmatchIndex(pat, -1) numMatches := len(matches) p.pats = make(patNames, numMatches) p.breaks = make([]byte, numMatches) p.literals = make([]string, numMatches+1) n := 0 for i, match := range matches { a, b := match[2], match[3] p.literals[i] = pat[n : a-1] // Need to leave off the colon p.pats[i].name = pattern.Variable(pat[a:b]) p.pats[i].idx = i if b == len(pat) { p.breaks[i] = '/' } else { p.breaks[i] = pat[b] } n = b } p.literals[numMatches] = pat[n:] sort.Sort(p.pats) return p } /* Match runs the Pat pattern on the given request, returning a non-nil output request if the input request matches the pattern. This function satisfies goji.Pattern. */ func (p *Pattern) Match(r *http.Request) *http.Request { if p.methods != nil { if _, ok := p.methods[r.Method]; !ok { return nil } } // Check Path ctx := r.Context() path := pattern.Path(ctx) var scratch []string if p.wildcard { scratch = make([]string, len(p.pats)+1) } else if len(p.pats) > 0 { scratch = make([]string, len(p.pats)) } for i := range p.pats { sli := p.literals[i] if !strings.HasPrefix(path, sli) { return nil } path = path[len(sli):] m := 0 bc := p.breaks[i] for ; m < len(path); m++ { if path[m] == bc || path[m] == '/' { break } } if m == 0 { // Empty strings are not matches, otherwise routes like // "/:foo" would match the path "/" return nil } scratch[i] = path[:m] path = path[m:] } // There's exactly one more literal than pat. tail := p.literals[len(p.pats)] if p.wildcard { if !strings.HasPrefix(path, tail) { return nil } scratch[len(p.pats)] = path[len(tail)-1:] } else if path != tail { return nil } for i := range p.pats { var err error scratch[i], err = unescape(scratch[i]) if err != nil { // If we encounter an encoding error here, there's // really not much we can do about it with our current // API, and I'm not really interested in supporting // clients that misencode URLs anyways. return nil } } return r.WithContext(&match{ctx, p, scratch}) } /* PathPrefix returns a string prefix that the Paths of all requests that this Pattern accepts must contain. This function satisfies goji's PathPrefix Pattern optimization. */ func (p *Pattern) PathPrefix() string { return p.literals[0] } /* HTTPMethods returns a set of HTTP methods that all requests that this Pattern matches must be in, or nil if it's not possible to determine which HTTP methods might be matched. This function satisfies goji's HTTPMethods Pattern optimization. */ func (p *Pattern) HTTPMethods() map[string]struct{} { return p.methods } /* String returns the pattern string that was used to create this Pattern. */ func (p *Pattern) String() string { return p.raw } /* Param returns the bound parameter with the given name. For instance, given the route: /user/:name and the URL Path: /user/carl a call to Param(r, "name") would return the string "carl". It is the caller's responsibility to ensure that the variable has been bound. Attempts to access variables that have not been set (or which have been invalidly set) are considered programmer errors and will trigger a panic. */ func Param(r *http.Request, name string) string { return r.Context().Value(pattern.Variable(name)).(string) } goji-2.0.2/pat/pat_test.go000066400000000000000000000114641342317204500154100ustar00rootroot00000000000000package pat import ( "context" "net/http" "reflect" "testing" "goji.io/pattern" ) func mustReq(method, path string) *http.Request { req, err := http.NewRequest(method, path, nil) if err != nil { panic(err) } ctx := pattern.SetPath(context.Background(), req.URL.EscapedPath()) return req.WithContext(ctx) } type PatTest struct { pat string req string match bool vars map[pattern.Variable]interface{} path string } type pv map[pattern.Variable]interface{} var PatTests = []PatTest{ {"/", "/", true, nil, ""}, {"/", "/hello", false, nil, ""}, {"/hello", "/hello", true, nil, ""}, {"/:name", "/carl", true, pv{"name": "carl"}, ""}, {"/:name", "/carl/", false, nil, ""}, {"/:name", "/", false, nil, ""}, {"/:name/", "/carl/", true, pv{"name": "carl"}, ""}, {"/:name/", "/carl/no", false, nil, ""}, {"/:name/hi", "/carl/hi", true, pv{"name": "carl"}, ""}, {"/:name/:color", "/carl/red", true, pv{"name": "carl", "color": "red"}, ""}, {"/:name/:color", "/carl/", false, nil, ""}, {"/:name/:color", "/carl.red", false, nil, ""}, {"/:file.:ext", "/data.json", true, pv{"file": "data", "ext": "json"}, ""}, {"/:file.:ext", "/data.tar.gz", true, pv{"file": "data", "ext": "tar.gz"}, ""}, {"/:file.:ext", "/data", false, nil, ""}, {"/:file.:ext", "/data.", false, nil, ""}, {"/:file.:ext", "/.gitconfig", false, nil, ""}, {"/:file.:ext", "/data.json/", false, nil, ""}, {"/:file.:ext", "/data/json", false, nil, ""}, {"/:file.:ext", "/data;json", false, nil, ""}, {"/hello.:ext", "/hello.json", true, pv{"ext": "json"}, ""}, {"/:file.json", "/hello.json", true, pv{"file": "hello"}, ""}, {"/:file.json", "/hello.world.json", false, nil, ""}, {"/file;:version", "/file;1", true, pv{"version": "1"}, ""}, {"/file;:version", "/file,1", false, nil, ""}, {"/file,:version", "/file,1", true, pv{"version": "1"}, ""}, {"/file,:version", "/file;1", false, nil, ""}, {"/*", "/", true, nil, "/"}, {"/*", "/hello", true, nil, "/hello"}, {"/users/*", "/", false, nil, ""}, {"/users/*", "/users", false, nil, ""}, {"/users/*", "/users/", true, nil, "/"}, {"/users/*", "/users/carl", true, nil, "/carl"}, {"/users/*", "/profile/carl", false, nil, ""}, {"/:name/*", "/carl", false, nil, ""}, {"/:name/*", "/carl/", true, pv{"name": "carl"}, "/"}, {"/:name/*", "/carl/photos", true, pv{"name": "carl"}, "/photos"}, {"/:name/*", "/carl/photos%2f2015", true, pv{"name": "carl"}, "/photos%2f2015"}, } func TestPat(t *testing.T) { t.Parallel() for _, test := range PatTests { pat := New(test.pat) if str := pat.String(); str != test.pat { t.Errorf("[%q %q] String()=%q, expected=%q", test.pat, test.req, str, test.pat) } req := pat.Match(mustReq("GET", test.req)) if (req != nil) != test.match { t.Errorf("[%q %q] match=%v, expected=%v", test.pat, test.req, req != nil, test.match) } if req == nil { continue } ctx := req.Context() if path := pattern.Path(ctx); path != test.path { t.Errorf("[%q %q] path=%q, expected=%q", test.pat, test.req, path, test.path) } vars := ctx.Value(pattern.AllVariables) if (vars != nil) != (test.vars != nil) { t.Errorf("[%q %q] vars=%#v, expected=%#v", test.pat, test.req, vars, test.vars) } if vars == nil { continue } if tvars := vars.(map[pattern.Variable]interface{}); !reflect.DeepEqual(tvars, test.vars) { t.Errorf("[%q %q] vars=%v, expected=%v", test.pat, test.req, tvars, test.vars) } } } func TestBadPathEncoding(t *testing.T) { t.Parallel() // This one is hard to fit into the table-driven test above since Go // refuses to have anything to do with poorly encoded URLs. ctx := pattern.SetPath(context.Background(), "/%nope") r, _ := http.NewRequest("GET", "/", nil) if New("/:name").Match(r.WithContext(ctx)) != nil { t.Error("unexpected match") } } var PathPrefixTests = []struct { pat string prefix string }{ {"/", "/"}, {"/hello/:world", "/hello/"}, {"/users/:name/profile", "/users/"}, {"/users/*", "/users/"}, } func TestPathPrefix(t *testing.T) { t.Parallel() for _, test := range PathPrefixTests { pat := New(test.pat) if prefix := pat.PathPrefix(); prefix != test.prefix { t.Errorf("%q.PathPrefix() = %q, expected %q", test.pat, prefix, test.prefix) } } } func TestHTTPMethods(t *testing.T) { t.Parallel() pat := New("/foo") if methods := pat.HTTPMethods(); methods != nil { t.Errorf("expected nil with no methods, got %v", methods) } pat = Get("/boo") expect := map[string]struct{}{"GET": {}, "HEAD": {}} if methods := pat.HTTPMethods(); !reflect.DeepEqual(expect, methods) { t.Errorf("methods=%v, expected %v", methods, expect) } } func TestParam(t *testing.T) { t.Parallel() pat := New("/hello/:name") req := pat.Match(mustReq("GET", "/hello/carl")) if req == nil { t.Fatal("expected a match") } if name := Param(req, "name"); name != "carl" { t.Errorf("name=%q, expected %q", name, "carl") } } goji-2.0.2/pat/url.go000066400000000000000000000020641342317204500143630ustar00rootroot00000000000000package pat import "net/url" // Stolen (with modifications) from net/url in the Go stdlib func ishex(c byte) bool { switch { case '0' <= c && c <= '9': return true case 'a' <= c && c <= 'f': return true case 'A' <= c && c <= 'F': return true } return false } func unhex(c byte) byte { switch { case '0' <= c && c <= '9': return c - '0' case 'a' <= c && c <= 'f': return c - 'a' + 10 case 'A' <= c && c <= 'F': return c - 'A' + 10 } return 0 } func unescape(s string) (string, error) { // Count %, check that they're well-formed. n := 0 for i := 0; i < len(s); { switch s[i] { case '%': n++ if i+2 >= len(s) || !ishex(s[i+1]) || !ishex(s[i+2]) { s = s[i:] if len(s) > 3 { s = s[:3] } return "", url.EscapeError(s) } i += 3 default: i++ } } if n == 0 { return s, nil } t := make([]byte, len(s)-2*n) j := 0 for i := 0; i < len(s); { switch s[i] { case '%': t[j] = unhex(s[i+1])<<4 | unhex(s[i+2]) j++ i += 3 default: t[j] = s[i] j++ i++ } } return string(t), nil } goji-2.0.2/pat/url_test.go000066400000000000000000000022331342317204500154200ustar00rootroot00000000000000package pat import ( "net/url" "testing" ) var HexTexts = []struct { input byte ishex bool unhex byte }{ {'0', true, 0}, {'4', true, 4}, {'a', true, 10}, {'F', true, 15}, {'h', false, 0}, {'^', false, 0}, } func TestHex(t *testing.T) { t.Parallel() for _, test := range HexTexts { if actual := ishex(test.input); actual != test.ishex { t.Errorf("ishex(%v) == %v, expected %v", test.input, actual, test.ishex) } if actual := unhex(test.input); actual != test.unhex { t.Errorf("unhex(%v) == %v, expected %v", test.input, actual, test.unhex) } } } var UnescapeTests = []struct { input string err error output string }{ {"hello", nil, "hello"}, {"file%20one%26two", nil, "file one&two"}, {"one/two%2fthree", nil, "one/two/three"}, {"this%20is%0not%valid", url.EscapeError("%0n"), ""}, } func TestUnescape(t *testing.T) { t.Parallel() for _, test := range UnescapeTests { if actual, err := unescape(test.input); err != test.err { t.Errorf("unescape(%q) had err %v, expected %q", test.input, err, test.err) } else if actual != test.output { t.Errorf("unescape(%q) = %q, expected %q)", test.input, actual, test.output) } } } goji-2.0.2/pattern.go000066400000000000000000000006021342317204500144460ustar00rootroot00000000000000package goji // httpMethods is an internal interface for the HTTPMethods pattern // optimization. See the documentation on Pattern for more. type httpMethods interface { HTTPMethods() map[string]struct{} } // pathPrefix is an internal interface for the PathPrefix pattern optimization. // See the documentation on Pattern for more. type pathPrefix interface { PathPrefix() string } goji-2.0.2/pattern/000077500000000000000000000000001342317204500141215ustar00rootroot00000000000000goji-2.0.2/pattern/pattern.go000066400000000000000000000042741342317204500161340ustar00rootroot00000000000000/* Package pattern contains utilities for Goji Pattern authors. Goji users should not import this package. Instead, use the utilities provided by your Pattern package. If you are looking for an implementation of Pattern, try Goji's pat subpackage, which contains a simple domain specific language for specifying routes. For Pattern authors, use of this subpackage is entirely optional. Nevertheless, authors who wish to take advantage of Goji's PathPrefix optimization or who wish to standardize on a few common interfaces may find this package useful. */ package pattern import ( "context" "goji.io/internal" ) /* Variable is a standard type for the names of Pattern-bound variables, e.g. variables extracted from the URL. Pass the name of a variable, cast to this type, to context.Context.Value to retrieve the value bound to that name. */ type Variable string type allVariables struct{} /* AllVariables is a standard value which, when passed to context.Context.Value, returns all variable bindings present in the context, with bindings in newer contexts overriding values deeper in the stack. The concrete type map[Variable]interface{} is used for this purpose. If no variables are bound, nil should be returned instead of an empty map. */ var AllVariables = allVariables{} /* Path returns the path that the Goji router uses to perform the PathPrefix optimization. While this function does not distinguish between the absence of a path and an empty path, Goji will automatically extract a path from the request if none is present. By convention, paths are stored in their escaped form (i.e., the value returned by net/url.URL.EscapedPath, and not URL.Path) to ensure that Patterns have as much discretion as possible (e.g., to behave differently for '/' and '%2f'). */ func Path(ctx context.Context) string { pi := ctx.Value(internal.Path) if pi == nil { return "" } return pi.(string) } /* SetPath returns a new context in which the given path is used by the Goji Router when performing the PathPrefix optimization. See Path for more information about the intended semantics of this path. */ func SetPath(ctx context.Context, path string) context.Context { return context.WithValue(ctx, internal.Path, path) } goji-2.0.2/pattern/pattern_test.go000066400000000000000000000012561342317204500171700ustar00rootroot00000000000000package pattern import ( "context" "net/http" "testing" ) type boolPattern bool func (b boolPattern) Match(ctx context.Context, r *http.Request) context.Context { if b { return ctx } return nil } type prefixPattern string func (p prefixPattern) Match(ctx context.Context, r *http.Request) context.Context { return ctx } func (p prefixPattern) PathPrefix() string { return string(p) } func TestPathRoundTrip(t *testing.T) { t.Parallel() ctx := SetPath(context.Background(), "hi") if path := Path(ctx); path != "hi" { t.Errorf("expected hi, got %q", path) } if path := Path(context.Background()); path != "" { t.Errorf("expected empty path, got %q", path) } } goji-2.0.2/router.go000066400000000000000000000005431342317204500143150ustar00rootroot00000000000000package goji import ( "context" "net/http" "goji.io/internal" ) type match struct { context.Context p Pattern h http.Handler } func (m match) Value(key interface{}) interface{} { switch key { case internal.Pattern: return m.p case internal.Handler: return m.h default: return m.Context.Value(key) } } var _ context.Context = match{} goji-2.0.2/router_simple.go000066400000000000000000000011201342317204500156560ustar00rootroot00000000000000// +build goji_router_simple package goji import "net/http" /* This is the simplest correct router implementation for Goji. */ type router []route type route struct { Pattern http.Handler } func (rt *router) add(p Pattern, h http.Handler) { *rt = append(*rt, route{p, h}) } func (rt *router) route(r *http.Request) *http.Request { for _, route := range *rt { if r2 := route.Match(r); r2 != nil { return r2.WithContext(&match{ Context: r2.Context(), p: route.Pattern, h: route.Handler, }) } } return r.WithContext(&match{Context: r.Context()}) } goji-2.0.2/router_test.go000066400000000000000000000127251342317204500153610ustar00rootroot00000000000000package goji import ( "context" "net/http" "reflect" "testing" "goji.io/internal" "goji.io/pattern" ) func TestNoMatch(t *testing.T) { t.Parallel() var rt router rt.add(boolPattern(false), http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { t.Fatal("did not expect handler to be called") })) _, r := wr() ctx := context.Background() ctx = context.WithValue(ctx, internal.Pattern, boolPattern(true)) ctx = context.WithValue(ctx, internal.Pattern, boolPattern(true)) ctx = context.WithValue(ctx, pattern.Variable("answer"), 42) ctx = context.WithValue(ctx, internal.Path, "/") r = r.WithContext(ctx) r = rt.route(r) ctx = r.Context() if p := ctx.Value(internal.Pattern); p != nil { t.Errorf("unexpected pattern %v", p) } if h := ctx.Value(internal.Handler); h != nil { t.Errorf("unexpected handler %v", h) } if h := ctx.Value(pattern.Variable("answer")); h != 42 { t.Errorf("context didn't work: got %v, wanted %v", h, 42) } } /* These are meant to be end-to-end torture tests of Goji's routing semantics. We generate a list of patterns that can be turned off incrementally with a global "high water mark." We then run a sequence of requests through the router N times, incrementing the mark each time. The net effect is that we can compile the entire set of routes Goji would attempt for every request, ensuring that the router is picking routes in the correct order. */ var TestRoutes = []testPattern{ testPattern{methods: nil, prefix: "/"}, testPattern{methods: nil, prefix: "/a"}, testPattern{methods: []string{"POST", "PUT"}, prefix: "/a"}, testPattern{methods: []string{"GET", "POST"}, prefix: "/a"}, testPattern{methods: []string{"GET"}, prefix: "/b"}, testPattern{methods: nil, prefix: "/ab"}, testPattern{methods: []string{"POST", "PUT"}, prefix: "/"}, testPattern{methods: nil, prefix: "/ba"}, testPattern{methods: nil, prefix: "/"}, testPattern{methods: []string{}, prefix: "/"}, testPattern{methods: nil, prefix: "/carl"}, testPattern{methods: []string{"PUT"}, prefix: "/car"}, testPattern{methods: nil, prefix: "/cake"}, testPattern{methods: nil, prefix: "/car"}, testPattern{methods: []string{"GET"}, prefix: "/c"}, testPattern{methods: []string{"POST"}, prefix: "/"}, testPattern{methods: []string{"PUT"}, prefix: "/"}, } var RouterTests = []struct { method, path string results []int }{ {"GET", "/", []int{0, 8, 8, 8, 8, 8, 8, 8, 8, -1, -1, -1, -1, -1, -1, -1, -1}}, {"POST", "/", []int{0, 6, 6, 6, 6, 6, 6, 8, 8, 15, 15, 15, 15, 15, 15, 15, -1}}, {"PUT", "/", []int{0, 6, 6, 6, 6, 6, 6, 8, 8, 16, 16, 16, 16, 16, 16, 16, 16}}, {"HEAD", "/", []int{0, 8, 8, 8, 8, 8, 8, 8, 8, -1, -1, -1, -1, -1, -1, -1, -1}}, {"GET", "/a", []int{0, 1, 3, 3, 8, 8, 8, 8, 8, -1, -1, -1, -1, -1, -1, -1, -1}}, {"POST", "/a", []int{0, 1, 2, 3, 6, 6, 6, 8, 8, 15, 15, 15, 15, 15, 15, 15, -1}}, {"PUT", "/a", []int{0, 1, 2, 6, 6, 6, 6, 8, 8, 16, 16, 16, 16, 16, 16, 16, 16}}, {"HEAD", "/a", []int{0, 1, 8, 8, 8, 8, 8, 8, 8, -1, -1, -1, -1, -1, -1, -1, -1}}, {"GET", "/b", []int{0, 4, 4, 4, 4, 8, 8, 8, 8, -1, -1, -1, -1, -1, -1, -1, -1}}, {"POST", "/b", []int{0, 6, 6, 6, 6, 6, 6, 8, 8, 15, 15, 15, 15, 15, 15, 15, -1}}, {"GET", "/ba", []int{0, 4, 4, 4, 4, 7, 7, 7, 8, -1, -1, -1, -1, -1, -1, -1, -1}}, {"GET", "/c", []int{0, 8, 8, 8, 8, 8, 8, 8, 8, 14, 14, 14, 14, 14, 14, -1, -1}}, {"POST", "/c", []int{0, 6, 6, 6, 6, 6, 6, 8, 8, 15, 15, 15, 15, 15, 15, 15, -1}}, {"GET", "/ab", []int{0, 1, 3, 3, 5, 5, 8, 8, 8, -1, -1, -1, -1, -1, -1, -1, -1}}, {"POST", "/ab", []int{0, 1, 2, 3, 5, 5, 6, 8, 8, 15, 15, 15, 15, 15, 15, 15, -1}}, {"GET", "/carl", []int{0, 8, 8, 8, 8, 8, 8, 8, 8, 10, 10, 13, 13, 13, 14, -1, -1}}, {"POST", "/carl", []int{0, 6, 6, 6, 6, 6, 6, 8, 8, 10, 10, 13, 13, 13, 15, 15, -1}}, {"HEAD", "/carl", []int{0, 8, 8, 8, 8, 8, 8, 8, 8, 10, 10, 13, 13, 13, -1, -1, -1}}, {"PUT", "/carl", []int{0, 6, 6, 6, 6, 6, 6, 8, 8, 10, 10, 11, 13, 13, 16, 16, 16}}, {"GET", "/cake", []int{0, 8, 8, 8, 8, 8, 8, 8, 8, 12, 12, 12, 12, 14, 14, -1, -1}}, {"PUT", "/cake", []int{0, 6, 6, 6, 6, 6, 6, 8, 8, 12, 12, 12, 12, 16, 16, 16, 16}}, {"OHAI", "/carl", []int{0, 8, 8, 8, 8, 8, 8, 8, 8, 10, 10, 13, 13, 13, -1, -1, -1}}, } func TestRouter(t *testing.T) { t.Parallel() var rt router mark := new(int) for i, p := range TestRoutes { i := i p.index = i p.mark = mark rt.add(p, intHandler(i)) } for i, test := range RouterTests { r, err := http.NewRequest(test.method, test.path, nil) if err != nil { panic(err) } ctx := context.WithValue(context.Background(), internal.Path, test.path) r = r.WithContext(ctx) var out []int for *mark = 0; *mark < len(TestRoutes); *mark++ { r := rt.route(r) ctx := r.Context() if h := ctx.Value(internal.Handler); h != nil { out = append(out, int(h.(intHandler))) } else { out = append(out, -1) } } if !reflect.DeepEqual(out, test.results) { t.Errorf("[%d] expected %v got %v", i, test.results, out) } } } type contextPattern struct{} func (contextPattern) Match(r *http.Request) *http.Request { return r.WithContext(context.WithValue(r.Context(), pattern.Variable("hello"), "world")) } func TestRouterContextPropagation(t *testing.T) { t.Parallel() var rt router rt.add(contextPattern{}, intHandler(0)) _, r := wr() r = r.WithContext(context.WithValue(r.Context(), internal.Path, "/")) r2 := rt.route(r) ctx := r2.Context() if hello := ctx.Value(pattern.Variable("hello")).(string); hello != "world" { t.Fatalf("routed request didn't include correct key from pattern: %q", hello) } } goji-2.0.2/router_trie.go000066400000000000000000000072121342317204500153400ustar00rootroot00000000000000// +build !goji_router_simple package goji import ( "net/http" "sort" "strings" "goji.io/internal" ) type router struct { routes []route methods map[string]*trieNode wildcard trieNode } type route struct { Pattern http.Handler } type child struct { prefix string node *trieNode } type trieNode struct { routes []int children []child } func (rt *router) add(p Pattern, h http.Handler) { i := len(rt.routes) rt.routes = append(rt.routes, route{p, h}) var prefix string if pp, ok := p.(pathPrefix); ok { prefix = pp.PathPrefix() } var methods map[string]struct{} if hm, ok := p.(httpMethods); ok { methods = hm.HTTPMethods() } if methods == nil { rt.wildcard.add(prefix, i) for _, sub := range rt.methods { sub.add(prefix, i) } } else { if rt.methods == nil { rt.methods = make(map[string]*trieNode) } for method := range methods { if _, ok := rt.methods[method]; !ok { rt.methods[method] = rt.wildcard.clone() } rt.methods[method].add(prefix, i) } } } func (rt *router) route(r *http.Request) *http.Request { tn := &rt.wildcard if tn2, ok := rt.methods[r.Method]; ok { tn = tn2 } ctx := r.Context() path := ctx.Value(internal.Path).(string) for path != "" { i := sort.Search(len(tn.children), func(i int) bool { return path[0] <= tn.children[i].prefix[0] }) if i == len(tn.children) || !strings.HasPrefix(path, tn.children[i].prefix) { break } path = path[len(tn.children[i].prefix):] tn = tn.children[i].node } for _, i := range tn.routes { if r2 := rt.routes[i].Match(r); r2 != nil { return r2.WithContext(&match{ Context: r2.Context(), p: rt.routes[i].Pattern, h: rt.routes[i].Handler, }) } } return r.WithContext(&match{Context: ctx}) } // We can be a teensy bit more efficient here: we're maintaining a sorted list, // so we know exactly where to insert the new element. But since that involves // more bookkeeping and makes the code messier, let's cross that bridge when we // come to it. type byPrefix []child func (b byPrefix) Len() int { return len(b) } func (b byPrefix) Less(i, j int) bool { return b[i].prefix < b[j].prefix } func (b byPrefix) Swap(i, j int) { b[i], b[j] = b[j], b[i] } func longestPrefix(a, b string) string { mlen := len(a) if len(b) < mlen { mlen = len(b) } for i := 0; i < mlen; i++ { if a[i] != b[i] { return a[:i] } } return a[:mlen] } func (tn *trieNode) add(prefix string, idx int) { if len(prefix) == 0 { tn.routes = append(tn.routes, idx) for i := range tn.children { tn.children[i].node.add(prefix, idx) } return } ch := prefix[0] i := sort.Search(len(tn.children), func(i int) bool { return ch <= tn.children[i].prefix[0] }) if i == len(tn.children) || ch != tn.children[i].prefix[0] { routes := append([]int(nil), tn.routes...) tn.children = append(tn.children, child{ prefix: prefix, node: &trieNode{routes: append(routes, idx)}, }) } else { lp := longestPrefix(prefix, tn.children[i].prefix) if tn.children[i].prefix == lp { tn.children[i].node.add(prefix[len(lp):], idx) return } split := new(trieNode) split.children = []child{ {tn.children[i].prefix[len(lp):], tn.children[i].node}, } split.routes = append([]int(nil), tn.routes...) split.add(prefix[len(lp):], idx) tn.children[i].prefix = lp tn.children[i].node = split } sort.Sort(byPrefix(tn.children)) } func (tn *trieNode) clone() *trieNode { clone := new(trieNode) clone.routes = append(clone.routes, tn.routes...) clone.children = append(clone.children, tn.children...) for i := range clone.children { clone.children[i].node = tn.children[i].node.clone() } return clone } goji-2.0.2/util_test.go000066400000000000000000000022161342317204500150100ustar00rootroot00000000000000package goji import ( "net/http" "net/http/httptest" "strings" "goji.io/internal" ) type boolPattern bool func (b boolPattern) Match(r *http.Request) *http.Request { if b { return r } return nil } type testPattern struct { index int mark *int methods []string prefix string } func (t testPattern) Match(r *http.Request) *http.Request { ctx := r.Context() if t.index < *t.mark { return nil } path := ctx.Value(internal.Path).(string) if !strings.HasPrefix(path, t.prefix) { return nil } if t.methods != nil { if _, ok := t.HTTPMethods()[r.Method]; !ok { return nil } } return r } func (t testPattern) PathPrefix() string { return t.prefix } func (t testPattern) HTTPMethods() map[string]struct{} { if t.methods == nil { return nil } m := make(map[string]struct{}) for _, method := range t.methods { m[method] = struct{}{} } return m } type intHandler int func (i intHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { } func wr() (*httptest.ResponseRecorder, *http.Request) { w := httptest.NewRecorder() r, err := http.NewRequest("GET", "/", nil) if err != nil { panic(err) } return w, r }