Skip to content
This repository has been archived by the owner on May 21, 2023. It is now read-only.

Commit

Permalink
Allow reloading tilesets with API endpoint without server restart
Browse files Browse the repository at this point in the history
  • Loading branch information
natnat-mc committed Dec 22, 2021
1 parent 79cf618 commit fa05fb5
Show file tree
Hide file tree
Showing 6 changed files with 92 additions and 14 deletions.
3 changes: 3 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,8 @@ USAGE:
mbtileserver [FLAGS] [OPTIONS]
FLAGS:
--allow-reload-api
Allow reloading tilesets with /reload endpoint
--disable-preview
Disable preview map
-h, --help
Expand Down Expand Up @@ -53,6 +55,7 @@ You can adjust the log level by setting `RUST_LOG` environment variable. Possbil

| Endpoint | Description |
|--------------------------------------------------------------|--------------------------------------------------------------------------------|
| /reload | reloads tilesets from directory (if enabled with `--allow-reload`) |
| /services | lists all discovered and valid mbtiles in the tiles directory |
| /services/\<path-to-tileset> | shows tileset metadata |
| /services/\<path-to-tileset>/map | tileset preview |
Expand Down
11 changes: 9 additions & 2 deletions src/config.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
use std::collections::HashMap;
use std::env;
use std::path::PathBuf;

Expand All @@ -9,11 +8,12 @@ use crate::tiles;

#[derive(Clone, Debug)]
pub struct Args {
pub tilesets: HashMap<String, tiles::TileMeta>,
pub tilesets: tiles::Tilesets,
pub port: u16,
pub allowed_hosts: Vec<String>,
pub headers: Vec<(String, String)>,
pub disable_preview: bool,
pub allow_reload_api: bool,
}

pub fn get_app<'a, 'b>() -> App<'a, 'b> {
Expand Down Expand Up @@ -57,6 +57,11 @@ pub fn get_app<'a, 'b>() -> App<'a, 'b> {
.long("disable-preview")
.help("Disable preview map\n"),
)
.arg(
Arg::with_name("allow_reload_api")
.long("allow-reload-api")
.help("Allow reloading tilesets with /reload endpoint\n"),
)
}

pub fn parse(matches: ArgMatches) -> Result<Args> {
Expand Down Expand Up @@ -108,13 +113,15 @@ pub fn parse(matches: ArgMatches) -> Result<Args> {
}

let disable_preview = matches.occurrences_of("disable_preview") != 0;
let allow_reload_api = matches.occurrences_of("allow_reload_api") != 0;

Ok(Args {
tilesets,
port,
allowed_hosts,
headers,
disable_preview,
allow_reload_api,
})
}

Expand Down
1 change: 1 addition & 0 deletions src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ fn main() {
args.allowed_hosts,
args.headers,
args.disable_preview,
args.allow_reload_api,
args.tilesets,
) {
error!("Server error: {}", e);
Expand Down
7 changes: 4 additions & 3 deletions src/server.rs
Original file line number Diff line number Diff line change
@@ -1,17 +1,17 @@
use hyper::service::{make_service_fn, service_fn};
use hyper::Server;
use std::collections::HashMap;

use crate::service;
use crate::tiles::TileMeta;
use crate::tiles::Tilesets;

#[tokio::main]
pub async fn run(
port: u16,
allowed_hosts: Vec<String>,
headers: Vec<(String, String)>,
disable_preview: bool,
tilesets: HashMap<String, TileMeta>,
allow_reload_api: bool,
tilesets: Tilesets,
) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
let addr = ([0, 0, 0, 0], port).into();
let server = Server::try_bind(&addr)?;
Expand All @@ -28,6 +28,7 @@ pub async fn run(
allowed_hosts.clone(),
headers.clone(),
disable_preview,
allow_reload_api,
)
}))
}
Expand Down
32 changes: 26 additions & 6 deletions src/service.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
use std::collections::HashMap;

use hyper::header::{CONTENT_ENCODING, CONTENT_TYPE, HOST};
use hyper::{Body, Request, Response, StatusCode};

Expand All @@ -8,7 +6,7 @@ use regex::Regex;
use serde_json::json;

use crate::errors::Result;
use crate::tiles::{get_grid_data, get_tile_data, TileMeta, TileSummaryJSON};
use crate::tiles::{get_grid_data, get_tile_data, TileSummaryJSON, Tilesets};
use crate::utils::{encode, get_blank_image, DataFormat};

lazy_static! {
Expand Down Expand Up @@ -103,10 +101,11 @@ fn is_host_valid(host: &Option<&str>, allowed_hosts: &[String]) -> bool {

pub async fn get_service(
request: Request<Body>,
tilesets: HashMap<String, TileMeta>,
tilesets: Tilesets,
allowed_hosts: Vec<String>,
headers: Vec<(String, String)>,
disable_preview: bool,
allow_reload_api: bool,
) -> Result<Response<Body>> {
let host = get_host(&request);

Expand Down Expand Up @@ -207,7 +206,7 @@ pub async fn get_service(
// Tileset details (/services/<tileset-path>)
let tile_name = segments[1..].join("/");
let tile_meta = match tilesets.get(&tile_name) {
Some(tile_meta) => tile_meta.clone(),
Some(tile_meta) => tile_meta,
None => {
if segments[segments.len() - 1] == "map" {
// Tileset map preview (/services/<tileset-path>/map)
Expand Down Expand Up @@ -279,6 +278,13 @@ pub async fn get_service(
.header(CONTENT_TYPE, "application/json")
.body(Body::from(serde_json::to_string(&tile_meta_json).unwrap()))
.unwrap()); // TODO handle error
} else if path == "/reload" {
if allow_reload_api {
tilesets.reload();
return Ok(no_content());
} else {
return Ok(forbidden());
}
}
}
};
Expand All @@ -301,6 +307,7 @@ mod tests {
allowed_hosts: Option<Vec<String>>,
headers: Option<Vec<(String, String)>>,
disable_preview: bool,
allow_reload: bool,
) -> Response<Body> {
let request = Request::builder()
.uri(format!("{}{}", host, path))
Expand All @@ -314,14 +321,15 @@ mod tests {
allowed_hosts.unwrap_or(vec![String::from("*")]),
headers.unwrap_or(vec![]),
disable_preview,
allow_reload,
)
.await
.unwrap()
}

#[tokio::test]
async fn get_services() {
let response = setup("http://localhost", "/services", None, None, false).await;
let response = setup("http://localhost", "/services", None, None, false, false).await;
assert_eq!(response.status(), 200);
}

Expand All @@ -333,6 +341,7 @@ mod tests {
Some(vec![String::from("example.com")]),
None,
false,
false,
)
.await;
assert_eq!(response.status(), 403);
Expand All @@ -346,6 +355,7 @@ mod tests {
Some(vec![String::from("*")]),
None,
false,
false,
)
.await;
assert_eq!(response.status(), 200);
Expand All @@ -359,6 +369,7 @@ mod tests {
Some(vec![String::from("example.com")]),
None,
false,
false,
)
.await;
assert_eq!(response.status(), 200);
Expand All @@ -372,6 +383,7 @@ mod tests {
Some(vec![String::from("example.com")]),
None,
false,
false,
)
.await;
assert_eq!(response.status(), 403);
Expand All @@ -385,6 +397,7 @@ mod tests {
Some(vec![String::from(".example.com")]),
None,
false,
false,
)
.await;
assert_eq!(response.status(), 200);
Expand All @@ -398,6 +411,7 @@ mod tests {
None,
None,
false,
false,
)
.await;
assert_eq!(response.status(), 200);
Expand All @@ -411,6 +425,7 @@ mod tests {
None,
None,
false,
false,
)
.await;
assert_eq!(response.status(), 200);
Expand All @@ -424,6 +439,7 @@ mod tests {
None,
None,
false,
false,
)
.await;
assert_eq!(response.status(), 200);
Expand All @@ -438,6 +454,7 @@ mod tests {
None,
None,
false,
false,
)
.await;
assert_eq!(response.status(), 200);
Expand All @@ -455,6 +472,7 @@ mod tests {
None,
None,
false,
false,
)
.await;
assert_eq!(response.status(), 200);
Expand All @@ -480,6 +498,7 @@ mod tests {
None,
None,
false,
false,
)
.await;
assert_eq!(response.status(), 204);
Expand All @@ -493,6 +512,7 @@ mod tests {
None,
None,
true,
false,
)
.await;
assert_eq!(response.status(), 404);
Expand Down
52 changes: 49 additions & 3 deletions src/tiles.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ use std::collections::HashMap;
use std::ffi::OsStr;
use std::fs::read_dir;
use std::path::{Path, PathBuf};
use std::sync::{Arc, Mutex};

use r2d2_sqlite::SqliteConnectionManager;
use rusqlite::{params, OpenFlags};
Expand Down Expand Up @@ -59,6 +60,51 @@ pub struct UTFGrid {
pub keys: Vec<String>,
}

#[derive(Clone, Debug)]
struct TilesetsData {
pub data: HashMap<String, TileMeta>,
pub path: PathBuf,
}

#[derive(Clone, Debug)]
pub struct Tilesets {
data: Arc<Mutex<TilesetsData>>,
}
impl Tilesets {
pub fn new(data: HashMap<String, TileMeta>, path: PathBuf) -> Tilesets {
Tilesets {
data: Arc::new(Mutex::new(TilesetsData { data, path })),
}
}

pub fn get<S: AsRef<str>>(&self, key: S) -> Option<TileMeta> {
self.data.lock().unwrap().data.get(key.as_ref()).cloned()
}
#[allow(dead_code)]
pub fn len(&self) -> usize {
self.data.lock().unwrap().data.len()
}
#[allow(dead_code)]
pub fn contains_key<S: AsRef<str>>(&self, key: S) -> bool {
self.data.lock().unwrap().data.contains_key(key.as_ref())
}

pub fn reload(&self) {
let mut data = self.data.lock().unwrap();
let replacement = discover_tilesets(String::new(), data.path.clone());
data.data.clear();
data.data.extend(replacement);
}
}
impl IntoIterator for Tilesets {
type Item = (String, TileMeta);
type IntoIter = <HashMap<String, TileMeta> as IntoIterator>::IntoIter;

fn into_iter(self) -> Self::IntoIter {
self.data.lock().unwrap().data.clone().into_iter()
}
}

pub fn get_data_format_via_query(
tile_name: &str,
connection: &Connection,
Expand Down Expand Up @@ -172,10 +218,10 @@ pub fn get_tile_details(path: &Path, tile_name: &str) -> Result<TileMeta> {
Ok(metadata)
}

pub fn discover_tilesets(parent_dir: String, path: PathBuf) -> HashMap<String, TileMeta> {
pub fn discover_tilesets(parent_dir: String, path: PathBuf) -> Tilesets {
// Walk through the given path and its subfolders, find all valid mbtiles and create and return a map of mbtiles file names to their absolute path
let mut tiles = HashMap::new();
for p in read_dir(path).unwrap() {
for p in read_dir(path.clone()).unwrap() {
let p = p.unwrap().path();
if p.is_dir() {
let dir_name = p.file_stem().unwrap().to_str().unwrap();
Expand All @@ -196,7 +242,7 @@ pub fn discover_tilesets(parent_dir: String, path: PathBuf) -> HashMap<String, T
};
}
}
tiles
Tilesets::new(tiles, path)
}

fn get_grid_info(tile_name: &str, connection: &Connection) -> Option<DataFormat> {
Expand Down

0 comments on commit fa05fb5

Please sign in to comment.