Skip to content

Commit 775cbfb

Browse files
authored
Merge pull request #52 from thedodd/33-proxy
Implement proxy system
2 parents 08248f6 + 66e7c5d commit 775cbfb

12 files changed

+767
-114
lines changed

CHANGELOG.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,13 @@ This changelog follows the patterns described here: https://keepachangelog.com/e
44

55
## Unreleased
66

7+
## 0.5.0
8+
### added
9+
- Added support for proxying requests to arbitrary HTTP backends.
10+
- This includes a basic CLI based config.
11+
- This also includes a more robust `Trunk.toml` config which allows for specifying multiple proxies.
12+
- Added a `trunk config show` subcommand. This command will pretty-print Trunk's final runtime config based on any config file & env vars. This can be useful for debugging & testing.
13+
714
## 0.4.0
815
### added
916
- In addition to CLI arguments and options, Trunk now supports layered configuration via `Trunk.toml` & environment variables.

CONTRIBUTING.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,12 @@ contributing
22
============
33
We are all living beings, and what is most important is that we respect each other and work together. If you can not uphold this simple standard, then your contributions are not welcome.
44

5+
## hacking
6+
Just a few simple items to keep in mind as you hack.
7+
8+
- Pull request early and often. This helps to let others know what you are working on. **Please use Github's Draft PR mechansim** if your PR is not yet ready for review.
9+
- Remember to update the `CHANGELOG.md` once you believe your work is nearing completion.
10+
511
## linting
612
We are using clippy & rustfmt. Clippy is SO GREAT! Rustfmt ... has a lot more growing to do; however, we are using it for uniformity.
713

Cargo.lock

Lines changed: 514 additions & 104 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[package]
22
name = "trunk"
3-
version = "0.4.0"
3+
version = "0.5.0"
44
edition = "2018"
55
description = "Build, bundle & ship your Rust WASM application to the web."
66
license = "MIT/Apache-2.0"
@@ -19,6 +19,7 @@ console = "0.12.0"
1919
envy = "0.4.1"
2020
fs_extra = "1.2.0"
2121
futures = "0.3.5"
22+
http-types = "2.4.0"
2223
indicatif = "0.15.0"
2324
nipper = "0.1.8"
2425
notify = "4.0.15"
@@ -27,7 +28,8 @@ sass-rs = "0.2.2"
2728
seahash = "4.0.1"
2829
serde = { version="1", features=["derive"] }
2930
structopt = "0.3.16"
30-
tide = "0.13.0"
31+
surf = "2.0.0-alpha.5"
32+
tide = { version="0.13.0", features=["unstable"] }
3133
toml = "0.5.6"
3234

3335
[profile.release]

README.md

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -111,6 +111,29 @@ Trunk environment variables mirror the `Trunk.toml` config schema. All Trunk env
111111
### cli arguments & options
112112
The final configuration layer is the CLI itself. Any arguments / options provided on the CLI will take final precedence over any other config layer.
113113

114+
## proxy
115+
Trunk ships with a built-in proxy which can be enabled when running `trunk serve`. There are two ways to configure the proxy, each discussed below. All Trunk proxies will transparently pass along the request body, headers, and query parameters to the proxy backend.
116+
117+
### proxy cli flags
118+
The `trunk serve` command accepts two proxy related flags.
119+
120+
`--proxy-backend` specifies the URL of the backend server to which requests should be proxied. The URI segment of the given URL will be used as the path on the Trunk server to handle proxy requests. E.G., `trunk serve --proxy-backend=http://localhost:9000/api/` will proxy any requests received on the path `/api/` to the server listening at `http://localhost:9000/api/`. Further path segments or query parameters will be seamlessly passed along.
121+
122+
`--proxy-rewrite` specifies an alternative URI on which the Trunk server is to listen for proxy requests. Any requests received on the given URI will be rewritten to match the URI of the proxy backend, effectively stripping the rewrite prefix. E.G., `trunk serve --proxy-backend=http://localhost:9000/ --proxy-rewrite=/api/` will proxy any requests received on `/api/` over to `http://localhost:9000/` with the `/api/` prefix stripped from the request, while everything following the `/api/` prefix will be left unchanged.
123+
124+
### config file
125+
The `Trunk.toml` config file accepts multiple `[[proxy]]` sections, which allows for multiple proxies to be configured. Each section requires at least the `backend` field, and optionally accepts the `rewrite` field, both corresponding to the `--proxy-*` CLI flags discussed above.
126+
127+
As it is with other Trunk config, a proxy declared via CLI will take final precedence and will cause any config file proxies to be ignored, even if there are multiple proxies declared in the config file.
128+
129+
The following is a snippet from the `Trunk.toml` file in this repo:
130+
131+
```toml
132+
[[proxy]]
133+
rewrite = "/api/v1/"
134+
backend = "http://localhost:9000/"
135+
```
136+
114137
## contributing
115138
Anyone and everyone is welcome to contribute! Please review the [CONTRIBUTING.md](./CONTRIBUTING.md) document for more details. The best way to get started is to find an open issue, and then start hacking on implementing it. Letting other folks know that you are working on it, and sharing progress is a great approach. Open pull requests early and often, and please use Github's draft pull request feature.
116139

Trunk.toml

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,3 +27,19 @@ open = false
2727
dist = "dist"
2828
# Optionally perform a cargo clean.
2929
cargo = false
30+
31+
## proxy
32+
# Proxies are optional, and default to `None`.
33+
# Proxies are only run as part of the `trunk serve` command.
34+
35+
[[proxy]]
36+
# This proxy example has a backend and a rewrite field. Requests received on `rewrite` will be
37+
# proxied to the backend after rewriting the `rewrite` prefix to the `backend`'s URI prefix.
38+
# E.G., `/api/v1/resource/x/y/z` -> `/resource/x/y/z`
39+
rewrite = "/api/v1/"
40+
backend = "http://localhost:9000/"
41+
42+
[[proxy]]
43+
# This proxy specifies only the backend, which is the only required field. In this example,
44+
# request URIs are not modified when proxied.
45+
backend = "http://localhost:9000/api/v2/"

src/cmd/config.rs

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
use std::path::PathBuf;
2+
3+
use anyhow::Result;
4+
use structopt::StructOpt;
5+
6+
use crate::config::ConfigOpts;
7+
8+
/// Trunk config controls.
9+
#[derive(Clone, Debug, StructOpt)]
10+
#[structopt(name = "config")]
11+
pub struct Config {
12+
#[structopt(subcommand)]
13+
action: ConfigSubcommands,
14+
}
15+
16+
impl Config {
17+
pub async fn run(self, config: Option<PathBuf>) -> Result<()> {
18+
// NOTE WELL: if we ever add additional subcommands, refactor this to match the pattern
19+
// used in main, which is much more scalable. This is faster to code, and will not force
20+
// incompatibility when new commands are added.
21+
match self.action {
22+
ConfigSubcommands::Show => {
23+
let cfg = ConfigOpts::full(config).await?;
24+
println!("{:#?}", cfg);
25+
}
26+
}
27+
Ok(())
28+
}
29+
}
30+
31+
#[derive(Clone, Debug, StructOpt)]
32+
enum ConfigSubcommands {
33+
/// Show Trunk's current config pre-CLI.
34+
Show,
35+
}

src/cmd/mod.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
pub mod build;
22
pub mod clean;
3+
pub mod config;
34
pub mod serve;
45
pub mod watch;

src/config.rs

Lines changed: 79 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ use std::path::PathBuf;
88
use std::sync::Arc;
99

1010
use anyhow::Result;
11+
use http_types::Url;
1112
use serde::Deserialize;
1213
use structopt::StructOpt;
1314

@@ -69,15 +70,40 @@ pub struct RtcServe {
6970
pub port: u16,
7071
/// Open a browser tab once the initial build is complete.
7172
pub open: bool,
73+
/// A URL to which requests will be proxied.
74+
pub proxy_backend: Option<Url>,
75+
/// The URI on which to accept requests which are to be rewritten and proxied to backend.
76+
pub proxy_rewrite: Option<String>,
77+
/// Any proxies configured to run along with the server.
78+
pub proxies: Option<Vec<ConfigOptsProxy>>,
7279
}
7380

74-
impl From<(CargoMetadata, ConfigOptsBuild, ConfigOptsWatch, ConfigOptsServe)> for RtcServe {
75-
fn from((manifest, build_opts, watch_opts, opts): (CargoMetadata, ConfigOptsBuild, ConfigOptsWatch, ConfigOptsServe)) -> Self {
81+
impl
82+
From<(
83+
CargoMetadata,
84+
ConfigOptsBuild,
85+
ConfigOptsWatch,
86+
ConfigOptsServe,
87+
Option<Vec<ConfigOptsProxy>>,
88+
)> for RtcServe
89+
{
90+
fn from(
91+
(manifest, build_opts, watch_opts, opts, proxies): (
92+
CargoMetadata,
93+
ConfigOptsBuild,
94+
ConfigOptsWatch,
95+
ConfigOptsServe,
96+
Option<Vec<ConfigOptsProxy>>,
97+
),
98+
) -> Self {
7699
let watch = Arc::new(RtcWatch::from((manifest, build_opts, watch_opts)));
77100
Self {
78101
watch,
79102
port: opts.port.unwrap_or(8080),
80103
open: opts.open,
104+
proxy_backend: opts.proxy_backend,
105+
proxy_rewrite: opts.proxy_rewrite,
106+
proxies,
81107
}
82108
}
83109
}
@@ -142,6 +168,14 @@ pub struct ConfigOptsServe {
142168
#[structopt(long)]
143169
#[serde(default)]
144170
pub open: bool,
171+
/// A URL to which requests will be proxied [default: None]
172+
#[structopt(long = "proxy-backend")]
173+
#[serde(default)]
174+
pub proxy_backend: Option<Url>,
175+
/// The URI on which to accept requests which are to be rewritten and proxied to backend [default: None]
176+
#[structopt(long = "proxy-rewrite")]
177+
#[serde(default)]
178+
pub proxy_rewrite: Option<String>,
145179
}
146180

147181
/// Config options for the serve system.
@@ -156,13 +190,30 @@ pub struct ConfigOptsClean {
156190
pub cargo: bool,
157191
}
158192

193+
/// Config options for building proxies.
194+
///
195+
/// NOTE WELL: this configuration type is different from the others inasmuch as it is only used
196+
/// when parsing the `Trunk.toml` config file. It is not intended to be configured via CLI or env vars.
197+
#[derive(Clone, Debug, Deserialize)]
198+
pub struct ConfigOptsProxy {
199+
/// The URL of the backend to which requests are to be proxied.
200+
pub backend: Url,
201+
/// An optional URI prefix which is to be used as the base URI for proxying requests, which
202+
/// defaults to the URI of the backend.
203+
///
204+
/// When a value is specified, requests received on this URI will have this URI segment replaced
205+
/// with the URI of the `backend`.
206+
pub rewrite: Option<String>,
207+
}
208+
159209
/// A model of all potential configuration options for the Trunk CLI system.
160210
#[derive(Clone, Debug, Default, Deserialize)]
161211
pub struct ConfigOpts {
162212
pub build: Option<ConfigOptsBuild>,
163213
pub watch: Option<ConfigOptsWatch>,
164214
pub serve: Option<ConfigOptsServe>,
165215
pub clean: Option<ConfigOptsClean>,
216+
pub proxy: Option<Vec<ConfigOptsProxy>>,
166217
}
167218

168219
impl ConfigOpts {
@@ -198,7 +249,13 @@ impl ConfigOpts {
198249
let watch_opts = serve_layer.watch.unwrap_or_default();
199250
let serve_opts = serve_layer.serve.unwrap_or_default();
200251
let manifest = CargoMetadata::new(&build_opts.manifest).await?;
201-
Ok(Arc::new(RtcServe::from((manifest, build_opts, watch_opts, serve_opts))))
252+
Ok(Arc::new(RtcServe::from((
253+
manifest,
254+
build_opts,
255+
watch_opts,
256+
serve_opts,
257+
serve_layer.proxy,
258+
))))
202259
}
203260

204261
/// Extract the runtime config for the clean system based on all config layers.
@@ -209,6 +266,11 @@ impl ConfigOpts {
209266
Ok(Arc::new(RtcClean::from(clean_opts)))
210267
}
211268

269+
/// Return the full configuration based on config file & environment variables.
270+
pub async fn full(config: Option<PathBuf>) -> Result<Self> {
271+
Self::file_and_env_layers(config)
272+
}
273+
212274
fn cli_opts_layer_build(cli: ConfigOptsBuild, cfg_base: Self) -> Self {
213275
let opts = ConfigOptsBuild {
214276
target: cli.target,
@@ -222,6 +284,7 @@ impl ConfigOpts {
222284
watch: None,
223285
serve: None,
224286
clean: None,
287+
proxy: None,
225288
};
226289
Self::merge(cfg_base, cfg_build)
227290
}
@@ -233,6 +296,7 @@ impl ConfigOpts {
233296
watch: Some(opts),
234297
serve: None,
235298
clean: None,
299+
proxy: None,
236300
};
237301
Self::merge(cfg_base, cfg)
238302
}
@@ -241,12 +305,15 @@ impl ConfigOpts {
241305
let opts = ConfigOptsServe {
242306
port: cli.port,
243307
open: cli.open,
308+
proxy_backend: cli.proxy_backend,
309+
proxy_rewrite: cli.proxy_rewrite,
244310
};
245311
let cfg = ConfigOpts {
246312
build: None,
247313
watch: None,
248314
serve: Some(opts),
249315
clean: None,
316+
proxy: None,
250317
};
251318
Self::merge(cfg_base, cfg)
252319
}
@@ -261,6 +328,7 @@ impl ConfigOpts {
261328
watch: None,
262329
serve: None,
263330
clean: Some(opts),
331+
proxy: None,
264332
};
265333
Self::merge(cfg_base, cfg)
266334
}
@@ -293,6 +361,7 @@ impl ConfigOpts {
293361
watch: Some(watch),
294362
serve: Some(serve),
295363
clean: Some(clean),
364+
proxy: None,
296365
})
297366
}
298367

@@ -325,6 +394,8 @@ impl ConfigOpts {
325394
(None, None) => None,
326395
(Some(val), None) | (None, Some(val)) => Some(val),
327396
(Some(l), Some(mut g)) => {
397+
g.proxy_backend = g.proxy_backend.or(l.proxy_backend);
398+
g.proxy_rewrite = g.proxy_rewrite.or(l.proxy_rewrite);
328399
g.port = g.port.or(l.port);
329400
// NOTE: this can not be disabled in the cascade.
330401
if l.open {
@@ -345,6 +416,11 @@ impl ConfigOpts {
345416
Some(g)
346417
}
347418
};
419+
greater.proxy = match (lesser.proxy.take(), greater.proxy.take()) {
420+
(None, None) => None,
421+
(Some(val), None) | (None, Some(val)) => Some(val),
422+
(Some(_), Some(g)) => Some(g), // No meshing/merging. Only take the greater value.
423+
};
348424
greater
349425
}
350426
}

src/main.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ mod build;
22
mod cmd;
33
mod common;
44
mod config;
5+
mod proxy;
56
mod serve;
67
mod watch;
78

@@ -38,6 +39,7 @@ impl Trunk {
3839
TrunkSubcommands::Clean(inner) => inner.run(self.config).await,
3940
TrunkSubcommands::Serve(inner) => inner.run(self.config).await,
4041
TrunkSubcommands::Watch(inner) => inner.run(self.config).await,
42+
TrunkSubcommands::Config(inner) => inner.run(self.config).await,
4143
}
4244
}
4345
}
@@ -48,4 +50,5 @@ enum TrunkSubcommands {
4850
Clean(cmd::clean::Clean),
4951
Serve(cmd::serve::Serve),
5052
Watch(cmd::watch::Watch),
53+
Config(cmd::config::Config),
5154
}

0 commit comments

Comments
 (0)