Create a reverse proxy server
twifkak opened this issue · comments
Create a reverse proxy server in Rust, similar to Web Packager Server as a wrapper around the sxg_rs
library. This could be run as a typical server or as a service on Google Cloud Run (see docs).
Of the available libraries for implementing a reverse proxy, hyper-reverse-proxy seems like the most accurate (removing hop-by-hop headers including those in Connection
, etc.). It's built on top of Hyper so it supports a bunch of things (notably, streamed responses).
We should also set X-Forwarded-Host
, Forwarded: host=
, and Via
in outgoing requests; not done by that lib.
We'll need some storage/sync mechanism for ACME & OCSP.
We could use routerify for the non-proxied URLs (cert-chain+cbor and validity), but we'll need to test well that it avoids inner URL != outer URL bugs that plagued several Go routers. Alternatively, we could build our own simple router using something like RegexSet or trie_rs
Alternatives considered include:
Another server exists in playground/
but it would take more effort to productionize (e.g. add all the above + the features in hyper-reverse-proxy), and it would add node as a dependency. Would be nice to have something that runs in Rust only.
To start with, I'll implement it with file storage, for the basic "run a server on a VM" case.
I split off #276 for Google Cloud Run support.
@antiphoton @quangIO It appears hyper requires async fns to implement Send
for multithreading (see details). SxgWorker fns do not implement Send
for a variety of reasons.
js_sys
doesn't implementSend
.dyn
doesn't implementSend
unless specified (dyn Foo + Send
) -- which would breakjs_sys
support.- Probably more reasons.
Options to solve this:
block_on()
SxgWorker fns. Presumably this eliminates parallelism -- perhaps that's OK for computations that aren't I/O bound?- Remove
dyn
from the code base; use type parameters instead. Simplest conceptually, but adds line noise and brittleness -- whenever we add a new type parameter, we have to update all callers. - Change types like
Runtime
to traits withSend
and non-Send
impls. Least line noise, but a bit of duplication.
I'm leaning towards 3 where possible, falling back to 2 for less pervasive types. However, I'm not an expert in Rust. Would appreciate your opinions. It seems @quangIO was considering 2 in #278; what are the implications for WasmWorker?
js_sys::Fucntion
does not implement Send
, hence Fetcher
, Signer
, and Storage
do not implement Send
, hence struct Runtime
does not implement Send
.
Box<dyn Fetcher>
, Arc<dyn Fetcher>
and Arc<F: Fetcher>
can't implement Send
as long as Fetcher
does not implement Send
.
I would suggest constructing Runtime
inside hyper
's handler function, so that the Runtime
no longer need to be sent between threads.
I would suggest constructing
Runtime
insidehyper
's handler function, so that theRuntime
no longer need to be sent between threads.
I'm already attempting to construct it inside the closure passed to service_fn
, and that's resulting in the "Future is not Send" error [1]. I'm not positive, but I believe the multithreading runtime needs each individual task to implement Send
because it reserves the right to reschedule it on a different thread; see Tokio docs.
[1] In particular, it's not the call to service_fn
but to server::Builder::serve
that triggers the error.
I'll give it another day of effort, but if that fails, I may just try spawn_blocking
and test that it can handle concurrent requests. I note that warp calls the handler synchronously and warp probably knows what it's doing.
I found myself wanting for language features:
- Constraint kinds so I didn't have to duplicate code between
RuntimeSend
andRuntimeNoSend
impls. - Type-changing struct update since our tests use the struct update syntax quite heavily, but that won't work if we get rid of
dyn
.
In WebAssembly applications with JsValue
, Runtime
is !Send
; in pure Rust applications, Runtime
is Send
.
We may consider using the conditional compilation in Rust (like pre-defined macro in C++), and declare Runtime
, Fetcher
, Signer
as !Send
only when the wasm
feature is enabled in sxg-rs
(like this).
Oh, really good idea.
TODO: Refactor this pattern from #314 into a macro in utils:
#[cfg_attr(feature = "wasm", async_trait(?Send))]
#[cfg_attr(not(feature = "wasm"), async_trait)]
Abandoning the attribute macro idea, as the maintenance overhead is not worth the benefit. It would require an additional proc-macro
crate.