Skip to content

Commit 8e4b70e

Browse files
committed
Merge branch 'master' into fix_test_response
2 parents dd278df + fcd4332 commit 8e4b70e

25 files changed

+484
-423
lines changed

.github/workflows/ci.yaml

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,13 @@ jobs:
4545
uses: actions-rs/cargo@v1
4646
with:
4747
command: check
48-
args: --all --benches --bins --examples --tests --features unstable
48+
args: --all --bins --examples --tests --features unstable
49+
50+
- name: check benches
51+
uses: actions-rs/cargo@v1
52+
with:
53+
command: check
54+
args: --benches --features __internal__bench
4955

5056
- name: tests
5157
uses: actions-rs/cargo@v1

Cargo.toml

Lines changed: 14 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[package]
22
name = "tide"
3-
version = "0.8.1"
3+
version = "0.9.0"
44
description = "Serve the web – HTTP server framework"
55
authors = [
66
"Aaron Turon <[email protected]>",
@@ -28,14 +28,15 @@ default = ["h1-server"]
2828
h1-server = ["async-h1"]
2929
docs = ["unstable"]
3030
unstable = []
31+
# DO NOT USE. Only exists to expose internals so they can be benchmarked.
32+
__internal__bench = []
3133

3234
[dependencies]
33-
async-h1 = { version = "1.1.2", optional = true }
35+
async-h1 = { version = "2.0.0", optional = true }
3436
async-sse = "2.1.0"
35-
async-std = { version = "1.4.0", features = ["unstable"] }
36-
cookie = { version = "0.12.0", features = ["percent-encode"]}
37+
async-std = { version = "1.6.0", features = ["unstable"] }
3738
femme = "2.0.1"
38-
http-types = "1.0.1"
39+
http-types = "2.0.0"
3940
kv-log-macro = "1.0.4"
4041
mime = "0.3.14"
4142
mime_guess = "2.0.3"
@@ -45,13 +46,19 @@ serde_json = "1.0.41"
4546
serde_qs = "0.5.0"
4647

4748
[dev-dependencies]
48-
async-std = { version = "1.4.0", features = ["unstable", "attributes"] }
49+
async-std = { version = "1.6.0", features = ["unstable", "attributes"] }
4950
juniper = "0.14.1"
5051
portpicker = "0.1.0"
5152
serde = { version = "1.0.102", features = ["derive"] }
52-
surf = "2.0.0-alpha.1"
53+
criterion = "0.3.1"
54+
surf = { version = "2.0.0-alpha.2", default-features = false, features = ["h1-client"] }
5355

5456
[[test]]
5557
name = "nested"
5658
path = "tests/nested.rs"
5759
required-features = ["unstable"]
60+
61+
[[bench]]
62+
name = "router"
63+
harness = false
64+

benches/router.rs

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
use criterion::{black_box, criterion_group, criterion_main, Criterion};
2+
use http_types::Method;
3+
use tide::router::Router;
4+
5+
fn criterion_benchmark(c: &mut Criterion) {
6+
let mut router = Router::<()>::new();
7+
router.add(
8+
"hello",
9+
Method::Get,
10+
Box::new(|_| async move { Ok("hello world") }),
11+
);
12+
13+
c.bench_function("route-match", |b| {
14+
b.iter(|| black_box(router.route("/hello", Method::Get)))
15+
});
16+
17+
c.bench_function("route-root", |b| {
18+
b.iter(|| black_box(router.route("", Method::Get)))
19+
});
20+
}
21+
22+
criterion_group!(benches, criterion_benchmark);
23+
criterion_main!(benches);

examples/fib.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ async fn fibsum(req: Request<()>) -> tide::Result<String> {
1717
// Compute the nth number in the fibonacci sequence
1818
let fib_n = fib(n);
1919
// Stop the stopwatch
20-
let duration = Instant::now().duration_since(start).as_secs();
20+
let duration = start.elapsed().as_secs();
2121
// Return the answer
2222
let res = format!(
2323
"The fib of {} is {}.\nIt was computed in {} secs.\n",

examples/graphql.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -95,7 +95,7 @@ async fn handle_graphql(mut cx: Request<State>) -> tide::Result {
9595
async fn handle_graphiql(_: Request<State>) -> tide::Result {
9696
let res = Response::new(StatusCode::Ok)
9797
.body_string(juniper::http::graphiql::graphiql_source("/graphql"))
98-
.set_header("content-type".parse().unwrap(), "text/html;charset=utf-8");
98+
.set_mime(mime::TEXT_HTML_UTF_8);
9999
Ok(res)
100100
}
101101

src/cookies/middleware.rs

Lines changed: 60 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ use crate::response::CookieEvent;
22
use crate::utils::BoxFuture;
33
use crate::{Middleware, Next, Request};
44

5-
use crate::http::cookies::{Cookie, CookieJar};
5+
use crate::http::cookies::{Cookie, CookieJar, Delta};
66
use crate::http::headers;
77

88
use std::sync::{Arc, RwLock};
@@ -13,19 +13,20 @@ use std::sync::{Arc, RwLock};
1313
///
1414
/// ```
1515
/// # use tide::{Request, Response, StatusCode};
16+
/// # use tide::http::cookies::Cookie;
1617
/// let mut app = tide::Server::new();
1718
/// app.at("/get").get(|cx: Request<()>| async move { Ok(cx.cookie("testCookie").unwrap().value().to_string()) });
1819
/// app.at("/set").get(|_| async {
1920
/// let mut res = Response::new(StatusCode::Ok);
20-
/// res.set_cookie(cookie::Cookie::new("testCookie", "NewCookieValue"));
21+
/// res.set_cookie(Cookie::new("testCookie", "NewCookieValue"));
2122
/// Ok(res)
2223
/// });
2324
/// ```
2425
#[derive(Debug, Clone, Default)]
2526
pub(crate) struct CookiesMiddleware;
2627

2728
impl CookiesMiddleware {
28-
/// Creates a new CookiesMiddleware.
29+
/// Creates a new `CookiesMiddleware`.
2930
pub fn new() -> Self {
3031
Self::default()
3132
}
@@ -38,30 +39,35 @@ impl<State: Send + Sync + 'static> Middleware<State> for CookiesMiddleware {
3839
next: Next<'a, State>,
3940
) -> BoxFuture<'a, crate::Result> {
4041
Box::pin(async move {
41-
let cookie_jar = if let Some(cookie_data) = ctx.local::<CookieData>() {
42+
let cookie_jar = if let Some(cookie_data) = ctx.ext::<CookieData>() {
4243
cookie_data.content.clone()
4344
} else {
44-
// no cookie data in local context, so we need to create it
4545
let cookie_data = CookieData::from_request(&ctx);
46+
// no cookie data in ext context, so we try to create it
4647
let content = cookie_data.content.clone();
47-
ctx = ctx.set_local(cookie_data);
48+
ctx = ctx.set_ext(cookie_data);
4849
content
4950
};
5051

5152
let mut res = next.run(ctx).await?;
5253

54+
// Don't do anything if there are no cookies.
55+
if res.cookie_events.is_empty() {
56+
return Ok(res);
57+
}
58+
59+
let jar = &mut *cookie_jar.write().unwrap();
60+
5361
// add modifications from response to original
5462
for cookie in res.cookie_events.drain(..) {
5563
match cookie {
56-
CookieEvent::Added(cookie) => cookie_jar.write().unwrap().add(cookie.clone()),
57-
CookieEvent::Removed(cookie) => {
58-
cookie_jar.write().unwrap().remove(cookie.clone())
59-
}
64+
CookieEvent::Added(cookie) => jar.add(cookie.clone()),
65+
CookieEvent::Removed(cookie) => jar.remove(cookie.clone()),
6066
}
6167
}
6268

6369
// iterate over added and removed cookies
64-
for cookie in cookie_jar.read().unwrap().delta() {
70+
for cookie in jar.delta() {
6571
let encoded_cookie = cookie.encoded().to_string();
6672
res = res.append_header(headers::SET_COOKIE, encoded_cookie);
6773
}
@@ -70,25 +76,61 @@ impl<State: Send + Sync + 'static> Middleware<State> for CookiesMiddleware {
7076
}
7177
}
7278

73-
#[derive(Debug, Clone)]
79+
#[derive(Debug, Default, Clone)]
7480
pub(crate) struct CookieData {
75-
pub(crate) content: Arc<RwLock<CookieJar>>,
81+
pub(crate) content: Arc<RwLock<LazyJar>>,
82+
}
83+
84+
#[derive(Debug, Default, Clone)]
85+
/// Wrapper around `CookieJar`, that initializes only when actually used.
86+
pub(crate) struct LazyJar(Option<CookieJar>);
87+
88+
impl LazyJar {
89+
fn add(&mut self, cookie: Cookie<'static>) {
90+
self.get_jar().add(cookie)
91+
}
92+
93+
fn remove(&mut self, cookie: Cookie<'static>) {
94+
self.get_jar().remove(cookie)
95+
}
96+
97+
fn delta(&mut self) -> Delta<'_> {
98+
self.get_jar().delta()
99+
}
100+
101+
pub(crate) fn get(&self, name: &str) -> Option<&Cookie<'static>> {
102+
if let Some(jar) = &self.0 {
103+
return jar.get(name);
104+
}
105+
None
106+
}
107+
108+
fn get_jar(&mut self) -> &mut CookieJar {
109+
if self.0.is_none() {
110+
self.0 = Some(CookieJar::new());
111+
}
112+
113+
self.0.as_mut().unwrap()
114+
}
76115
}
77116

78117
impl CookieData {
79118
pub(crate) fn from_request<S>(req: &Request<S>) -> Self {
80-
let mut jar = CookieJar::new();
81-
82-
if let Some(cookie_headers) = req.header(&headers::COOKIE) {
119+
let jar = if let Some(cookie_headers) = req.header(&headers::COOKIE) {
120+
let mut jar = CookieJar::new();
83121
for cookie_header in cookie_headers {
84122
// spec says there should be only one, so this is permissive
85-
for pair in cookie_header.as_str().split(";") {
123+
for pair in cookie_header.as_str().split(';') {
86124
if let Ok(cookie) = Cookie::parse_encoded(String::from(pair)) {
87125
jar.add_original(cookie);
88126
}
89127
}
90128
}
91-
}
129+
130+
LazyJar(Some(jar))
131+
} else {
132+
LazyJar::default()
133+
};
92134

93135
CookieData {
94136
content: Arc::new(RwLock::new(jar)),

src/endpoint.rs

Lines changed: 4 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -27,10 +27,8 @@ use crate::{Middleware, Request, Response};
2727
/// Ok(String::from("hello"))
2828
/// }
2929
///
30-
/// fn main() {
31-
/// let mut app = tide::Server::new();
32-
/// app.at("/hello").get(hello);
33-
/// }
30+
/// let mut app = tide::Server::new();
31+
/// app.at("/hello").get(hello);
3432
/// ```
3533
///
3634
/// An endpoint with similar functionality that does not make use of the `async` keyword would look something like this:
@@ -41,10 +39,8 @@ use crate::{Middleware, Request, Response};
4139
/// async_std::future::ready(Ok(String::from("hello")))
4240
/// }
4341
///
44-
/// fn main() {
45-
/// let mut app = tide::Server::new();
46-
/// app.at("/hello").get(hello);
47-
/// }
42+
/// let mut app = tide::Server::new();
43+
/// app.at("/hello").get(hello);
4844
/// ```
4945
///
5046
/// Tide routes will also accept endpoints with `Fn` signatures of this form, but using the `async` keyword has better ergonomics.

src/fs/serve_dir.rs

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -57,12 +57,11 @@ impl<State> Endpoint<State> for ServeDir {
5757
Ok(file) => file,
5858
};
5959

60-
let len = match file.metadata().await {
61-
Ok(metadata) => metadata.len() as usize,
62-
Err(_) => {
63-
log::warn!("Could not retrieve metadata");
64-
return Ok(Response::new(StatusCode::InternalServerError));
65-
}
60+
let len = if let Ok(metadata) = file.metadata().await {
61+
metadata.len() as usize
62+
} else {
63+
log::warn!("Could not retrieve metadata");
64+
return Ok(Response::new(StatusCode::InternalServerError));
6665
};
6766

6867
let body = Body::from_reader(BufReader::new(file), Some(len));

src/lib.rs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -193,7 +193,11 @@ mod redirect;
193193
mod request;
194194
mod response;
195195
mod route;
196+
197+
#[cfg(not(feature = "__internal__bench"))]
196198
mod router;
199+
#[cfg(feature = "__internal__bench")]
200+
pub mod router;
197201
mod server;
198202
mod utils;
199203

@@ -227,6 +231,7 @@ pub use http_types::{self as http, Body, Error, Status, StatusCode};
227231
/// #
228232
/// # Ok(()) }) }
229233
/// ```
234+
#[must_use]
230235
pub fn new() -> server::Server<()> {
231236
Server::new()
232237
}

src/log/middleware.rs

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -12,13 +12,14 @@ use crate::{Middleware, Next, Request};
1212
/// let mut app = tide::Server::new();
1313
/// app.middleware(tide::log::LogMiddleware::new());
1414
/// ```
15-
#[derive(Debug, Clone)]
15+
#[derive(Debug, Default, Clone)]
1616
pub struct LogMiddleware {
1717
_priv: (),
1818
}
1919

2020
impl LogMiddleware {
2121
/// Create a new instance of `LogMiddleware`.
22+
#[must_use]
2223
pub fn new() -> Self {
2324
Self { _priv: () }
2425
}
@@ -44,21 +45,21 @@ impl LogMiddleware {
4445
method: method,
4546
path: path,
4647
status: status as u16,
47-
duration: format!("{}ms", start.elapsed().as_millis()),
48+
duration: format!("{:?}", start.elapsed()),
4849
});
4950
} else if status.is_client_error() {
5051
log::warn!("--> Response sent", {
5152
method: method,
5253
path: path,
5354
status: status as u16,
54-
duration: format!("{}ms", start.elapsed().as_millis()),
55+
duration: format!("{:?}", start.elapsed()),
5556
});
5657
} else {
5758
log::info!("--> Response sent", {
5859
method: method,
5960
path: path,
6061
status: status as u16,
61-
duration: format!("{}ms", start.elapsed().as_millis()),
62+
duration: format!("{:?}", start.elapsed()),
6263
});
6364
}
6465
Ok(res)
@@ -68,7 +69,7 @@ impl LogMiddleware {
6869
method: method,
6970
path: path,
7071
status: err.status() as u16,
71-
duration: format!("{}ms", start.elapsed().as_millis()),
72+
duration: format!("{:?}", start.elapsed()),
7273
});
7374
Err(err)
7475
}

src/middleware.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@ pub struct Next<'a, State> {
4747

4848
impl<'a, State: 'static> Next<'a, State> {
4949
/// Asynchronously execute the remaining middleware chain.
50+
#[must_use]
5051
pub fn run(mut self, req: Request<State>) -> BoxFuture<'a, crate::Result> {
5152
if let Some((current, next)) = self.next_middleware.split_first() {
5253
self.next_middleware = next;

src/redirect.rs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
//! # Ok(()) }) }
1717
//! ```
1818
19+
use crate::http::headers::LOCATION;
1920
use crate::utils::BoxFuture;
2021
use crate::StatusCode;
2122
use crate::{Endpoint, Request, Response};
@@ -103,6 +104,6 @@ impl<T: AsRef<str>> Into<Response> for Redirect<T> {
103104

104105
impl<T: AsRef<str>> Into<Response> for &Redirect<T> {
105106
fn into(self) -> Response {
106-
Response::new(self.status).set_header("location".parse().unwrap(), &self.location)
107+
Response::new(self.status).set_header(LOCATION, self.location.as_ref())
107108
}
108109
}

0 commit comments

Comments
 (0)