import "darlinggo.co/trout/v2"
trout
is an opinionated router that is biased towards RESTful services. It
does not rely on global, mutable state and has no dependencies. trout
also
goes out of its way to enable servers to correctly respond with an
http.StatusMethodNotAllowed
error instead of an http.StatusNotFound
error
when the endpoint can be matched but is not configured to respond to the HTTP
method the request used. It also takes pains to make sure that OPTIONS
requests can be fulfilled, by making the methods an endpoint is configured with
available to the http.Handler
. In general, the over-arching goals of trout
are:
http.Handler
s to get to information that will help
them do their jobs.Docs can be found on GoDoc.org.
Creating a router is straight-forward: the trout.Router
type’s zero value is
an acceptable router with no endpoints. Adding endpoints is a matter of calling
methods on the variable.
var router trout.Router
router.Endpoint("/posts/{slug}/comments/{id}").Handler(postsHandler)
That router will now match the URL /posts/WHATEVERYOUTYPE/comments/123
. You
can replace WHATEVERYOUTYPE
and 123
with any string that doesn’t contain a
/
. All requests matching this pattern will be handled by postsHandler
.
The Endpoint
method is basic: it accepts a string to match the URL against.
Strings get broken down into path elements; path elements are split by the /
character. Path elements come in two flavours: static and dynamic. A static
path element will match the path element text exactly; posts
and comments
in the example above are static path elements. Dynamic path elements are just
placeholders; they match any text at all; WHATEVERYOUTYPE
and 123
are
dynamic resources in the example above.
The Endpoint
method returns a trout.Endpoint
, which can have an
http.Handler
associated with it by calling its Handler
method, and passing
the http.Handler
you want to use as the handler for requests that match the
endpoint.
var router trout.Router
router.Endpoint("/posts/{slug}").Methods("GET", "POST").Handler(postsHandler)
The example above associates postsHandler
with the /posts/{slug}
endpoint,
but only for requests made using the GET
or POST
HTTP method. All other
requests will return an http.StatusMethodNotAllowed
error. Any number of
methods can be passed to the Methods
method.
Now that a handler has been matched, we need to get the values that filled the
dynamic path element placeholders in the URL. The trout.RequestVars
helper
function can be used to return the values the were used.
Variables in trout
are passed as request headers. All the trout
parameters
are set as Trout-Param-RESOURCETEXT
, where RESOURCETEXT
is the text you
entered between {
and }
in the endpoint. For example,
/posts/{slug}/comments/{id}
would have Trout-Param-Slug
and
Trout-Param-Id
set in the request headers. trout.RequestVars(r)
returns all
headers that begin with Trout-Param-
, and strip that prefix, returning an
http.Header
object. So calling trout.RequestVars(r)
in our example would
return an http.Header
object with keys for Id
and Slug
.
In the event that the same text is reused as a dynamic resource in multiple
parts of the endpoint, both values will still be available, because each key in
an http.Header
corresponds to a slice of values. For example, if the endpoint
is /posts/{id}/comments/{id}
, the http.Header
returned from
trout.RequestVars(r)
will contain just a single Id
key, and it would hold
two values. The values will always be in the same order they were in in the
URL.
By default, trout
will respond with http.StatusNotFound
when no endpoint
can fulfill the request, and http.StatusMethodNotAllowed
when none of the
endpoints that can fulfill the request are configured to respond to the HTTP
method used. These defaults will also write a default error message as the
response body. Furthermore, for http.StatusMethodNotAllowed
responses, the
Allow
header will be set on the response, containing the methods the endpoint
is configured to respond to.
Sometimes, however, you want something besides these defaults. In that case,
you can set the Handle404
property on your router to the http.Handler
you
want to use for requests where the endpoint can’t be found, and Handle405
to
the http.Handler
you want to use for requests where an endpoint is matched,
but isn’t configured to respond to the HTTP method used.
trout
sets two extra request headers when routing:
Trout-Timer
is set to the number of nanoseconds it took to route the
request. This allows you to monitor how much of your response time is spent
on routing.Trout-Pattern
is set to the endpoint text that the request matched, which
makes it easier to determine which endpoint resulted in the handler being
called. This is particularly useful when using placeholders, as the value
will always be the same, no matter what text is placed in the placeholder.
This makes it easier to monitor at an endpoint-granularity.There are times when multiple endpoints can be used to serve the same request.
A simplified example would be /version
and /{id}
both being endpoints on a
router. When a request is made to /version
, which should be used?
Trout resolves this by trying to find the most specific route that can handle the request. Endpoints get a score based on how many placeholders exist in the endpoint, and how close to the beginning of the endpoint they are. The more placeholders an endpoint has, and the earlier in the endpoint they are, the lower the score is. The highest scoring endpoint serves the request.