Skip to content

Commit 5634f48

Browse files
authored
Merge pull request #8 from milesgranger/async-server
Switch to Actix-Web
2 parents 65b8691 + c356b82 commit 5634f48

File tree

9 files changed

+1169
-355
lines changed

9 files changed

+1169
-355
lines changed

Cargo.lock

Lines changed: 1027 additions & 191 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 11 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -4,15 +4,15 @@ version = "0.1.0"
44
authors = ["Miles Granger <miles59923@gmail.com>"]
55

66
[dependencies]
7-
log = "0.4.0"
8-
clap = "2.32.0"
9-
env_logger = "0.5.12"
10-
ndarray = "0.11.1"
7+
log = "0.4"
8+
clap = "2.32"
9+
env_logger = "0.5"
10+
ndarray = "0.11"
1111
netcdf = {git = "https://github.com/mhiley/rust-netcdf.git"}
12-
glob = "0.2.11"
13-
serde_json = "1.0.11"
14-
serde = "1.0.29"
15-
serde_derive = "1.0.29"
16-
rocket = "0.3.6"
17-
rocket_codegen = "0.3.6"
18-
rocket_contrib = {features = ["json", "tera_templates"], version = "*", default-features = false}
12+
glob = "0.2"
13+
serde_json = "1.0"
14+
serde = "1.0"
15+
serde_derive = "1.0"
16+
actix-web = "0.7"
17+
actix = "0.7"
18+
tera = "0.11"

Dockerfile-Server

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,9 @@ FROM debian:jessie-slim
33
RUN apt update && apt install -y \
44
libnetcdf-dev
55

6+
# build passes --build-arg DONTCACHE=$(date) to keep the steps below from being cached
7+
ARG DONTCACHE=1
8+
69
RUN mkdir /data
710
WORKDIR /server
811
COPY ./target/release/elevation-api /server

build-images.sh

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,5 +7,5 @@ docker run --rm -v $(pwd):/build/ milesg/elevation-api-builder:latest
77
echo "Completed building binary at $(pwd)/build/"
88

99
echo "Building server"
10-
docker build . -t milesg/elevation-api-server:latest --file ./Dockerfile-Server
10+
docker build . -t milesg/elevation-api-server:latest --file ./Dockerfile-Server --build-arg NOCACHE=$(date +%s)
1111
echo "Built server, run with : 'docker run --rm -d -p 8000:8000 -v $(pwd)/processed_netcdf_files/:/data/ milesg/elevation-api-server:latest'"

src/elevation/mod.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -135,8 +135,8 @@ pub struct Elevation {
135135
}
136136

137137
#[derive(Serialize)]
138-
pub struct ElevationResponse {
139-
pub points: Vec<Elevation>
138+
pub struct Elevations {
139+
pub elevations: Vec<Elevation>
140140
}
141141

142142
/// Load a created summary.json file; holds information about what coordiantes belong to which file

src/json_structs.rs

Lines changed: 4 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -1,24 +1,17 @@
11
use std::str::FromStr;
22
use std::num::ParseFloatError;
3-
use rocket::request;
4-
use rocket::http;
53

64

75
/// Struct to represent a JSON query parameter for a given location
8-
#[derive(FromForm, Serialize, Deserialize)]
6+
#[derive(Serialize, Deserialize)]
97
pub struct Points {
10-
pub points: CoordinateList
8+
pub points: Vec<(f64, f64)>
119
}
1210

1311

14-
/// Struct to represent the core value of the Points struct
15-
/// list of tuples representing (lat, lon) values
16-
#[derive(Debug, Serialize, Deserialize)]
17-
pub struct CoordinateList(pub Vec<(f64, f64)>);
18-
1912

2013
/// Implement FromStr for CoordinateList to parse the coordinate list from the request query
21-
impl FromStr for CoordinateList {
14+
impl FromStr for Points {
2215

2316
type Err = ParseFloatError;
2417

@@ -47,21 +40,6 @@ impl FromStr for CoordinateList {
4740
parsed_points.push((parsed_vec[0], parsed_vec[1]));
4841
}
4942

50-
Ok(CoordinateList(parsed_points))
51-
}
52-
}
53-
54-
55-
/// Implement for Rocket to parse the value from the request, which will implicitly invoke the
56-
/// FromStr impl above.
57-
impl<'v> request::FromFormValue<'v> for CoordinateList {
58-
type Error = &'v http::RawStr;
59-
60-
fn from_form_value(form_value: &'v http::RawStr) -> Result<CoordinateList, &'v http::RawStr> {
61-
match form_value.parse::<CoordinateList>() {
62-
Ok(points) => Ok(points),
63-
_ => Err(form_value)
64-
}
43+
Ok(Points { points: parsed_points })
6544
}
6645
}
67-

src/main.rs

Lines changed: 80 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -1,26 +1,29 @@
1-
#![feature(plugin, custom_derive)]
2-
#![plugin(rocket_codegen)]
3-
pub extern crate rocket;
4-
pub extern crate rocket_contrib;
1+
#[macro_use] pub extern crate serde_derive;
2+
#[macro_use] pub extern crate log;
53
pub extern crate netcdf;
64
pub extern crate ndarray;
75
pub extern crate glob;
86
pub extern crate serde;
97
pub extern crate serde_json;
10-
#[macro_use] pub extern crate serde_derive;
11-
#[macro_use] extern crate log;
12-
extern crate env_logger;
8+
pub extern crate actix_web;
9+
pub extern crate env_logger;
1310
extern crate clap;
11+
extern crate actix;
12+
#[macro_use]
13+
extern crate tera;
1414

1515

16+
use actix_web::{
17+
error, http, middleware, server, App, Error, HttpResponse, HttpRequest, Query, Responder, State, fs, Json, Form, Result
18+
};
19+
1620
use std::collections::HashMap;
17-
use std::path::{Path, PathBuf};
1821
use std::env;
19-
use clap::{Arg, App, SubCommand};
22+
use std::str::FromStr;
23+
24+
use clap::{Arg, App as ClapApp, SubCommand};
2025
use glob::glob;
21-
use rocket_contrib::{Json, Template};
22-
use rocket::response::status::BadRequest;
23-
use rocket::response::NamedFile;
26+
2427

2528
// Local mods
2629
#[cfg(test)]
@@ -29,49 +32,51 @@ mod elevation;
2932
mod json_structs;
3033
use json_structs::{Points};
3134

35+
36+
struct AppState {
37+
template: tera::Tera,
38+
}
39+
3240
// Sanity check
33-
#[get("/")]
34-
fn index() -> Template {
41+
fn index((state, _query): (State<AppState>, Query<HashMap<String, String>>)) -> Result<HttpResponse, Error> {
3542
let mut context = HashMap::new();
3643
context.insert("title".to_string(), "Free Elevation API".to_string());
3744

45+
let s = state
46+
.template
47+
.render("index.html", &context)
48+
.unwrap();
49+
50+
Ok(HttpResponse::Ok().content_type("text/html").body(s))
3851

39-
Template::render("index", &context)
4052
}
4153

54+
// Main API for 90m resolution
55+
fn get_elevations(req: &HttpRequest<AppState>) -> impl Responder {
4256

43-
#[get("/<file..>")]
44-
fn static_files(file: PathBuf) -> Option<NamedFile> {
45-
NamedFile::open(Path::new("static/").join(file)).ok()
46-
}
57+
let points_str = req.query().get("points").unwrap().to_owned();
58+
info!("Got the points string!");
59+
let metas = elevation::load_summary_file();
60+
let points = Points::from_str(&points_str).expect("Unable to parse points!");
4761

62+
let elevations = elevation::get_elevations(points.points, &metas);
63+
let elevations_resp = elevation::Elevations{ elevations };
64+
65+
Json(elevations_resp)
4866

49-
// Main API for 90m resolution
50-
#[get("/api/elevation?<points>")]
51-
fn get_elevations(points: Option<Points>) -> Result<Json<elevation::ElevationResponse>, BadRequest<String>> {
52-
53-
match points {
54-
Some(points) => {
55-
let metas = elevation::load_summary_file();
56-
let elevations = elevation::get_elevations(points.points.0, &metas);
57-
58-
let elevation_response = elevation::ElevationResponse{points: elevations};
59-
Ok(Json(elevation_response))
60-
},
61-
None => {
62-
Err(BadRequest(Some("Unable to parse coordinates. Should be in form '(lat,lon),(lat,lon),(lat,lon)'".to_string())))
63-
}
64-
}
6567

6668
}
6769

6870

6971
fn main() {
7072

73+
env::set_var("RUST_LOG", "actix_web=debug");
74+
//env::set_var("RUST_BACKTRACE", "1");
75+
7176
env_logger::init();
7277
info!("Starting up!");
7378

74-
let matches = App::new("Elevation API")
79+
let matches = ClapApp::new("Elevation API")
7580
.version("1.0")
7681
.author("Miles Granger")
7782
.about("Web service and utility for giving elevations for locations on earth")
@@ -104,12 +109,47 @@ fn main() {
104109
elevation::make_summary_file(path);
105110

106111
} else if let Some(m) = matches.subcommand_matches("run-server") {
112+
107113
let summary_file = m.value_of("SUMMARY-FILE").expect("No path specified!");
114+
108115
env::set_var("SUMMARY_FILE_PATH", summary_file);
109-
rocket::ignite()
110-
.mount("/", routes![index, get_elevations, static_files])
111-
.attach(Template::fairing())
112-
.launch();
116+
117+
// Server
118+
let sys = actix::System::new("elevation-api");
119+
120+
server::new(|| {
121+
122+
let tera = compile_templates!("./templates/**/*");
123+
124+
App::with_state(AppState {template: tera})
125+
126+
// Logging
127+
.middleware(middleware::Logger::default())
128+
129+
// Application base route
130+
.prefix("/")
131+
132+
// Static files
133+
.handler(
134+
"/static",
135+
fs::StaticFiles::new("static")
136+
.expect("Can't find static directory!")
137+
.show_files_listing())
138+
139+
// Homepage
140+
.resource("/", |r| r.method(http::Method::GET).with(index))
141+
142+
// Main elevation API
143+
.resource("/api/elevation", |r| r.method(http::Method::GET).f(get_elevations))
144+
})
145+
.bind("0.0.0.0:8000")
146+
.expect("Unable to bind to 0.0.0.0:8000")
147+
.start();
148+
149+
info!("Started server running on 0.0.0.0:8000");
150+
let _ = sys.run();
151+
152+
113153
} else {
114154
warn!("Nothing to do, exiting!");
115155
}

src/tests/mod.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,15 +16,15 @@ fn test_v1_0_0_query() {
1616
}
1717
*/
1818
use self::super::get_elevations_v1_0_0;
19-
use self::super::elevation::{ElevationResponse, Elevation};
19+
use self::super::elevation::{Elevations, Elevation};
2020

2121
// Parse a query into a points object
2222
let points = Points {
2323
points: CoordinateList::from_str("(48.35,5.3),(48.35,5.23)").unwrap()
2424
};
2525

2626
// Get elevations from the function
27-
let elevations: ElevationResponse = get_elevations_v1_0_0(Some(points)).ok().unwrap().0;
27+
let elevations: Elevations = get_elevations_v1_0_0(Some(points)).ok().unwrap().0;
2828

2929
// Ensure that the first element == 48.35 which guarantees the version promised format.
3030
let point: &Elevation = &elevations.points[0];

0 commit comments

Comments
 (0)