From d65ed21ea90ebb3b630eebc16efffb7a932432fe Mon Sep 17 00:00:00 2001 From: Carl Jackson Date: Sun, 15 Feb 2015 16:07:02 -0800 Subject: [PATCH] Use "vanity" types to clarify use of interface{} In order to expose a convenient API, it's unfortunately necessary to lean on Go's interface{} quite a bit: in reality we only accept a handful of types at each call site, but it's impossible to express this using the type system. Prior to this commit (as well as the ParsePattern commit), I exposed all of this type information in the form of an enormous comment on web.Mux, however this was pretty gross. Instead, let's use "vanity" type aliases for interface{} to provide documentation about which types are accepted where. This will hopefully make the API documentation easier to skim, but doesn't affect any existing uses of Goji. This commit also clarifies a couple other parts of the documentation. --- default.go | 28 ++++++------ web/mux.go | 119 ++++++++++++++++++------------------------------- web/pattern.go | 31 ++----------- web/web.go | 61 ++++++++++++++++++++++++- 4 files changed, 120 insertions(+), 119 deletions(-) diff --git a/default.go b/default.go index 541830e..540e792 100644 --- a/default.go +++ b/default.go @@ -19,84 +19,84 @@ func init() { // Use appends the given middleware to the default Mux's middleware stack. See // the documentation for web.Mux.Use for more information. -func Use(middleware interface{}) { +func Use(middleware web.MiddlewareType) { DefaultMux.Use(middleware) } // Insert the given middleware into the default Mux's middleware stack. See the // documentation for web.Mux.Insert for more information. -func Insert(middleware, before interface{}) error { +func Insert(middleware, before web.MiddlewareType) error { return DefaultMux.Insert(middleware, before) } // Abandon removes the given middleware from the default Mux's middleware stack. // See the documentation for web.Mux.Abandon for more information. -func Abandon(middleware interface{}) error { +func Abandon(middleware web.MiddlewareType) error { return DefaultMux.Abandon(middleware) } // Handle adds a route to the default Mux. See the documentation for web.Mux for // more information about what types this function accepts. -func Handle(pattern interface{}, handler interface{}) { +func Handle(pattern web.PatternType, handler web.HandlerType) { DefaultMux.Handle(pattern, handler) } // Connect adds a CONNECT route to the default Mux. See the documentation for // web.Mux for more information about what types this function accepts. -func Connect(pattern interface{}, handler interface{}) { +func Connect(pattern web.PatternType, handler web.HandlerType) { DefaultMux.Connect(pattern, handler) } // Delete adds a DELETE route to the default Mux. See the documentation for // web.Mux for more information about what types this function accepts. -func Delete(pattern interface{}, handler interface{}) { +func Delete(pattern web.PatternType, handler web.HandlerType) { DefaultMux.Delete(pattern, handler) } // Get adds a GET route to the default Mux. See the documentation for web.Mux for // more information about what types this function accepts. -func Get(pattern interface{}, handler interface{}) { +func Get(pattern web.PatternType, handler web.HandlerType) { DefaultMux.Get(pattern, handler) } // Head adds a HEAD route to the default Mux. See the documentation for web.Mux // for more information about what types this function accepts. -func Head(pattern interface{}, handler interface{}) { +func Head(pattern web.PatternType, handler web.HandlerType) { DefaultMux.Head(pattern, handler) } // Options adds a OPTIONS route to the default Mux. See the documentation for // web.Mux for more information about what types this function accepts. -func Options(pattern interface{}, handler interface{}) { +func Options(pattern web.PatternType, handler web.HandlerType) { DefaultMux.Options(pattern, handler) } // Patch adds a PATCH route to the default Mux. See the documentation for web.Mux // for more information about what types this function accepts. -func Patch(pattern interface{}, handler interface{}) { +func Patch(pattern web.PatternType, handler web.HandlerType) { DefaultMux.Patch(pattern, handler) } // Post adds a POST route to the default Mux. See the documentation for web.Mux // for more information about what types this function accepts. -func Post(pattern interface{}, handler interface{}) { +func Post(pattern web.PatternType, handler web.HandlerType) { DefaultMux.Post(pattern, handler) } // Put adds a PUT route to the default Mux. See the documentation for web.Mux for // more information about what types this function accepts. -func Put(pattern interface{}, handler interface{}) { +func Put(pattern web.PatternType, handler web.HandlerType) { DefaultMux.Put(pattern, handler) } // Trace adds a TRACE route to the default Mux. See the documentation for // web.Mux for more information about what types this function accepts. -func Trace(pattern interface{}, handler interface{}) { +func Trace(pattern web.PatternType, handler web.HandlerType) { DefaultMux.Trace(pattern, handler) } // NotFound sets the NotFound handler for the default Mux. See the documentation // for web.Mux.NotFound for more information. -func NotFound(handler interface{}) { +func NotFound(handler web.HandlerType) { DefaultMux.NotFound(handler) } diff --git a/web/mux.go b/web/mux.go index 3908de5..c48ed4b 100644 --- a/web/mux.go +++ b/web/mux.go @@ -5,38 +5,18 @@ import ( ) /* -Mux is an HTTP multiplexer, much like net/http's ServeMux. - -Routes may be added using any of the various HTTP-method-specific functions. -When processing a request, when iterating in insertion order the first route -that matches both the request's path and method is used. - -There are two other differences worth mentioning between web.Mux and -http.ServeMux. First, string patterns (i.e., Sinatra-like patterns) must match -exactly: the "rooted subtree" behavior of ServeMux is not implemented. Secondly, -unlike ServeMux, Mux does not support Host-specific patterns. - -If you require any of these features, remember that you are free to mix and -match muxes at any part of the stack. - -In order to provide a sane API, many functions on Mux take interface{}'s. This -is obviously not a very satisfying solution, but it's probably the best we can -do for now. Instead of duplicating documentation on each method, the types -accepted by those functions are documented here. - -A middleware (the untyped parameter in Use() and Insert()) must be one of the -following types: - - func(http.Handler) http.Handler - - func(c *web.C, http.Handler) http.Handler - -All of the route-adding functions on Mux take two untyped parameters: pattern -and handler. Pattern will be passed to ParsePattern, which takes a web.Pattern, -a string, or a regular expression (more information can be found in the -ParsePattern documentation). Handler must be one of the following types: - - http.Handler - - web.Handler - - func(w http.ResponseWriter, r *http.Request) - - func(c web.C, w http.ResponseWriter, r *http.Request) +Mux is an HTTP multiplexer, much like net/http's ServeMux. It functions as both +a middleware stack and as an HTTP router. + +Middleware provide a great abstraction for actions that must be performed on +every request, such as request logging and authentication. To append, insert, +and remove middleware, you can call the Use, Insert, and Abandon functions +respectively. + +Routes may be added using any of the HTTP verb functions (Get, Post, etc.), or +through the generic Handle function. Goji's routing algorithm is very simple: +routes are processed in the order they are added, and the first matching route +will be executed. Routes match if their HTTP method and Pattern both match. */ type Mux struct { ms mStack @@ -59,7 +39,7 @@ func New() *Mux { return &mux } -// ServeHTTP processes HTTP requests. It make Muxes satisfy net/http.Handler. +// ServeHTTP processes HTTP requests. Satisfies net/http.Handler. func (m *Mux) ServeHTTP(w http.ResponseWriter, r *http.Request) { stack := m.ms.alloc() stack.ServeHTTP(w, r) @@ -67,7 +47,7 @@ func (m *Mux) ServeHTTP(w http.ResponseWriter, r *http.Request) { } // ServeHTTPC creates a context dependent request with the given Mux. Satisfies -// the web.Handler interface. +// the Handler interface. func (m *Mux) ServeHTTPC(c C, w http.ResponseWriter, r *http.Request) { stack := m.ms.alloc() stack.ServeHTTPC(c, w, r) @@ -76,23 +56,21 @@ func (m *Mux) ServeHTTPC(c C, w http.ResponseWriter, r *http.Request) { // Middleware Stack functions -// Append the given middleware to the middleware stack. See the documentation -// for type Mux for a list of valid middleware types. +// Append the given middleware to the middleware stack. // // No attempt is made to enforce the uniqueness of middlewares. It is illegal to // call this function concurrently with active requests. -func (m *Mux) Use(middleware interface{}) { +func (m *Mux) Use(middleware MiddlewareType) { m.ms.Use(middleware) } // Insert the given middleware immediately before a given existing middleware in -// the stack. See the documentation for type Mux for a list of valid middleware -// types. Returns an error if no middleware has the name given by "before." +// the stack. Returns an error if "before" cannot be found in the current stack. // // No attempt is made to enforce the uniqueness of middlewares. If the insertion // point is ambiguous, the first (outermost) one is chosen. It is illegal to // call this function concurrently with active requests. -func (m *Mux) Insert(middleware, before interface{}) error { +func (m *Mux) Insert(middleware, before MiddlewareType) error { return m.ms.Insert(middleware, before) } @@ -102,7 +80,7 @@ func (m *Mux) Insert(middleware, before interface{}) error { // If the name of the middleware to delete is ambiguous, the first (outermost) // one is chosen. It is illegal to call this function concurrently with active // requests. -func (m *Mux) Abandon(middleware interface{}) error { +func (m *Mux) Abandon(middleware MiddlewareType) error { return m.ms.Abandon(middleware) } @@ -110,96 +88,85 @@ func (m *Mux) Abandon(middleware interface{}) error { /* Dispatch to the given handler when the pattern matches, regardless of HTTP -method. See the documentation for type Mux for a description of what types are -accepted for pattern and handler. +method. This method is commonly used to implement sub-routing: an admin application, for instance, can expose a single handler that is attached to the main Mux by -calling Handle("/admin*", adminHandler) or similar. Note that this function +calling Handle("/admin/*", adminHandler) or similar. Note that this function doesn't strip this prefix from the path before forwarding it on (e.g., the -handler will see the full path, including the "/admin" part), but this +handler will see the full path, including the "/admin/" part), but this functionality can easily be performed by an extra middleware layer. */ -func (m *Mux) Handle(pattern interface{}, handler interface{}) { +func (m *Mux) Handle(pattern PatternType, handler HandlerType) { m.rt.handleUntyped(pattern, mALL, handler) } // Dispatch to the given handler when the pattern matches and the HTTP method is -// CONNECT. See the documentation for type Mux for a description of what types -// are accepted for pattern and handler. -func (m *Mux) Connect(pattern interface{}, handler interface{}) { +// CONNECT. +func (m *Mux) Connect(pattern PatternType, handler HandlerType) { m.rt.handleUntyped(pattern, mCONNECT, handler) } // Dispatch to the given handler when the pattern matches and the HTTP method is -// DELETE. See the documentation for type Mux for a description of what types -// are accepted for pattern and handler. -func (m *Mux) Delete(pattern interface{}, handler interface{}) { +// DELETE. +func (m *Mux) Delete(pattern PatternType, handler HandlerType) { m.rt.handleUntyped(pattern, mDELETE, handler) } // Dispatch to the given handler when the pattern matches and the HTTP method is -// GET. See the documentation for type Mux for a description of what types are -// accepted for pattern and handler. +// GET. // // All GET handlers also transparently serve HEAD requests, since net/http will // take care of all the fiddly bits for you. If you wish to provide an alternate // implementation of HEAD, you should add a handler explicitly and place it // above your GET handler. -func (m *Mux) Get(pattern interface{}, handler interface{}) { +func (m *Mux) Get(pattern PatternType, handler HandlerType) { m.rt.handleUntyped(pattern, mGET|mHEAD, handler) } // Dispatch to the given handler when the pattern matches and the HTTP method is -// HEAD. See the documentation for type Mux for a description of what types are -// accepted for pattern and handler. -func (m *Mux) Head(pattern interface{}, handler interface{}) { +// HEAD. +func (m *Mux) Head(pattern PatternType, handler HandlerType) { m.rt.handleUntyped(pattern, mHEAD, handler) } // Dispatch to the given handler when the pattern matches and the HTTP method is -// OPTIONS. See the documentation for type Mux for a description of what types -// are accepted for pattern and handler. -func (m *Mux) Options(pattern interface{}, handler interface{}) { +// OPTIONS. +func (m *Mux) Options(pattern PatternType, handler HandlerType) { m.rt.handleUntyped(pattern, mOPTIONS, handler) } // Dispatch to the given handler when the pattern matches and the HTTP method is -// PATCH. See the documentation for type Mux for a description of what types are -// accepted for pattern and handler. -func (m *Mux) Patch(pattern interface{}, handler interface{}) { +// PATCH. +func (m *Mux) Patch(pattern PatternType, handler HandlerType) { m.rt.handleUntyped(pattern, mPATCH, handler) } // Dispatch to the given handler when the pattern matches and the HTTP method is -// POST. See the documentation for type Mux for a description of what types are -// accepted for pattern and handler. -func (m *Mux) Post(pattern interface{}, handler interface{}) { +// POST. +func (m *Mux) Post(pattern PatternType, handler HandlerType) { m.rt.handleUntyped(pattern, mPOST, handler) } // Dispatch to the given handler when the pattern matches and the HTTP method is -// PUT. See the documentation for type Mux for a description of what types are -// accepted for pattern and handler. -func (m *Mux) Put(pattern interface{}, handler interface{}) { +// PUT. +func (m *Mux) Put(pattern PatternType, handler HandlerType) { m.rt.handleUntyped(pattern, mPUT, handler) } // Dispatch to the given handler when the pattern matches and the HTTP method is -// TRACE. See the documentation for type Mux for a description of what types are -// accepted for pattern and handler. -func (m *Mux) Trace(pattern interface{}, handler interface{}) { +// TRACE. +func (m *Mux) Trace(pattern PatternType, handler HandlerType) { m.rt.handleUntyped(pattern, mTRACE, handler) } -// Set the fallback (i.e., 404) handler for this mux. See the documentation for -// type Mux for a description of what types are accepted for handler. +// Set the fallback (i.e., 404) handler for this mux. // // As a convenience, the context environment variable "goji.web.validMethods" // (also available as the constant ValidMethodsKey) will be set to the list of // HTTP methods that could have been routed had they been provided on an // otherwise identical request. -func (m *Mux) NotFound(handler interface{}) { +func (m *Mux) NotFound(handler HandlerType) { m.rt.notFound = parseHandler(handler) } diff --git a/web/pattern.go b/web/pattern.go index c277ae1..3b64385 100644 --- a/web/pattern.go +++ b/web/pattern.go @@ -36,35 +36,12 @@ ParsePattern is used internally by Goji to parse route patterns. It is exposed publicly to make it easier to write thin wrappers around the built-in Pattern implementations. -Although its parameter has type interface{}, ParsePattern only accepts arguments -of three types: - - web.Pattern, which is passed through - - string, which is interpreted as a Sinatra-like URL pattern. In - particular, the following syntax is recognized: - - a path segment starting with with a colon will match any - string placed at that position. e.g., "/:name" will match - "/carl", binding "name" to "carl". - - a pattern ending with "/*" will match any route with that - prefix. For instance, the pattern "/u/:name/*" will match - "/u/carl/" and "/u/carl/projects/123", but not "/u/carl" - (because there is no trailing slash). In addition to any names - bound in the pattern, the special key "*" is bound to the - unmatched tail of the match, but including the leading "/". So - for the two matching examples above, "*" would be bound to "/" - and "/projects/123" respectively. - - regexp.Regexp, which is assumed to be a Perl-style regular expression - that is anchored on the left (i.e., the beginning of the string). If - your regular expression is not anchored on the left, a - hopefully-identical left-anchored regular expression will be created - and used instead. Named capturing groups will bind URLParams of the - same name; unnamed capturing groups will be bound to the variables - "$1", "$2", etc. - ParsePattern fatally exits (using log.Fatalf) if it is passed a value of an -unexpected type. It is the caller's responsibility to ensure that ParsePattern -is called in a type-safe manner. +unexpected type (see the documentation for PatternType for a list of which types +are accepted). It is the caller's responsibility to ensure that ParsePattern is +called in a type-safe manner. */ -func ParsePattern(raw interface{}) Pattern { +func ParsePattern(raw PatternType) Pattern { switch v := raw.(type) { case Pattern: return v diff --git a/web/web.go b/web/web.go index 81a2bae..95b25a4 100644 --- a/web/web.go +++ b/web/web.go @@ -80,7 +80,7 @@ middleware layers and given to the final request handler. type C struct { // URLParams is a map of variables extracted from the URL (typically // from the path portion) during routing. See the documentation for the - // URL Pattern you are using (or the documentation for ParsePattern for + // URL Pattern you are using (or the documentation for PatternType for // the case of standard pattern types) for more information about how // variables are extracted and named. URLParams map[string]string @@ -98,7 +98,7 @@ type Handler interface { } // HandlerFunc is similar to net/http's http.HandlerFunc, but supports a context -// object. Implements both http.Handler and web.Handler. +// object. Implements both http.Handler and Handler. type HandlerFunc func(C, http.ResponseWriter, *http.Request) // ServeHTTP implements http.Handler, allowing HandlerFunc's to be used with @@ -112,3 +112,60 @@ func (h HandlerFunc) ServeHTTP(w http.ResponseWriter, r *http.Request) { func (h HandlerFunc) ServeHTTPC(c C, w http.ResponseWriter, r *http.Request) { h(c, w, r) } + +/* +PatternType is the type denoting Patterns and types that Goji internally +converts to Pattern (via the ParsePattern function). In order to provide an +expressive API, this type is an alias for interface{} (that is named for the +purposes of documentation), however only the following concrete types are +accepted: + - types that implement Pattern + - string, which is interpreted as a Sinatra-like URL pattern. In + particular, the following syntax is recognized: + - a path segment starting with with a colon will match any + string placed at that position. e.g., "/:name" will match + "/carl", binding "name" to "carl". + - a pattern ending with "/*" will match any route with that + prefix. For instance, the pattern "/u/:name/*" will match + "/u/carl/" and "/u/carl/projects/123", but not "/u/carl" + (because there is no trailing slash). In addition to any names + bound in the pattern, the special key "*" is bound to the + unmatched tail of the match, but including the leading "/". So + for the two matching examples above, "*" would be bound to "/" + and "/projects/123" respectively. + Unlike http.ServeMux's patterns, string patterns support neither the + "rooted subtree" behavior nor Host-specific routes. Users who require + either of these features are encouraged to compose package http's mux + with the mux provided by this package. + - regexp.Regexp, which is assumed to be a Perl-style regular expression + that is anchored on the left (i.e., the beginning of the string). If + your regular expression is not anchored on the left, a + hopefully-identical left-anchored regular expression will be created + and used instead. + + Capturing groups will be converted into bound URL parameters in + URLParams. If the capturing group is named, that name will be used; + otherwise the special identifiers "$1", "$2", etc. will be used. +*/ +type PatternType interface{} + +/* +HandlerType is the type of Handlers and types that Goji internally converts to +Handler. In order to provide an expressive API, this type is an alias for +interface{} (that is named for the purposes of documentation), however only the +following concrete types are accepted: + - types that implement http.Handler + - types that implement Handler + - func(w http.ResponseWriter, r *http.Request) + - func(c web.C, w http.ResponseWriter, r *http.Request) +*/ +type HandlerType interface{} + +/* +MiddlewareType is the type of Goji middleware. In order to provide an expressive +API, this type is an alias for interface{} (that is named for the purposes of +documentation), however only the following concrete types are accepted: + - func(http.Handler) http.Handler + - func(c *web.C, http.Handler) http.Handler +*/ +type MiddlewareType interface{}