Handle routing by calling Next.js code (#3446)
This implements routing by using a-yet-to-be-implemented API exposed by
Next.js. The API follows something similar to:
```typescript
type MakeResolver = (config: NextConfig) => Resolver;
type Resolver = (IncomingMessage, ServerResponse) => Promise<void> | void;
import { makeResolver } from "next/dist/...";
const resolver = makeResolver(nextConfig as object);
// Later, once we have a request we'd like to route:
// We don't care what the promise resolved to, we just want it to settle.
await resolver(req, res);
```
The resolver can do 1 of 3 things with this:
1. ~~Return a redirect response~~ Removed
2. Return a rewrite response
3. Stream a middleware response
> ~~1. Return a redirect response~~
<details>
First, ensure a `x-nextjs-route-result: 1` header is present on the
response. Then return a JSON encoded body:
```typescript
{
url: string,
statusCode: u16,
headers: Record<string, string>
isRedirect: true
}
```
The Rust server will then respond with a redirect using to the
appropriate location.
</details>
> 2. Return a rewrite response
First, ensure a `x-nextjs-route-result: 1` header is present on the
response. Then return a JSON encoded body:
```typescript
{
url: string,
headers: Record<string, string>
}
```
The Rust server will use this updated URL to request content from our
handlers.
> 3. Stream a middleware response
Ensure `x-nextjs-route-result` header **is not present** on the
response. All headers will be sent back, and the body will be streamed
back to the browser.
- - -
TODO:
- ~~Do `headers` actually matter to a `redirect`~~ Yes?
- ~~Does `statusCode` actually matter to a `rewrite`?~~ No
- ~~We can't handle streaming body responses yet.~~ Mocked by buffering.
Fixes WEB-228
Co-authored-by: JJ Kasper <jj@jjsweb.site>