mirror of
https://github.com/maplibre/martin.git
synced 2024-12-21 13:51:47 +03:00
Add --base-path CLI option to override the URL path in the tilejson (#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
This commit is contained in:
parent
e0c960a1b6
commit
99cd99eb51
@ -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
|
||||
|
||||
|
@ -27,6 +27,8 @@ 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
|
||||
|
@ -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};
|
||||
|
@ -1,7 +1,8 @@
|
||||
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 {
|
||||
@ -9,6 +10,9 @@ pub struct SrvArgs {
|
||||
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>,
|
||||
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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};
|
||||
|
||||
@ -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()?);
|
||||
|
@ -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>,
|
||||
}
|
||||
@ -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!(
|
||||
@ -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!(
|
||||
@ -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,
|
||||
}
|
||||
);
|
||||
}
|
||||
|
@ -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 {
|
||||
@ -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()
|
||||
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());
|
||||
.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() {
|
||||
@ -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;
|
||||
|
@ -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),
|
||||
|
||||
|
@ -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();
|
||||
@ -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()),
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user