Instead of using struct embedding to build web.Mux, start moving towards
explicit mappings. This doesn't actually change the public API of
web.Mux, but feels a little cleaner to me.
The longer-term thing here is to get rid of the functions defined on
Muxes in the public documentation that are defined on "rt *Mux", which
is just plain ugly.
It turns out WriterProxy is pretty generally useful, especially when
defining custom http loggers. Expose it in a util package so that other
packages can use it.
If you're manipulating your middleware stack concurrently with active
requests you're probably doing something wrong, and it's not worth
either the complexity or runtime cost to support you hitting yourself.
We can probably take this principle a bit further and disallow mutating
the middleware stack after any requests have been made (which will
eliminate even more complexity) but that can be a project for another
day.
App Engine disallows package unsafe. As a workaround for the (unsafe)
RCU atomic pointer shenanigans we pull in order to avoid taking a lock
in the hot routing path, let's just grab the lock. Honestly, I doubt
anyone will notice anyways, especially considering the fact that App
Engine is single-threaded anyways.
Fixes#52.
Previously, a state machine invalidation could have raced against an
in-flight routing attempt: if the invalidation occured after the routing
attempt had already completed its nil-check (choosing not to compile a
new state machine) but before the state machine was atomically loaded to
perform routing, the routing goroutine would begin to panic from
dereferencing nil.
The meat of this change is that we now return the state machine that we
compiled (while still holding the lock), and we only ever interact with
the state machine through atomic pointer loads.
Many common panic values, e.g. nil pointer dereferences, don't print
very well under "%#v", emitting something like
"runtime.errorCString{cstr:0x54b2a4}" or similar.
If WriteHeader is called multiple times on a http.ResponseWriter, the
first status is the one that is used, not the last. Fix the wrapped
writer to reflect this fact.
For whatever reason, Go insisted on loading rm.sm[i] in several chunks,
even though it could be loaded in a single 64-bit block. Instead, let's
reorder our loads to minimize the amount of memory we're uselessly
moving around.
This gives us about a 15% perf boost in
github.com/julienschmidt/go-http-routing-benchmark's
BenchmarkGoji_StaticAll, and questionable benefits (i.e., not
distinguishable from noise but certainly no worse) on Goji's own
benchmarks.
Change the per-process nonce part of the request ID from 8 characters to
10, and wrap the entire thing in a retry loop so you can never get an
"unlucky" panic. I know this will "never" happen in practice, but it
doesn't hurt to make sure we never, ever have any collisions, and never,
ever have any runtime panics.
It's also worth documenting the math ("math") I used to calculate the
numbers here.
Previously, we would keep the URLParams / Env associated with a cStack
around until the next request flushed them. However, this might cause
either of these maps to stick around for much longer than they ought to,
potentially keeping references to many, many objects.
Instead, clear out the saved context on every release.
The "dryrun" parameter on Pattern.Match was kind of ugly and made for an
exceedingly mediocre public interface. Instead, split its functionality
in two: the previous "dryrun" behavior now lives in the Match method,
and Patterns now actually mutate state when Run is called.
The code on the backend is of course still the same (for now), but at
least the interface is a little nicer.
Previously, the middleware stack passed the router a C, but this was
both odd semantically (a pattern which mutated the environment might see
a *different* environment) and bad for perf: it cost us an allocation.
Now we only pass around *C's internally.
Importantly ("importantly"), this gets us down to 0 allocations for the
static routing case, and one allocation (the URLParams map) for the
normal routing case.
Let's just hope the GC does its job correctly and don't try to help it
out. This case is probably triggered very infrequently since most people
set up their middleware before they accept a single request, and it's
worth about 100ns of perf on the common case for us if we get rid of the
defer.
The fast routing diff introduced a regression with how method sets were
calculated for routes that did not match. This fixes that behavior, as
well as making routing considerably more memory-efficient (and therefore
CPU-efficient too) for the case in which many routes share a prefix.
Swap out the naive "try all the routes in order" router with a "compile
a trie down to bytecode" router. It's a ton faster, while providing all
the same semantics.
See the documentation at the top of web/fast_router.go for more.
Partially sort the routes on insertion. We're doing this so we can do
more efficient things to routes later.
The sorting rules are a bit subtle since we aren't allowed to rearrange
routes in a way that would cause the semantics to differ from the dumb
linear scan.
This middleware allows you to override a http.Request's RemoteAddr with
a value derived from either the X-Forwarded-For or X-Real-IP headers.
Fixes#12.
Provide a standard middleware to set c.Env. Don't include it in the
default stack, however, since the RequestID middleware will end up
allocating Env anyways.
Fixes#11
httptest was adding an extra flag, which was sort of ugly. Instead,
reimplement the parts of its functionality we were using. Bonus: due to
specialization, it's now a bit more efficient as well!