Skip to content

Commit

Permalink
feat: Improve perf, migrate query params to middleware (#52)
Browse files Browse the repository at this point in the history
This PR:
- Allows the option to optimize for small requests via `start_small_load_optimized`
- Moves query params into a middleware so not all routes are slowed by processing query parameters by default
- Removes safety check from middleware. If, for example, a terminal piece of middleware calls next, you'll see an exception. This might be added back in in the future.
  • Loading branch information
trezm committed Aug 7, 2018
1 parent 9eb2136 commit 0c98b08
Show file tree
Hide file tree
Showing 20 changed files with 450 additions and 302 deletions.
9 changes: 9 additions & 0 deletions CONTRIBUTING.md
@@ -0,0 +1,9 @@
# Contributing

All thoughts and PRs are welcome! Feel free to add any thoughts or discussions as issues via github, or reach out via [gitter](https://gitter.im/thruster-rs/).

## Making Changes
In order to add or change code in the repository, please open a pull request and reference the issue that the PR resolves in the description.

## We're small!
This is still a small project, so go crazy :)
6 changes: 1 addition & 5 deletions Cargo.toml
@@ -1,6 +1,6 @@
[package]
name = "thruster"
version = "0.5.0-beta7"
version = "0.5.0-beta10"
authors = ["Pete Mertz <peter.s.mertz@gmail.com>"]
description = "A middleware based http async web server."
readme = "README.md"
Expand Down Expand Up @@ -36,10 +36,6 @@ tokio-codec = "0.1.0"
tokio-core = "0.1.17"
tokio-io = "0.1"

# Other things
tokio-proto = "0.1"
tokio-service = "0.1"

[dev-dependencies]
criterion = "0.2"
rustc-serialize = "0.3"
Expand Down
6 changes: 4 additions & 2 deletions README.md
Expand Up @@ -56,6 +56,7 @@ Based on frameworks like Koa, and Express, thruster aims to be a pleasure to dev
extern crate thruster;
extern crate futures;

use std::collections::HashMap;
use std::boxed::Box;
use futures::future;

Expand All @@ -64,8 +65,9 @@ use thruster::{App, BasicContext as Ctx, MiddlewareChain, MiddlewareReturnValue,
fn generate_context(request: Request) -> Ctx {
Ctx {
body: "".to_owned(),
params: request.params,
query_params: request.query_params
params: HashMap::new(),
query_params: HashMap::new(),
request: request
}
}

Expand Down
99 changes: 80 additions & 19 deletions benches/app.rs
Expand Up @@ -11,20 +11,78 @@ use bytes::{BytesMut, BufMut};

use criterion::Criterion;
use thruster::{decode, App, BasicContext, MiddlewareChain, MiddlewareReturnValue};
use thruster::builtins::query_params::query_params;

fn bench_no_check(c: &mut Criterion) {
c.bench_function("No check", |bench| {
bench.iter(|| {
let v = vec!["a"];

let _ = v.get(0).unwrap();
});
});
}
fn bench_expect(c: &mut Criterion) {
c.bench_function("Expect", |bench| {
bench.iter(|| {
let v = vec!["a"];

let _ = v.get(0).expect("Yikes");
});
});
}
fn bench_assert(c: &mut Criterion) {
c.bench_function("Assert", |bench| {
bench.iter(|| {
let v = vec!["a"];

let a = v.get(0);
assert!(a.is_some());
let _ = a.unwrap();
});
});
}
fn bench_remove(c: &mut Criterion) {
c.bench_function("Remove", |bench| {
bench.iter(|| {
let mut v = vec!["a"];

let a = v.remove(0);
});
});
}

fn bench_route_match(c: &mut Criterion) {
c.bench_function("Route match", |bench| {
let mut app = App::<BasicContext>::new();

fn test_fn_1(context: BasicContext, _chain: &MiddlewareChain<BasicContext>) -> MiddlewareReturnValue<BasicContext> {
Box::new(future::ok(BasicContext {
body: "world".to_owned(),
params: HashMap::new(),
query_params: context.query_params
}))
fn test_fn_1(mut context: BasicContext, _chain: &MiddlewareChain<BasicContext>) -> MiddlewareReturnValue<BasicContext> {
context.body = "world".to_owned();
Box::new(future::ok(context))
};

app.get("/test/hello", vec![test_fn_1]);

bench.iter(|| {
let mut bytes = BytesMut::with_capacity(47);
bytes.put(&b"GET /test/hello HTTP/1.1\nHost: localhost:8080\n\n"[..]);
let request = decode(&mut bytes).unwrap().unwrap();
let _response = app.resolve(request).wait().unwrap();
});
});
}

fn optimized_bench_route_match(c: &mut Criterion) {
c.bench_function("Optimized route match", |bench| {
let mut app = App::<BasicContext>::new();

fn test_fn_1(mut context: BasicContext, _chain: &MiddlewareChain<BasicContext>) -> MiddlewareReturnValue<BasicContext> {
context.body = "world".to_owned();
Box::new(future::ok(context))
};

app.get("/test/hello", vec![test_fn_1]);
app._route_parser.optimize();

bench.iter(|| {
let mut bytes = BytesMut::with_capacity(47);
Expand All @@ -39,12 +97,9 @@ fn bench_route_match_with_param(c: &mut Criterion) {
c.bench_function("Route match with route params", |bench| {
let mut app = App::<BasicContext>::new();

fn test_fn_1(context: BasicContext, _chain: &MiddlewareChain<BasicContext>) -> MiddlewareReturnValue<BasicContext> {
Box::new(future::ok(BasicContext {
body: context.params.get("hello").unwrap().to_owned(),
params: HashMap::new(),
query_params: context.query_params
}))
fn test_fn_1(mut context: BasicContext, _chain: &MiddlewareChain<BasicContext>) -> MiddlewareReturnValue<BasicContext> {
context.body = context.params.get("hello").unwrap().to_owned();
Box::new(future::ok(context))
};

app.get("/test/:hello", vec![test_fn_1]);
Expand All @@ -62,14 +117,12 @@ fn bench_route_match_with_query_param(c: &mut Criterion) {
c.bench_function("Route match with query params", |bench| {
let mut app = App::<BasicContext>::new();

fn test_fn_1(context: BasicContext, _chain: &MiddlewareChain<BasicContext>) -> MiddlewareReturnValue<BasicContext> {
Box::new(future::ok(BasicContext {
body: context.query_params.get("hello").unwrap().to_owned(),
params: HashMap::new(),
query_params: context.query_params
}))
fn test_fn_1(mut context: BasicContext, _chain: &MiddlewareChain<BasicContext>) -> MiddlewareReturnValue<BasicContext> {
context.body = context.query_params.get("hello").unwrap().to_owned();
Box::new(future::ok(context))
};

app.use_middleware("/", query_params);
app.get("/test", vec![test_fn_1]);

bench.iter(|| {
Expand All @@ -81,6 +134,14 @@ fn bench_route_match_with_query_param(c: &mut Criterion) {
});
}

criterion_group!(benches, bench_route_match, bench_route_match_with_param, bench_route_match_with_query_param);
criterion_group!(benches,
// bench_no_check,
// bench_assert,
// bench_expect,
// bench_remove,
optimized_bench_route_match,
bench_route_match,
bench_route_match_with_param,
bench_route_match_with_query_param);
criterion_main!(benches);

31 changes: 12 additions & 19 deletions examples/hello_world/context.rs
Expand Up @@ -8,9 +8,9 @@ pub struct Ctx {
pub path: String,
pub request_body: String,
pub params: HashMap<String, String>,
pub query_params: HashMap<String, String>,
pub headers: SmallVec<[(String, String); 8]>,
pub status_code: u32
pub status_code: u32,
response: Response
}

impl Ctx {
Expand All @@ -20,32 +20,25 @@ impl Ctx {
method: context.method,
path: context.path,
params: context.params,
query_params: context.query_params,
request_body: context.request_body,
headers: SmallVec::new(),
status_code: 200
status_code: 200,
response: Response::new()
}
}

pub fn set_header(&mut self, key: String, val: String) {
self.headers.push((key, val));
pub fn set_header(&mut self, key: &str, val: &str) {
self.response.header(key, val);
}
}

impl Context for Ctx {
fn get_response(&self) -> Response {
let mut response = Response::new();
response.status_code(200, "OK");
fn get_response(mut self) -> Response {
self.response.status_code(200, "OK");

for header_pair in &self.headers {
let key: &str = &header_pair.0;
let val: &str = &header_pair.1;
response.header(key, val);
}

response.body(&self.body);
self.response.body(&self.body);

response
self.response
}

fn set_body(&mut self, body: String) {
Expand All @@ -63,9 +56,9 @@ pub fn generate_context(request: Request) -> Ctx {
method: method,
path: path,
params: request.params,
query_params: request.query_params,
request_body: request_body,
headers: SmallVec::new(),
status_code: 200
status_code: 200,
response: Response::new()
}
}
9 changes: 4 additions & 5 deletions examples/hello_world/main.rs
Expand Up @@ -20,7 +20,7 @@ fn not_found_404(context: Ctx, _chain: &MiddlewareChain<Ctx>) -> MiddlewareRetur
context.body = "<html>
( ͡° ͜ʖ ͡°) What're you looking for here?
</html>".to_owned();
context.set_header("Content-Type".to_owned(), "text/html".to_owned());
context.set_header("Content-Type", "text/html");
context.status_code = 404;

Box::new(future::ok(context))
Expand All @@ -38,8 +38,7 @@ fn json(mut context: Ctx, _chain: &MiddlewareChain<Ctx>) -> MiddlewareReturnValu
let val = serde_json::to_string(&json).unwrap();

context.body = val;
context.set_header("Server".to_owned(), "thruster".to_owned());
context.set_header("Content-Type".to_owned(), "application/json".to_owned());
context.set_header("Content-Type", "application/json");

Box::new(future::ok(context))
}
Expand All @@ -48,8 +47,7 @@ fn plaintext(mut context: Ctx, _chain: &MiddlewareChain<Ctx>) -> MiddlewareRetur
let val = "Hello, World!".to_owned();

context.body = val;
context.set_header("Server".to_owned(), "thruster".to_owned());
context.set_header("Content-Type".to_owned(), "text/plain".to_owned());
context.set_header("Content-Type", "text/plain");

Box::new(future::ok(context))
}
Expand All @@ -61,6 +59,7 @@ fn main() {

app.get("/json", vec![json]);
app.get("/plaintext", vec![plaintext]);
app.post("/post-plaintext", vec![plaintext]);

app.set404(vec![not_found_404]);

Expand Down
6 changes: 4 additions & 2 deletions examples/most_basic.rs
@@ -1,6 +1,7 @@
extern crate thruster;
extern crate futures;

use std::collections::HashMap;
use std::boxed::Box;
use futures::future;

Expand All @@ -9,8 +10,9 @@ use thruster::{App, BasicContext as Ctx, MiddlewareChain, MiddlewareReturnValue,
fn generate_context(request: Request) -> Ctx {
Ctx {
body: "".to_owned(),
params: request.params,
query_params: request.query_params
params: HashMap::new(),
query_params: HashMap::new(),
request: request
}
}

Expand Down

0 comments on commit 0c98b08

Please sign in to comment.