Skip to content

Commit b861a91

Browse files
authored
Merge pull request #79 from tomprince/server-refactor
Refactor the server to use a real(-ish) router and serve some actual results.
2 parents eb89a36 + 2904e2c commit b861a91

File tree

6 files changed

+143
-63
lines changed

6 files changed

+143
-63
lines changed

Cargo.lock

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

Cargo.toml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,10 +19,12 @@ hyper = { git = "https://github.com/hyperium/hyper.git" }
1919
kernel32-sys = "0.2.2"
2020
lazy_static = "0.2"
2121
libc = "0.2.7"
22+
mime = "0.2.3"
2223
rand = "0.3"
2324
ref_slice = "1.1.1"
2425
reqwest = "0.6"
2526
result = "0.0.1"
27+
route-recognizer = "0.1.12"
2628
scopeguard = "0.3"
2729
semver = "0.6"
2830
serde = "1.0"

src/lib.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,10 @@ extern crate result;
4040
extern crate ref_slice;
4141
extern crate crates_index;
4242
extern crate hyper;
43+
#[macro_use]
44+
extern crate mime;
4345
extern crate arc_cell;
46+
extern crate route_recognizer;
4447

4548
#[macro_use]
4649
pub mod log;

src/report.rs

Lines changed: 15 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ fn results_file(dest: &Path) -> PathBuf {
1313
}
1414

1515
#[derive(Serialize, Deserialize)]
16-
struct TestResults {
16+
pub struct TestResults {
1717
crates: Vec<CrateResult>,
1818
}
1919

@@ -40,13 +40,16 @@ struct BuildTestResult {
4040
log: String,
4141
}
4242

43-
fn generate_report(ex: &ex::Experiment, dest: &Path) -> Result<TestResults> {
43+
44+
pub fn generate_report(ex: &ex::Experiment, dest: Option<&Path>) -> Result<TestResults> {
4445
let db = FileDB::for_experiment(ex);
4546
assert_eq!(ex.toolchains.len(), 2);
4647

47-
fs::create_dir_all(dest)?;
48-
let json = serde_json::to_string(&ex)?;
49-
file::write_string(&dest.join("config.json"), &json)?;
48+
if let Some(dest) = dest {
49+
fs::create_dir_all(dest)?;
50+
let json = serde_json::to_string(&ex)?;
51+
file::write_string(&dest.join("config.json"), &json)?;
52+
}
5053

5154
let res = ex::ex_crates_and_dirs(ex)?
5255
.into_iter()
@@ -59,13 +62,15 @@ fn generate_report(ex: &ex::Experiment, dest: &Path) -> Result<TestResults> {
5962
let res = writer.load_test_result()?;
6063
// If there was no test result return an error
6164
let res = res.ok_or_else(|| Error::from("no result"))?;
62-
let mut result_log = writer.read_log()?;
6365

6466
let rel_log = writer.result_path_fragement();
6567

66-
// TODO: Seperate out writting log files so that they can
67-
// be served via HTTP directly instead.
68-
write_log_file(dest, &rel_log, &mut result_log)?;
68+
if let Some(dest) = dest {
69+
let mut result_log = writer.read_log()?;
70+
// TODO: Seperate out writting log files so that they can
71+
// be served via HTTP directly instead.
72+
write_log_file(dest, &rel_log, &mut result_log)?;
73+
}
6974

7075
Ok(BuildTestResult {
7176
res: res,
@@ -93,7 +98,7 @@ fn generate_report(ex: &ex::Experiment, dest: &Path) -> Result<TestResults> {
9398
pub fn gen(ex_name: &str, dest: &Path) -> Result<()> {
9499
let ex = ex::Experiment::load(ex_name)?;
95100

96-
let res = generate_report(&ex, dest)?;
101+
let res = generate_report(&ex, Some(dest))?;
97102
let json = serde_json::to_string(&res)?;
98103

99104
info!("writing results to {}", results_file(dest).display());

src/server/api.rs

Lines changed: 29 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,20 +5,21 @@
55
//! The responses are calculated in the server.rs file.
66
77
pub mod get {
8-
use server::Data;
8+
use server::{Data, Params};
99

1010
#[derive(Serialize, Deserialize)]
1111
pub struct Response {
1212
pub text: String,
1313
}
1414

15-
pub fn handler(_data: &Data) -> Response {
15+
#[cfg_attr(feature = "cargo-clippy", allow(needless_pass_by_value))]
16+
pub fn handler(_data: &Data, _params: Params) -> Response {
1617
Response { text: String::from("This is a response!") }
1718
}
1819
}
1920

2021
pub mod post {
21-
use server::Data;
22+
use server::{Data, Params};
2223

2324
#[derive(Serialize, Deserialize)]
2425
pub struct Request {
@@ -31,7 +32,31 @@ pub mod post {
3132
}
3233

3334
#[cfg_attr(feature = "cargo-clippy", allow(needless_pass_by_value))]
34-
pub fn handler(post: Request, _data: &Data) -> Response {
35+
pub fn handler(post: Request, _data: &Data, _params: Params) -> Response {
3536
Response { out: format!("Got {}!", post.input) }
3637
}
3738
}
39+
40+
pub mod ex_report {
41+
use ex;
42+
use report::{TestResults, generate_report};
43+
use server::{Data, Params};
44+
45+
#[cfg_attr(feature = "cargo-clippy", allow(needless_pass_by_value))]
46+
pub fn handler(_data: &Data, params: Params) -> TestResults {
47+
let ex_name = params.find("experiment").unwrap();
48+
let ex = ex::Experiment::load(ex_name).unwrap();
49+
generate_report(&ex, None).unwrap()
50+
}
51+
}
52+
53+
pub mod ex_config {
54+
use ex;
55+
use server::{Data, Params};
56+
57+
#[cfg_attr(feature = "cargo-clippy", allow(needless_pass_by_value))]
58+
pub fn handler(_data: &Data, params: Params) -> ex::Experiment {
59+
let ex_name = params.find("experiment").unwrap();
60+
ex::Experiment::load(ex_name).unwrap()
61+
}
62+
}

src/server/mod.rs

Lines changed: 86 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -1,50 +1,76 @@
11
use arc_cell::ArcCell;
2-
use futures::{self, Future, Stream};
2+
use futures::{self, BoxFuture, Future, Stream};
33
use futures_cpupool::CpuPool;
44
use hyper::{self, Get, Post, StatusCode};
55
use hyper::header::{ContentLength, ContentType};
66
use hyper::server::{Http, Request, Response, Service};
7+
use route_recognizer::{Match, Params, Router};
78

89
use serde::Serialize;
910
use serde::de::DeserializeOwned;
1011
use serde_json;
1112
use std::env;
12-
use std::fs::File;
13-
use std::io::Read;
1413
use std::net::SocketAddr;
15-
use std::path::Path;
1614
use std::str;
1715
use std::sync::Arc;
1816

1917
mod api;
2018

2119
pub struct Data;
2220

21+
type Handler =
22+
Box<Fn(&Server, Request, Params) -> BoxFuture<Response, hyper::Error> + Sync + Send + 'static>;
2323
struct Server {
24+
router: Router<Handler>,
2425
data: ArcCell<Data>,
2526
pool: CpuPool,
2627
}
2728

2829
impl Server {
29-
fn handle_get<F, S>(&self, req: &Request, handler: F) -> <Server as Service>::Future
30-
where F: FnOnce(&Data) -> S,
30+
fn handle_get<F, S>(&self,
31+
req: Request,
32+
params: Params,
33+
handler: F)
34+
-> <Server as Service>::Future
35+
where F: FnOnce(&Data, Params) -> S,
3136
S: Serialize
3237
{
33-
assert_eq!(*req.method(), Get);
38+
if *req.method() != Get {
39+
return self.error(StatusCode::BadRequest);
40+
};
3441
let data = self.data.get();
35-
let result = handler(&data);
42+
let result = handler(&data, params);
3643
let response = Response::new()
3744
.with_header(ContentType::json())
3845
.with_body(serde_json::to_string(&result).unwrap());
3946
futures::future::ok(response).boxed()
4047
}
4148

42-
fn handle_post<F, D, S>(&self, req: Request, handler: F) -> <Server as Service>::Future
43-
where F: FnOnce(D, &Data) -> S + Send + 'static,
49+
fn handle_static(&self,
50+
req: Request,
51+
_params: Params,
52+
content_type: ContentType,
53+
body: &'static str)
54+
-> <Server as Service>::Future {
55+
if *req.method() != Get {
56+
return self.error(StatusCode::BadRequest);
57+
};
58+
let response = Response::new().with_header(content_type).with_body(body);
59+
futures::future::ok(response).boxed()
60+
}
61+
62+
fn handle_post<F, D, S>(&self,
63+
req: Request,
64+
params: Params,
65+
handler: F)
66+
-> <Server as Service>::Future
67+
where F: FnOnce(D, &Data, Params) -> S + Send + 'static,
4468
D: DeserializeOwned,
4569
S: Serialize
4670
{
47-
assert_eq!(*req.method(), Post);
71+
if *req.method() != Post {
72+
return self.error(StatusCode::BadRequest);
73+
};
4874
let length = req.headers()
4975
.get::<ContentLength>()
5076
.expect("content-length to exist")
@@ -74,67 +100,78 @@ impl Server {
74100
err));
75101
}
76102
};
77-
let result = handler(body, &data);
103+
let result = handler(body, &data, params);
78104
Response::new()
79105
.with_header(ContentType::json())
80106
.with_body(serde_json::to_string(&result).unwrap())
81107
})
82108
})
83109
.boxed()
84110
}
111+
112+
fn error(&self, status: StatusCode) -> <Server as Service>::Future {
113+
futures::future::ok(Response::new()
114+
.with_header(ContentType::html())
115+
.with_status(status))
116+
.boxed()
117+
}
85118
}
86119

87120
impl Service for Server {
88121
type Request = Request;
89122
type Response = Response;
90123
type Error = hyper::Error;
91-
type Future = Box<Future<Item = Self::Response, Error = Self::Error>>;
124+
type Future = BoxFuture<Self::Response, Self::Error>;
92125

93126
fn call(&self, req: Request) -> Self::Future {
94-
let fs_path = format!("static{}",
95-
if req.path() == "" || req.path() == "/" {
96-
"/index.html"
97-
} else {
98-
req.path()
99-
});
100-
101-
info!("handling: req.path()={:?}, fs_path={:?}",
102-
req.path(),
103-
fs_path);
104-
105-
if fs_path.contains("./") | fs_path.contains("../") {
106-
return futures::future::ok(Response::new()
107-
.with_header(ContentType::html())
108-
.with_status(StatusCode::NotFound))
109-
.boxed();
110-
}
127+
info!("handling: req.path()={:?}", req.path());
111128

112-
if Path::new(&fs_path).is_file() {
113-
return self.pool
114-
.spawn_fn(move || {
115-
let mut f = File::open(&fs_path).unwrap();
116-
let mut source = Vec::new();
117-
f.read_to_end(&mut source).unwrap();
118-
futures::future::ok(Response::new().with_body(source))
119-
})
120-
.boxed();
129+
match self.router.recognize(req.path()) {
130+
Ok(Match { handler, params }) => handler(self, req, params),
131+
Err(_) => self.error(StatusCode::NotFound),
121132
}
122133

123-
match req.path() {
124-
"/api/get" => self.handle_get(&req, api::get::handler),
125-
"/api/post" => self.handle_post(req, api::post::handler),
126-
_ => {
127-
futures::future::ok(Response::new()
128-
.with_header(ContentType::html())
129-
.with_status(StatusCode::NotFound))
130-
.boxed()
131-
}
132-
}
134+
133135
}
134136
}
135137

138+
macro_rules! route {
139+
($router:ident, $path:expr, $method:ident, $($handler:tt)* ) => (
140+
$router.add($path,
141+
Box::new(|server: &Server, req, params| server.$method(req, params, $($handler)*)));
142+
)
143+
}
144+
136145
pub fn start(data: Data) {
146+
let mut router = Router::<Handler>::new();
147+
route!(router, "/api/get", handle_get, api::get::handler);
148+
route!(router, "/api/post", handle_post, api::post::handler);
149+
route!(router,
150+
"/api/ex/:experiment/results",
151+
handle_get,
152+
api::ex_report::handler);
153+
route!(router,
154+
"/api/ex/:experiment/config",
155+
handle_get,
156+
api::ex_config::handler);
157+
route!(router,
158+
"/static/report.html",
159+
handle_static,
160+
ContentType::html(),
161+
include_str!("../../static/report.html"));
162+
route!(router,
163+
"/static/report.js",
164+
handle_static,
165+
ContentType(mime!(Application / Javascript)),
166+
include_str!("../../static/report.js"));
167+
route!(router,
168+
"/static/report.css",
169+
handle_static,
170+
ContentType(mime!(Text / Css)),
171+
include_str!("../../static/report.css"));
172+
137173
let server = Arc::new(Server {
174+
router,
138175
data: ArcCell::new(Arc::new(data)),
139176
pool: CpuPool::new_num_cpus(),
140177
});

0 commit comments

Comments
 (0)