Skip to content

Commit

Permalink
Add --base-path CLI option to override the URL path in the tilejson (#…
Browse files Browse the repository at this point in the history
…1205)

Override URL path in the tilejson's `tiles` field when used behind a proxy that is not setting `X-Rewrite-URL` header

Fixes #1185
  • Loading branch information
sharkAndshark committed Feb 27, 2024
1 parent e0c960a commit 99cd99e
Show file tree
Hide file tree
Showing 9 changed files with 75 additions and 14 deletions.
4 changes: 4 additions & 0 deletions docs/src/config-file.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,10 @@ keep_alive: 75
# The socket address to bind [default: 0.0.0.0:3000]
listen_addresses: '0.0.0.0:3000'


# Set TileJSON URL path prefix, ignoring X-Rewrite-URL header. Must begin with a `/`
base_path: /tiles

# Number of web server workers
worker_processes: 8

Expand Down
4 changes: 3 additions & 1 deletion docs/src/run-with-cli.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,9 @@ Options:

-l, --listen-addresses <LISTEN_ADDRESSES>
The socket address to bind. [DEFAULT: 0.0.0.0:3000]

--base-path <BASE_PATH>
Set TileJSON URL path prefix, ignoring X-Rewrite-URL header. Must begin with a `/`. Examples: `/`, `/tiles`

-W, --workers <WORKERS>
Number of web server workers

Expand Down
3 changes: 1 addition & 2 deletions martin/src/args/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,5 +13,4 @@ mod root;
pub use root::{Args, ExtraArgs, MetaArgs};

mod srv;
pub use srv::PreferredEncoding;
pub use srv::SrvArgs;
pub use srv::{PreferredEncoding, SrvArgs};
9 changes: 8 additions & 1 deletion martin/src/args/srv.rs
Original file line number Diff line number Diff line change
@@ -1,14 +1,18 @@
use crate::srv::{SrvConfig, KEEP_ALIVE_DEFAULT, LISTEN_ADDRESSES_DEFAULT};
use clap::ValueEnum;
use serde::{Deserialize, Serialize};

use crate::srv::{SrvConfig, KEEP_ALIVE_DEFAULT, LISTEN_ADDRESSES_DEFAULT};

#[derive(clap::Args, Debug, PartialEq, Default)]
#[command(about, version)]
pub struct SrvArgs {
#[arg(help = format!("Connection keep alive timeout. [DEFAULT: {}]", KEEP_ALIVE_DEFAULT), short, long)]
pub keep_alive: Option<u64>,
#[arg(help = format!("The socket address to bind. [DEFAULT: {}]", LISTEN_ADDRESSES_DEFAULT), short, long)]
pub listen_addresses: Option<String>,
/// Set TileJSON URL path prefix, ignoring X-Rewrite-URL header. Must begin with a `/`. Examples: `/`, `/tiles`
#[arg(long)]
pub base_path: Option<String>,
/// Number of web server workers
#[arg(short = 'W', long)]
pub workers: Option<usize>,
Expand Down Expand Up @@ -42,5 +46,8 @@ impl SrvArgs {
if self.preferred_encoding.is_some() {
srv_config.preferred_encoding = self.preferred_encoding;
}
if self.base_path.is_some() {
srv_config.base_path = self.base_path;
}
}
}
6 changes: 5 additions & 1 deletion martin/src/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ use crate::source::{TileInfoSources, TileSources};
#[cfg(feature = "sprites")]
use crate::sprites::{SpriteConfig, SpriteSources};
use crate::srv::{SrvConfig, RESERVED_KEYWORDS};
use crate::utils::{CacheValue, MainCache, OptMainCache};
use crate::utils::{parse_base_path, CacheValue, MainCache, OptMainCache};
use crate::MartinError::{ConfigLoadError, ConfigParseError, ConfigWriteError, NoSources};
use crate::{IdResolver, MartinResult, OptOneMany};

Expand Down Expand Up @@ -71,6 +71,10 @@ impl Config {
let mut res = UnrecognizedValues::new();
copy_unrecognized_config(&mut res, "", &self.unrecognized);

if let Some(path) = &self.srv.base_path {
self.srv.base_path = Some(parse_base_path(path)?);
}

#[cfg(feature = "postgres")]
for pg in self.postgres.iter_mut() {
res.extend(pg.finalize()?);
Expand Down
4 changes: 4 additions & 0 deletions martin/src/srv/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ pub const LISTEN_ADDRESSES_DEFAULT: &str = "0.0.0.0:3000";
pub struct SrvConfig {
pub keep_alive: Option<u64>,
pub listen_addresses: Option<String>,
pub base_path: Option<String>,
pub worker_processes: Option<usize>,
pub preferred_encoding: Option<PreferredEncoding>,
}
Expand All @@ -35,6 +36,7 @@ mod tests {
listen_addresses: some("0.0.0.0:3000"),
worker_processes: Some(8),
preferred_encoding: None,
base_path: None,
}
);
assert_eq!(
Expand All @@ -50,6 +52,7 @@ mod tests {
listen_addresses: some("0.0.0.0:3000"),
worker_processes: Some(8),
preferred_encoding: Some(PreferredEncoding::Brotli),
base_path: None
}
);
assert_eq!(
Expand All @@ -65,6 +68,7 @@ mod tests {
listen_addresses: some("0.0.0.0:3000"),
worker_processes: Some(8),
preferred_encoding: Some(PreferredEncoding::Brotli),
base_path: None,
}
);
}
Expand Down
21 changes: 12 additions & 9 deletions martin/src/srv/tiles_info.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ use serde::Deserialize;
use tilejson::{tilejson, TileJSON};

use crate::source::{Source, TileSources};
use crate::srv::SrvConfig;

#[derive(Deserialize)]
pub struct SourceIDsRequest {
Expand All @@ -26,17 +27,19 @@ async fn get_source_info(
req: HttpRequest,
path: Path<SourceIDsRequest>,
sources: Data<TileSources>,
srv_config: Data<SrvConfig>,
) -> ActixResult<HttpResponse> {
let sources = sources.get_sources(&path.source_ids, None)?.0;

// Get `X-REWRITE-URL` header value, and extract its `path` component.
// If the header is not present or cannot be parsed as a URL, return the request path.
let tiles_path = req
.headers()
.get("x-rewrite-url")
.and_then(|v| v.to_str().ok())
.and_then(|v| v.parse::<Uri>().ok())
.map_or_else(|| req.path().to_owned(), |v| v.path().to_owned());
let tiles_path = if let Some(base_path) = &srv_config.base_path {
format!("{base_path}/{}", path.source_ids)
} else {
req.headers()
.get("x-rewrite-url")
.and_then(|v| v.to_str().ok())
.and_then(|v| v.parse::<Uri>().ok())
.map_or_else(|| req.path().to_owned(), |v| v.path().to_owned())
};

let query_string = req.query_string();
let path_and_query = if query_string.is_empty() {
Expand Down Expand Up @@ -155,7 +158,7 @@ pub fn merge_tilejson(sources: &[&dyn Source], tiles_url: String) -> TileJSON {
pub mod tests {
use std::collections::BTreeMap;

use tilejson::{tilejson, Bounds, VectorLayer};
use tilejson::{Bounds, VectorLayer};

use super::*;
use crate::srv::server::tests::TestSource;
Expand Down
3 changes: 3 additions & 0 deletions martin/src/utils/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,9 @@ pub enum MartinError {
#[error("Unable to bind to {1}: {0}")]
BindingError(io::Error, String),

#[error("Base path must be a valid URL path, and must begin with a '/' symbol, but is '{0}'")]
BasePathError(String),

#[error("Unable to load config file {}: {0}", .1.display())]
ConfigLoadError(io::Error, PathBuf),

Expand Down
35 changes: 35 additions & 0 deletions martin/src/utils/utilities.rs
Original file line number Diff line number Diff line change
@@ -1,8 +1,12 @@
use std::io::{Read as _, Write as _};

use actix_web::http::Uri;
use flate2::read::GzDecoder;
use flate2::write::GzEncoder;

use crate::MartinError::BasePathError;
use crate::MartinResult;

pub fn decode_gzip(data: &[u8]) -> Result<Vec<u8>, std::io::Error> {
let mut decoder = GzDecoder::new(data);
let mut decompressed = Vec::new();
Expand All @@ -28,3 +32,34 @@ pub fn encode_brotli(data: &[u8]) -> Result<Vec<u8>, std::io::Error> {
encoder.write_all(data)?;
Ok(encoder.into_inner())
}

pub fn parse_base_path(path: &str) -> MartinResult<String> {
if !path.starts_with('/') {
return Err(BasePathError(path.to_string()));
}
if let Ok(uri) = path.parse::<Uri>() {
return Ok(uri.path().trim_end_matches('/').to_string());
}
Err(BasePathError(path.to_string()))
}

#[cfg(test)]
pub mod tests {
use crate::utils::parse_base_path;
#[test]
fn test_parse_base_path() {
for (path, expected) in [
("/", Some("")),
("//", Some("")),
("/foo/bar", Some("/foo/bar")),
("/foo/bar/", Some("/foo/bar")),
("", None),
("foo/bar", None),
] {
match expected {
Some(v) => assert_eq!(v, parse_base_path(path).unwrap()),
None => assert!(parse_base_path(path).is_err()),
}
}
}
}

0 comments on commit 99cd99e

Please sign in to comment.