Skip to content

Commit

Permalink
feat: allow more control over the different base URLs
Browse files Browse the repository at this point in the history
This change introduced the idea of the three different base URLs:

* The public base/common prefix, when generating links
* The "serve base", when serving content using `trunk serve`
* The "websocket base", where the auto-reload websocket connects to

The sane default still is to use an absolute --public-url, defaulting to
`/`. However, each of the URLs/bases can be overridden when necessary.

Closes: trunk-rs#668, trunk-rs#626
  • Loading branch information
ctron committed Feb 16, 2024
1 parent f59d78e commit fd5038a
Show file tree
Hide file tree
Showing 16 changed files with 480 additions and 92 deletions.
35 changes: 35 additions & 0 deletions examples/behind-reverse-proxy/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
# Trunk behind a reverse proxy

The idea is to have `trunk serve` running behind a reverse proxy. This may be suitable for testing,
but `trunk serve` is not intended to host your application.

**NOTE:** This is different to using trunk's built in proxy, which by itself acts as a reverse proxy, allowing to
access other endpoints using the endpoint served by `trunk serve`.

**NOTE**: All commands are relative to this file.

## Running the example

As reverse proxy, we run an NGINX:

```shell
podman run --rm --network=host -ti -v $(pwd)/nginx.conf:/etc/nginx/nginx.conf:z docker.io/library/nginx:latest
```

And then we serve the "vanilla" example using trunk:

```shell
trunk serve ../vanilla/Trunk.toml --public-url /my-app --serve-base /
```

Or, using the development version:

```shell
cargo run --manifest-path=../../Cargo.toml -- serve --config ../vanilla/Trunk.toml --public-url /my-app --serve-base /
```

## Trying it out

When you go to <http://localhost:9090>, you will see the NGINX welcome screen.

When you go to <http://localhost:9090/my-app/>, you will see the application served by `trunk`.
41 changes: 41 additions & 0 deletions examples/behind-reverse-proxy/nginx.conf
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
user nginx;
worker_processes auto;

error_log /var/log/nginx/error.log notice;
pid /var/run/nginx.pid;

events {
worker_connections 1024;
}

http {
include /etc/nginx/mime.types;
default_type application/octet-stream;

log_format main '$remote_addr - $remote_user [$time_local] "$request" '
'$status $body_bytes_sent "$http_referer" '
'"$http_user_agent" "$http_x_forwarded_for"';

access_log /var/log/nginx/access.log main;

keepalive_timeout 65;

server {
listen 9090;

location /my-app/.well-known/trunk/ws {
proxy_pass http://localhost:8080/.well-known/trunk/ws;

proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "Upgrade";
}
location /my-app/ {
proxy_pass http://localhost:8080/;

# required for trunk to create correct URLs
proxy_set_header Host $http_host;
}

}
}
43 changes: 42 additions & 1 deletion site/content/advanced.md
Original file line number Diff line number Diff line change
Expand Up @@ -71,10 +71,12 @@ export default function myInitializer () {
};
```

For a full example see: <https://github.com/trunk-rs/trunk/examples/initializer>.
For a full example, see: <https://github.com/trunk-rs/trunk/examples/initializer>.

## Update check

Since: `0.19.0-alpha.2`.

Trunk has an update check built in. By default, it will check the `trunk` crate on `crates.io` for a newer
(non pre-release) version. If one is found, the information will be shown in the command line.

Expand All @@ -83,3 +85,42 @@ runtime using the environment variable `TRUNK_SKIP_VERSION_CHECK`, or using the
`--skip-version-check`.

The check is only performed every 24 hours.

## Base URLs, public URLs, paths & reverse proxies

Since: `0.19.0-alpha.3`.

Originally `trunk` had a single `--public-url`, which allowed to set the base URL of the hosted application.
Plain and simple. This was a prefix for all URLs generated and acted as a base for `trunk serve`.

Unfortunately, life isn't that simple and naming is hard.

Today `trunk` was three paths:

* The "public base URL": acting as a prefix for all generated URLs
* The "serve base": acting as a scope/prefix for all things served by `trunk serve`
* The "websocket base": acting as a base path for the auto-reload websocket

All three can be configured, but there are reasonable defaults in place. By default, the serve base and websocket base
default to the absolute path of the public base. The public base will have a slash appended if it doesn't have one. The
public base can be one of:

* Unset/nothing/default (meaning `/`)
* An absolute URL (e.g. `http://domain/path/app`)
* An absolute path (e.g. `/path/app`)
* A relative path (e.g. `foo` or `./`)

If the public base is an absolute URL, then the path of that URL will be used as serve and websocket base. If the public
base is a relative path, then it will be turned into an absolute one. Both approaches might result in a dysfunctional
application, based on your environment. There will be a warning on the console. However, by providing an explicit
value using serve-base or ws-base, this can be fixed.

Why is this necessary and when is it useful? It's mostly there to provide all the knobs/configurations for the case
that weren't considered. The magic of public-url worked for many, but not for all. To support such cases, it
is now possible to tweak all the settings, at the cost of more complexity. Having reasonable defaults should keep it
simple for the simple cases.

An example use case is a reverse proxy *in front* of `trunk serve`, which can't be configured to serve the trunk
websocket at the location `trunk serve` expects it. Now, it is possible to have `--public-url` to choose the base when
generating links, so that it looks correct when being served by the proxy. But also use `--serve-base /` to keep
serving resource from the root.
3 changes: 2 additions & 1 deletion src/autoreload.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,15 @@
(function () {

const address = '{{__TRUNK_ADDRESS__}}';
const base = '{{__TRUNK_WS_BASE__}}';
let protocol = '{{__TRUNK_WS_PROTOCOL__}}';
protocol =
protocol
? protocol
: window.location.protocol === 'https:'
? 'wss'
: 'ws';
const url = protocol + '://' + address + '/_trunk/ws';
const url = protocol + '://' + address + base + '.well-known/trunk/ws';

class Overlay {
constructor() {
Expand Down
12 changes: 0 additions & 12 deletions src/common.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ use async_recursion::async_recursion;
use console::Emoji;
use once_cell::sync::Lazy;
use std::collections::HashSet;
use std::convert::Infallible;
use std::ffi::OsStr;
use std::fmt::Debug;
use std::fs::Metadata;
Expand All @@ -26,17 +25,6 @@ pub static UPDATE: Emoji = Emoji("⏫ ", "");
static CWD: Lazy<PathBuf> =
Lazy::new(|| std::env::current_dir().expect("error getting current dir"));

/// Ensure the given value for `--public-url` is formatted correctly.
pub fn parse_public_url(val: &str) -> Result<String, Infallible> {
let prefix = if val.starts_with('/') || val.starts_with("./") {
""
} else {
"/"
};
let suffix = if !val.ends_with('/') { "/" } else { "" };
Ok(format!("{}{}{}", prefix, val, suffix))
}

/// A utility function to recursively copy a directory.
#[async_recursion]
pub async fn copy_dir_recursive<F, T>(from_dir: F, to_dir: T) -> Result<HashSet<PathBuf>>
Expand Down
4 changes: 2 additions & 2 deletions src/config/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ pub const STAGE_DIR: &str = ".stage";

pub use manifest::CargoMetadata;
pub use models::{
ConfigOpts, ConfigOptsBuild, ConfigOptsClean, ConfigOptsCore, ConfigOptsHook, ConfigOptsProxy,
ConfigOptsServe, ConfigOptsTools, ConfigOptsWatch, CrossOrigin, WsProtocol,
BaseUrl, ConfigOpts, ConfigOptsBuild, ConfigOptsClean, ConfigOptsCore, ConfigOptsHook,
ConfigOptsProxy, ConfigOptsServe, ConfigOptsTools, ConfigOptsWatch, CrossOrigin, WsProtocol,
};
pub use rt::{Features, RtcBuild, RtcClean, RtcCore, RtcServe, RtcWatch};
12 changes: 9 additions & 3 deletions src/config/models/build.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
use crate::common::parse_public_url;
use crate::config::models::BaseUrl;
use clap::Args;
use serde::Deserialize;
use std::collections::HashMap;
Expand Down Expand Up @@ -36,8 +36,14 @@ pub struct ConfigOptsBuild {
pub locked: bool,

/// The public URL from which assets are to be served
#[arg(long, value_parser = parse_public_url)]
pub public_url: Option<String>,
#[arg(long)]
#[serde(default)]
pub public_url: Option<BaseUrl>,

/// Don't add a trailing slash to the public URL if it is missing [default: false]
#[arg(long)]
#[serde(default)]
pub public_url_no_trailing_slash_fix: bool,

/// Build without default features [default: false]
#[arg(long)]
Expand Down
3 changes: 3 additions & 0 deletions src/config/models/core.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
use semver::VersionReq;
use serde::Deserialize;
use std::path::PathBuf;

/// Config options for the core project.
#[derive(Clone, Debug, Default, Deserialize)]
Expand All @@ -8,4 +9,6 @@ pub struct ConfigOptsCore {
// align that with cargo's `rust-version`
#[serde(alias = "trunk-version")]
pub trunk_version: Option<VersionReq>,
#[serde(skip)]
pub working_directory: Option<PathBuf>,
}
17 changes: 15 additions & 2 deletions src/config/models/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ mod hook;
mod proxy;
mod serve;
mod tools;
mod types;
mod watch;

pub use build::*;
Expand All @@ -29,6 +30,7 @@ pub use hook::*;
pub use proxy::*;
pub use serve::*;
pub use tools::*;
pub use types::*;
pub use watch::*;

#[derive(Clone, Debug)]
Expand Down Expand Up @@ -242,9 +244,18 @@ impl ConfigOpts {
}

fn file_and_env_layers(path: Option<PathBuf>) -> Result<Self> {
let toml_cfg = Self::from_file(path)?;
let toml_cfg = Self::from_file(path.clone())?;
let env_cfg = Self::from_env().context("error reading trunk env var config")?;
let cfg = Self::merge(toml_cfg, env_cfg);
let mut cfg = Self::merge(toml_cfg, env_cfg);

// We always set the working directory with the parent of the configuration. So that
// we have a canonical location of the expected working directory.

let core = cfg.core.get_or_insert(ConfigOptsCore::default());
core.working_directory = path.and_then(|path| path.parent().map(|p| p.to_path_buf()));

// return the result

Ok(cfg)
}

Expand Down Expand Up @@ -439,6 +450,8 @@ impl ConfigOpts {
g.ws_protocol = g.ws_protocol.or(l.ws_protocol);
g.tls_key_path = g.tls_key_path.or(l.tls_key_path);
g.tls_cert_path = g.tls_cert_path.or(l.tls_cert_path);
g.serve_base = g.serve_base.or(l.serve_base);
g.ws_base = g.ws_base.or(l.ws_base);
// NOTE: this can not be disabled in the cascade.
if l.no_autoreload {
g.no_autoreload = true;
Expand Down
6 changes: 6 additions & 0 deletions src/config/models/serve.rs
Original file line number Diff line number Diff line change
Expand Up @@ -68,10 +68,16 @@ pub struct ConfigOptsServe {
/// Protocol used for the auto-reload WebSockets connection [enum: ws, wss]
#[arg(long = "ws-protocol")]
pub ws_protocol: Option<WsProtocol>,
/// The path to the trunk web-socket [default: <serve-base>]
#[arg(long)]
pub ws_base: Option<String>,
/// The TLS key file to enable TLS encryption [default: None]
#[arg(long)]
pub tls_key_path: Option<PathBuf>,
/// The TLS cert file to enable TLS encryption [default: None]
#[arg(long)]
pub tls_cert_path: Option<PathBuf>,
/// A base path to serve the application from [default: <public-url>]
#[arg(long)]
pub serve_base: Option<String>,
}
Loading

0 comments on commit fd5038a

Please sign in to comment.