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

Allow reloading tilesets without server restart #8

Open
wants to merge 5 commits into
base: main
Choose a base branch
from
Open
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Next Next commit
Allow reloading tilesets with API endpoint without server restart
  • Loading branch information
natnat-mc committed Dec 22, 2021
commit fa05fb5aeae93223bdad8bd9df74016b105703fb
3 changes: 3 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -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
@@ -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 |
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;

@@ -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> {
@@ -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> {
@@ -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,
})
}

1 change: 1 addition & 0 deletions src/main.rs
Original file line number Diff line number Diff line change
@@ -35,6 +35,7 @@ fn main() {
args.allowed_hosts,
args.headers,
args.disable_preview,
args.allow_reload_api,
args.tilesets,
) {
error!("Server error: {}", e);
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)?;
@@ -28,6 +28,7 @@ pub async fn run(
allowed_hosts.clone(),
headers.clone(),
disable_preview,
allow_reload_api,
)
}))
}
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};

@@ -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! {
@@ -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);

@@ -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)
@@ -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());
}
}
}
};
@@ -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))
@@ -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);
}

@@ -333,6 +341,7 @@ mod tests {
Some(vec![String::from("example.com")]),
None,
false,
false,
)
.await;
assert_eq!(response.status(), 403);
@@ -346,6 +355,7 @@ mod tests {
Some(vec![String::from("*")]),
None,
false,
false,
)
.await;
assert_eq!(response.status(), 200);
@@ -359,6 +369,7 @@ mod tests {
Some(vec![String::from("example.com")]),
None,
false,
false,
)
.await;
assert_eq!(response.status(), 200);
@@ -372,6 +383,7 @@ mod tests {
Some(vec![String::from("example.com")]),
None,
false,
false,
)
.await;
assert_eq!(response.status(), 403);
@@ -385,6 +397,7 @@ mod tests {
Some(vec![String::from(".example.com")]),
None,
false,
false,
)
.await;
assert_eq!(response.status(), 200);
@@ -398,6 +411,7 @@ mod tests {
None,
None,
false,
false,
)
.await;
assert_eq!(response.status(), 200);
@@ -411,6 +425,7 @@ mod tests {
None,
None,
false,
false,
)
.await;
assert_eq!(response.status(), 200);
@@ -424,6 +439,7 @@ mod tests {
None,
None,
false,
false,
)
.await;
assert_eq!(response.status(), 200);
@@ -438,6 +454,7 @@ mod tests {
None,
None,
false,
false,
)
.await;
assert_eq!(response.status(), 200);
@@ -455,6 +472,7 @@ mod tests {
None,
None,
false,
false,
)
.await;
assert_eq!(response.status(), 200);
@@ -480,6 +498,7 @@ mod tests {
None,
None,
false,
false,
)
.await;
assert_eq!(response.status(), 204);
@@ -493,6 +512,7 @@ mod tests {
None,
None,
true,
false,
)
.await;
assert_eq!(response.status(), 404);
52 changes: 49 additions & 3 deletions src/tiles.rs
Original file line number Diff line number Diff line change
@@ -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};
@@ -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,
@@ -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();
@@ -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> {