tiles_proxy/
main.rs

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
//! TilesProxy: a cache proxy for tiles.
//!
//! Supported standards for tiles:
//! - [OGC](https://www.ogc.org/) WMTS: [OpenGIS Web Map Tile Service](https://www.ogc.org/standard/wmts/)
//! - XYZ (aka Slippy Map Tilenames): the de facto OpenStreetMap standard
//!
//! Configuration of origin tiles servers is done in a TOML file.
//!
//! To enable browser caching, HTTP headers are added: [ETag](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/ETag) and [Cache-Control](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Cache-Control).
//! [CORS headers](https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS) are also added to enable canvas export (eg.: in OpenLayers) from browser.

#[macro_use]
extern crate rocket;

mod cache;
mod config;
mod http_headers;
#[cfg(test)]
mod tests;
mod tile_request;

use cache::Cache;
use cache::CachedFile;
use config::Config;
use figment::Figment;
use rocket::Build;
use rocket::Request;
use rocket::Rocket;
use rocket::State;
use rocket_etag_if_none_match::EtagIfNoneMatch;
use std::env;
use tile_request::WmtsRequest;
use tile_request::XyzRequest;

/// Catcher for 404 errors.
#[catch(404)]
fn general_not_found(req: &Request) -> String {
    format!("Sorry, '{}' is not a valid path.", req.uri())
}

/// URL for WMTS tile.
#[get("/wmts/<alias>?<request..>")]
pub async fn wmts_tile(
    app_cache: &State<Cache>,
    etag_if_none_match: EtagIfNoneMatch<'_>,
    alias: &str,
    request: WmtsRequest,
) -> Option<CachedFile> {
    let req = request.with_alias(alias);
    app_cache
        .get_or_download_wmts(etag_if_none_match, req)
        .await
        .ok()
}

/// Catcher for 404 errors about WMTS tile.
#[catch(404)]
fn wmts_tile_not_found(req: &Request) -> String {
    format!("Sorry, the WMTS tile for '{}' was not found.", req.uri())
}

/// URL for XYZ tile.
#[get("/xyz/<alias>/<a>/<x>/<y>/<z>")]
pub async fn xyz_tile<'a>(
    app_cache: &State<Cache>,
    etag_if_none_match: EtagIfNoneMatch<'_>,
    alias: &str,
    a: &str,
    x: &str,
    y: &str,
    z: &str,
) -> Option<CachedFile> {
    let request = XyzRequest::new(alias, a, x, y, z);
    app_cache
        .get_or_download_xyz(etag_if_none_match, request)
        .await
        .ok()
}

/// Catcher for 404 errors about XYZ tile.
#[catch(404)]
fn xyz_tile_not_found(req: &Request) -> String {
    format!("Sorry, the XYZ tile for '{}' was not found.", req.uri())
}

/// Root url.
#[get("/")]
fn index() -> &'static str {
    "Hello, there is nothing here! Read the fully described manual ;)"
}

/// Rocket launch.
#[launch]
fn rocket() -> _ {
    match run(env::args().collect()) {
        Ok(r) => r,
        Err(e) => panic!("Error while launching: {}", e),
    }
}

/// Run function, also used by tests.
fn run(args: Vec<String>) -> Result<Rocket<Build>, String> {
    if let Some(filepath) = args.get(1) {
        let app_config: Config = Config::from_file(filepath);
        let cache: Cache = Cache::new(app_config.clone());
        dbg!(app_config.clone());
        let new_figment = Figment::new()
            .join(("ident", false)) // remove HTTP header "Server"
            .join(("port", app_config.port));
        let figment = rocket::Config::figment().merge(new_figment);

        Ok(rocket::custom(figment)
            .attach(http_headers::CacheControl)
            .attach(http_headers::Cors)
            .manage(cache)
            .mount("/", routes![index, wmts_tile, xyz_tile])
            .register("/", catchers![general_not_found])
            .register("/wmts", catchers![wmts_tile_not_found])
            .register("/xyz", catchers![xyz_tile_not_found]))
    } else {
        Err("No configuration!".to_string())
    }
}