Add blocking variant of 'local'.

This commit adds the 'local::blocking' module and moves the existing
asynchronous testing to 'local::asynchronous'. It also includes several
changes to improve the local API, bringing it to parity (and beyond)
with master. These changes are:

  * 'LocalRequest' implements 'Clone'.
  * 'LocalResponse' doesn't implement 'DerefMut<Target=Response>'.
    Instead, direct methods on the type, such as 'into_string()', can
    be used to read the 'Response'.
  * 'Response::body()' returns an '&ResponseBody' as opposed to '&mut
    ResponseBody', which is returned by a new 'Response::body_mut()'.
  * '&ResponseBody' implements 'known_size()` to retrieve a body's size,
    if it is known.

Co-authored-by: Jeb Rosen <jeb@jebrosen.com>
This commit is contained in:
Sergio Benitez 2020-06-22 04:54:34 -07:00
parent 824de061c3
commit 03127f4dae
84 changed files with 1513 additions and 1408 deletions

View File

@ -328,7 +328,7 @@ impl Template {
/// use std::collections::HashMap;
///
/// use rocket_contrib::templates::Template;
/// use rocket::local::Client;
/// use rocket::local::asynchronous::Client;
///
/// fn main() {
/// # rocket::async_test(async {

View File

@ -9,7 +9,7 @@ mod compress_responder_tests {
use rocket::http::hyper::header::{ContentEncoding, Encoding};
use rocket::http::Status;
use rocket::http::{ContentType, Header};
use rocket::local::Client;
use rocket::local::asynchronous::Client;
use rocket::response::{Content, Response};
use rocket_contrib::compression::Compress;
@ -80,7 +80,7 @@ mod compress_responder_tests {
.any(|x| x == "br"));
let mut body_plain = Cursor::new(Vec::<u8>::new());
brotli::BrotliDecompress(
&mut Cursor::new(response.body_bytes().unwrap()),
&mut Cursor::new(response.into_bytes().unwrap()),
&mut body_plain,
)
.expect("decompress response");
@ -104,7 +104,7 @@ mod compress_responder_tests {
.any(|x| x == "br"));
let mut body_plain = Cursor::new(Vec::<u8>::new());
brotli::BrotliDecompress(
&mut Cursor::new(response.body_bytes().unwrap()),
&mut Cursor::new(response.into_bytes().unwrap()),
&mut body_plain,
)
.expect("decompress response");
@ -131,7 +131,7 @@ mod compress_responder_tests {
.get("Content-Encoding")
.any(|x| x == "gzip"));
let mut s = String::new();
GzDecoder::new(&response.body_bytes().unwrap()[..])
GzDecoder::new(&response.into_bytes().unwrap()[..])
.read_to_string(&mut s)
.expect("decompress response");
assert_eq!(s, String::from(HELLO));
@ -154,7 +154,7 @@ mod compress_responder_tests {
.get("Content-Encoding")
.any(|x| x == "gzip"));
let mut s = String::new();
GzDecoder::new(&response.body_bytes().unwrap()[..])
GzDecoder::new(&response.into_bytes().unwrap()[..])
.read_to_string(&mut s)
.expect("decompress response");
assert_eq!(s, String::from(HELLO));
@ -173,7 +173,7 @@ mod compress_responder_tests {
.get("Content-Encoding")
.any(|x| x != "identity"));
assert_eq!(
String::from_utf8(response.body_bytes().unwrap()).unwrap(),
String::from_utf8(response.into_bytes().unwrap()).unwrap(),
String::from(HELLO)
);
}
@ -192,7 +192,7 @@ mod compress_responder_tests {
.any(|x| x == "br"));
let mut body_plain = Cursor::new(Vec::<u8>::new());
brotli::BrotliDecompress(
&mut Cursor::new(response.body_bytes().unwrap()),
&mut Cursor::new(response.into_bytes().unwrap()),
&mut body_plain,
)
.expect("decompress response");
@ -215,7 +215,7 @@ mod compress_responder_tests {
.get("Content-Encoding")
.any(|x| x != "identity"));
assert_eq!(
String::from_utf8(response.body_bytes().unwrap()).unwrap(),
String::from_utf8(response.into_bytes().unwrap()).unwrap(),
String::from(HELLO)
);
}
@ -233,7 +233,7 @@ mod compress_responder_tests {
.get("Content-Encoding")
.any(|x| x != "identity"));
assert_eq!(
String::from_utf8(response.body_bytes().unwrap()).unwrap(),
String::from_utf8(response.into_bytes().unwrap()).unwrap(),
String::from(HELLO)
);
}

View File

@ -10,7 +10,7 @@ mod compression_fairing_tests {
use rocket::http::hyper::header::{ContentEncoding, Encoding};
use rocket::http::Status;
use rocket::http::{ContentType, Header};
use rocket::local::Client;
use rocket::local::asynchronous::Client;
use rocket::response::{Content, Response};
use rocket_contrib::compression::Compression;
@ -99,7 +99,7 @@ mod compression_fairing_tests {
.any(|x| x == "br"));
let mut body_plain = Cursor::new(Vec::<u8>::new());
brotli::BrotliDecompress(
&mut Cursor::new(response.body_bytes().unwrap()),
&mut Cursor::new(response.into_bytes().unwrap()),
&mut body_plain,
)
.expect("decompress response");
@ -123,7 +123,7 @@ mod compression_fairing_tests {
.any(|x| x == "br"));
let mut body_plain = Cursor::new(Vec::<u8>::new());
brotli::BrotliDecompress(
&mut Cursor::new(response.body_bytes().unwrap()),
&mut Cursor::new(response.into_bytes().unwrap()),
&mut body_plain,
)
.expect("decompress response");
@ -150,7 +150,7 @@ mod compression_fairing_tests {
.get("Content-Encoding")
.any(|x| x == "gzip"));
let mut s = String::new();
GzDecoder::new(&response.body_bytes().unwrap()[..])
GzDecoder::new(&response.into_bytes().unwrap()[..])
.read_to_string(&mut s)
.expect("decompress response");
assert_eq!(s, String::from(HELLO));
@ -173,7 +173,7 @@ mod compression_fairing_tests {
.get("Content-Encoding")
.any(|x| x == "gzip"));
let mut s = String::new();
GzDecoder::new(&response.body_bytes().unwrap()[..])
GzDecoder::new(&response.into_bytes().unwrap()[..])
.read_to_string(&mut s)
.expect("decompress response");
assert_eq!(s, String::from(HELLO));
@ -192,7 +192,7 @@ mod compression_fairing_tests {
.get("Content-Encoding")
.any(|x| x != "identity"));
assert_eq!(
String::from_utf8(response.body_bytes().unwrap()).unwrap(),
String::from_utf8(response.into_bytes().unwrap()).unwrap(),
String::from(HELLO)
);
}
@ -210,7 +210,7 @@ mod compression_fairing_tests {
.get("Content-Encoding")
.any(|x| x != "identity"));
assert_eq!(
String::from_utf8(response.body_bytes().unwrap()).unwrap(),
String::from_utf8(response.into_bytes().unwrap()).unwrap(),
String::from(HELLO)
);
}
@ -228,7 +228,7 @@ mod compression_fairing_tests {
.get("Content-Encoding")
.any(|x| x != "identity"));
assert_eq!(
String::from_utf8(response.body_bytes().unwrap()).unwrap(),
String::from_utf8(response.into_bytes().unwrap()).unwrap(),
String::from(HELLO)
);
}
@ -246,7 +246,7 @@ mod compression_fairing_tests {
.get("Content-Encoding")
.any(|x| x != "identity"));
assert_eq!(
String::from_utf8(response.body_bytes().unwrap()).unwrap(),
String::from_utf8(response.into_bytes().unwrap()).unwrap(),
String::from(HELLO)
);
}
@ -264,7 +264,7 @@ mod compression_fairing_tests {
.get("Content-Encoding")
.any(|x| x != "identity"));
assert_eq!(
String::from_utf8(response.body_bytes().unwrap()).unwrap(),
String::from_utf8(response.into_bytes().unwrap()).unwrap(),
String::from(HELLO)
);
}
@ -283,7 +283,7 @@ mod compression_fairing_tests {
.any(|x| x == "br"));
let mut body_plain = Cursor::new(Vec::<u8>::new());
brotli::BrotliDecompress(
&mut Cursor::new(response.body_bytes().unwrap()),
&mut Cursor::new(response.into_bytes().unwrap()),
&mut body_plain,
)
.expect("decompress response");

View File

@ -7,7 +7,7 @@ extern crate rocket;
#[cfg(feature = "helmet")]
mod helmet_tests {
use rocket::http::{Status, uri::Uri};
use rocket::local::{Client, LocalResponse};
use rocket::local::asynchronous::{Client, LocalResponse};
use rocket_contrib::helmet::*;
use time::Duration;

View File

@ -8,7 +8,7 @@ mod static_tests {
use rocket::{self, Rocket, Route};
use rocket_contrib::serve::{StaticFiles, Options};
use rocket::http::Status;
use rocket::local::Client;
use rocket::local::asynchronous::Client;
fn static_root() -> PathBuf {
Path::new(env!("CARGO_MANIFEST_DIR"))
@ -47,7 +47,7 @@ mod static_tests {
async fn assert_file(client: &Client, prefix: &str, path: &str, exists: bool) {
let full_path = format!("/{}/{}", prefix, path);
let mut response = client.get(full_path).dispatch().await;
let response = client.get(full_path).dispatch().await;
if exists {
assert_eq!(response.status(), Status::Ok);
@ -59,7 +59,7 @@ mod static_tests {
let mut file = File::open(path).expect("open file");
let mut expected_contents = String::new();
file.read_to_string(&mut expected_contents).expect("read file");
assert_eq!(response.body_string().await, Some(expected_contents));
assert_eq!(response.into_string().await, Some(expected_contents));
} else {
assert_eq!(response.status(), Status::NotFound);
}
@ -135,13 +135,13 @@ mod static_tests {
let rocket = rocket().mount("/default", routes![catch_one, catch_two]);
let client = Client::new(rocket).await.expect("valid rocket");
let mut response = client.get("/default/ireallydontexist").dispatch().await;
let response = client.get("/default/ireallydontexist").dispatch().await;
assert_eq!(response.status(), Status::Ok);
assert_eq!(response.body_string().await.unwrap(), "ireallydontexist");
assert_eq!(response.into_string().await.unwrap(), "ireallydontexist");
let mut response = client.get("/default/idont/exist").dispatch().await;
let response = client.get("/default/idont/exist").dispatch().await;
assert_eq!(response.status(), Status::Ok);
assert_eq!(response.body_string().await.unwrap(), "idont/exist");
assert_eq!(response.into_string().await.unwrap(), "idont/exist");
assert_all(&client, "both", REGULAR_FILES, true).await;
assert_all(&client, "both", HIDDEN_FILES, true).await;

View File

@ -42,7 +42,7 @@ mod templates_tests {
use super::*;
use std::collections::HashMap;
use rocket::http::Status;
use rocket::local::Client;
use rocket::local::asynchronous::Client;
const UNESCAPED_EXPECTED: &'static str
= "\nh_start\ntitle: _test_\nh_end\n\n\n<script />\n\nfoot\n";
@ -89,7 +89,7 @@ mod templates_tests {
use super::*;
use std::collections::HashMap;
use rocket::http::Status;
use rocket::local::Client;
use rocket::local::asynchronous::Client;
const EXPECTED: &'static str
= "Hello _test_!\n\n<main> &lt;script /&gt; hi </main>\nDone.\n\n";
@ -128,7 +128,7 @@ mod templates_tests {
use std::io::Write;
use std::time::Duration;
use rocket::local::Client;
use rocket::local::asynchronous::Client;
const RELOAD_TEMPLATE: &str = "hbs/reload";
const INITIAL_TEXT: &str = "initial";

View File

@ -2,7 +2,7 @@
#[macro_use] extern crate rocket;
use rocket::local::Client;
use rocket::local::asynchronous::Client;
#[get("/easy/<id>")]
fn easy(id: i32) -> String {
@ -38,14 +38,14 @@ async fn test_reexpansion() {
let rocket = rocket::ignite().mount("/", routes![easy, hard, hi]);
let client = Client::new(rocket).await.unwrap();
let mut response = client.get("/easy/327").dispatch().await;
assert_eq!(response.body_string().await.unwrap(), "easy id: 327");
let response = client.get("/easy/327").dispatch().await;
assert_eq!(response.into_string().await.unwrap(), "easy id: 327");
let mut response = client.get("/hard/72").dispatch().await;
assert_eq!(response.body_string().await.unwrap(), "hard id: 72");
let response = client.get("/hard/72").dispatch().await;
assert_eq!(response.into_string().await.unwrap(), "hard id: 72");
let mut response = client.get("/hello/fish").dispatch().await;
assert_eq!(response.body_string().await.unwrap(), "fish");
let response = client.get("/hello/fish").dispatch().await;
assert_eq!(response.into_string().await.unwrap(), "fish");
}
macro_rules! index {
@ -64,6 +64,6 @@ async fn test_index() {
let rocket = rocket::ignite().mount("/", routes![index]).manage(100i32);
let client = Client::new(rocket).await.unwrap();
let mut response = client.get("/").dispatch().await;
assert_eq!(response.body_string().await.unwrap(), "Thing: 100");
let response = client.get("/").dispatch().await;
assert_eq!(response.into_string().await.unwrap(), "Thing: 100");
}

View File

@ -1,6 +1,6 @@
#![feature(proc_macro_hygiene)]
use rocket::local::Client;
use rocket::local::asynchronous::Client;
use rocket::response::Responder;
use rocket::http::{Status, ContentType, Cookie};

View File

@ -3,7 +3,7 @@
#[macro_use] extern crate rocket;
use rocket::{Request, Data, Outcome::*};
use rocket::local::Client;
use rocket::local::asynchronous::Client;
use rocket::request::Form;
use rocket::data::{self, FromDataSimple};
use rocket::http::{RawStr, ContentType, Status};
@ -46,16 +46,16 @@ async fn test_data() {
let rocket = rocket::ignite().mount("/", routes![form, simple]);
let client = Client::new(rocket).await.unwrap();
let mut response = client.post("/f")
let response = client.post("/f")
.header(ContentType::Form)
.body("field=this%20is%20here")
.dispatch().await;
assert_eq!(response.body_string().await.unwrap(), "this is here");
assert_eq!(response.into_string().await.unwrap(), "this is here");
let mut response = client.post("/s").body("this is here").dispatch().await;
assert_eq!(response.body_string().await.unwrap(), "this is here");
let response = client.post("/s").body("this is here").dispatch().await;
assert_eq!(response.into_string().await.unwrap(), "this is here");
let mut response = client.post("/s").body("this%20is%20here").dispatch().await;
assert_eq!(response.body_string().await.unwrap(), "this%20is%20here");
let response = client.post("/s").body("this%20is%20here").dispatch().await;
assert_eq!(response.into_string().await.unwrap(), "this%20is%20here");
}

View File

@ -2,7 +2,7 @@
#[macro_use] extern crate rocket;
use rocket::local::Client;
use rocket::local::asynchronous::Client;
use rocket::http::{ContentType, MediaType, Accept, Status};
// Test that known formats work as expected, including not colliding.
@ -41,26 +41,26 @@ async fn test_formats() {
let client = Client::new(rocket).await.unwrap();
let mut response = client.post("/").header(ContentType::JSON).dispatch().await;
assert_eq!(response.body_string().await.unwrap(), "json");
let response = client.post("/").header(ContentType::JSON).dispatch().await;
assert_eq!(response.into_string().await.unwrap(), "json");
let mut response = client.post("/").header(ContentType::MsgPack).dispatch().await;
assert_eq!(response.body_string().await.unwrap(), "msgpack_long");
let response = client.post("/").header(ContentType::MsgPack).dispatch().await;
assert_eq!(response.into_string().await.unwrap(), "msgpack_long");
let mut response = client.post("/").header(ContentType::XML).dispatch().await;
assert_eq!(response.body_string().await.unwrap(), "xml");
let response = client.post("/").header(ContentType::XML).dispatch().await;
assert_eq!(response.into_string().await.unwrap(), "xml");
let mut response = client.get("/").header(Accept::Plain).dispatch().await;
assert_eq!(response.body_string().await.unwrap(), "plain");
let response = client.get("/").header(Accept::Plain).dispatch().await;
assert_eq!(response.into_string().await.unwrap(), "plain");
let mut response = client.get("/").header(Accept::Binary).dispatch().await;
assert_eq!(response.body_string().await.unwrap(), "binary");
let response = client.get("/").header(Accept::Binary).dispatch().await;
assert_eq!(response.into_string().await.unwrap(), "binary");
let mut response = client.get("/").header(ContentType::JSON).dispatch().await;
assert_eq!(response.body_string().await.unwrap(), "plain");
let response = client.get("/").header(ContentType::JSON).dispatch().await;
assert_eq!(response.into_string().await.unwrap(), "plain");
let mut response = client.get("/").dispatch().await;
assert_eq!(response.body_string().await.unwrap(), "plain");
let response = client.get("/").dispatch().await;
assert_eq!(response.into_string().await.unwrap(), "plain");
let response = client.put("/").header(ContentType::HTML).dispatch().await;
assert_eq!(response.status(), Status::NotFound);
@ -92,20 +92,20 @@ async fn test_custom_formats() {
let bar_baz_ct = ContentType::new("bar", "baz");
let bar_baz_a = Accept::new(&[MediaType::new("bar", "baz").into()]);
let mut response = client.get("/").header(foo_a).dispatch().await;
assert_eq!(response.body_string().await.unwrap(), "get_foo");
let response = client.get("/").header(foo_a).dispatch().await;
assert_eq!(response.into_string().await.unwrap(), "get_foo");
let mut response = client.post("/").header(foo_ct).dispatch().await;
assert_eq!(response.body_string().await.unwrap(), "post_foo");
let response = client.post("/").header(foo_ct).dispatch().await;
assert_eq!(response.into_string().await.unwrap(), "post_foo");
let mut response = client.get("/").header(bar_baz_a).dispatch().await;
assert_eq!(response.body_string().await.unwrap(), "get_bar_baz");
let response = client.get("/").header(bar_baz_a).dispatch().await;
assert_eq!(response.into_string().await.unwrap(), "get_bar_baz");
let mut response = client.put("/").header(bar_baz_ct).dispatch().await;
assert_eq!(response.body_string().await.unwrap(), "put_bar_baz");
let response = client.put("/").header(bar_baz_ct).dispatch().await;
assert_eq!(response.into_string().await.unwrap(), "put_bar_baz");
let mut response = client.get("/").dispatch().await;
assert_eq!(response.body_string().await.unwrap(), "get_foo");
let response = client.get("/").dispatch().await;
assert_eq!(response.into_string().await.unwrap(), "get_foo");
let response = client.put("/").header(ContentType::HTML).dispatch().await;
assert_eq!(response.status(), Status::NotFound);

View File

@ -2,7 +2,7 @@
#[macro_use] extern crate rocket;
use rocket::local::Client;
use rocket::local::asynchronous::Client;
// Test that manual/auto ranking works as expected.
@ -23,17 +23,17 @@ async fn test_ranking() {
let rocket = rocket::ignite().mount("/", routes![get0, get1, get2, get3]);
let client = Client::new(rocket).await.unwrap();
let mut response = client.get("/0").dispatch().await;
assert_eq!(response.body_string().await.unwrap(), "0");
let response = client.get("/0").dispatch().await;
assert_eq!(response.into_string().await.unwrap(), "0");
let mut response = client.get(format!("/{}", 1 << 8)).dispatch().await;
assert_eq!(response.body_string().await.unwrap(), "1");
let response = client.get(format!("/{}", 1 << 8)).dispatch().await;
assert_eq!(response.into_string().await.unwrap(), "1");
let mut response = client.get(format!("/{}", 1 << 16)).dispatch().await;
assert_eq!(response.body_string().await.unwrap(), "2");
let response = client.get(format!("/{}", 1 << 16)).dispatch().await;
assert_eq!(response.into_string().await.unwrap(), "2");
let mut response = client.get(format!("/{}", 1u64 << 32)).dispatch().await;
assert_eq!(response.body_string().await.unwrap(), "3");
let response = client.get(format!("/{}", 1u64 << 32)).dispatch().await;
assert_eq!(response.into_string().await.unwrap(), "3");
}
// Test a collision due to same auto rank.

View File

@ -11,7 +11,7 @@ use std::path::PathBuf;
use rocket::{Request, Outcome::*};
use rocket::http::ext::Normalize;
use rocket::local::Client;
use rocket::local::asynchronous::Client;
use rocket::data::{self, Data, FromDataSimple};
use rocket::request::Form;
use rocket::http::{Status, RawStr, ContentType};
@ -105,24 +105,24 @@ async fn test_full_route() {
let response = client.post(format!("/1{}", uri)).body(simple).dispatch().await;
assert_eq!(response.status(), Status::NotFound);
let mut response = client
let response = client
.post(format!("/1{}", uri))
.header(ContentType::JSON)
.body(simple)
.dispatch().await;
assert_eq!(response.body_string().await.unwrap(), format!("({}, {}, {}, {}, {}, {}) ({})",
assert_eq!(response.into_string().await.unwrap(), format!("({}, {}, {}, {}, {}, {}) ({})",
sky, name, "A A", "inside", path, simple, expected_uri));
let response = client.post(format!("/2{}", uri)).body(simple).dispatch().await;
assert_eq!(response.status(), Status::NotFound);
let mut response = client
let response = client
.post(format!("/2{}", uri))
.header(ContentType::JSON)
.body(simple)
.dispatch().await;
assert_eq!(response.body_string().await.unwrap(), format!("({}, {}, {}, {}, {}, {}) ({})",
assert_eq!(response.into_string().await.unwrap(), format!("({}, {}, {}, {}, {}, {}) ({})",
sky, name, "A A", "inside", path, simple, expected_uri));
}

View File

@ -39,6 +39,7 @@ pear = "0.1"
atty = "0.2"
async-trait = "0.1"
ref-cast = "1.0"
atomic = "0.4"
[dependencies.tokio]
version = "0.2.9"

View File

@ -22,7 +22,7 @@ mod benches {
use super::rocket;
use self::test::Bencher;
use rocket::local::Client;
use rocket::local::blocking::Client;
use rocket::http::{Accept, ContentType};
fn client(_rocket: rocket::Rocket) -> Option<Client> {
@ -32,28 +32,28 @@ mod benches {
#[bench]
fn accept_format(b: &mut Bencher) {
let client = client(rocket()).unwrap();
let mut request = client.get("/").header(Accept::JSON);
b.iter(|| { request.mut_dispatch(); });
let request = client.get("/").header(Accept::JSON);
b.iter(|| { request.clone().dispatch(); });
}
#[bench]
fn wrong_accept_format(b: &mut Bencher) {
let client = client(rocket()).unwrap();
let mut request = client.get("/").header(Accept::HTML);
b.iter(|| { request.mut_dispatch(); });
let request = client.get("/").header(Accept::HTML);
b.iter(|| { request.clone().dispatch(); });
}
#[bench]
fn content_type_format(b: &mut Bencher) {
let client = client(rocket()).unwrap();
let mut request = client.post("/").header(ContentType::JSON);
b.iter(|| { request.mut_dispatch(); });
let request = client.post("/").header(ContentType::JSON);
b.iter(|| { request.clone().dispatch(); });
}
#[bench]
fn wrong_content_type_format(b: &mut Bencher) {
let client = client(rocket()).unwrap();
let mut request = client.post("/").header(ContentType::Plain);
b.iter(|| { request.mut_dispatch(); });
let request = client.post("/").header(ContentType::Plain);
b.iter(|| { request.clone().dispatch(); });
}
}

View File

@ -36,7 +36,7 @@ mod benches {
use super::rocket;
use self::test::Bencher;
use rocket::local::Client;
use rocket::local::blocking::Client;
use rocket::http::{Accept, ContentType};
fn client(_rocket: rocket::Rocket) -> Option<Client> {
@ -46,14 +46,15 @@ mod benches {
#[bench]
fn accept_format(b: &mut Bencher) {
let client = client(rocket()).unwrap();
let mut requests = vec![];
requests.push(client.get("/").header(Accept::JSON));
requests.push(client.get("/").header(Accept::HTML));
requests.push(client.get("/").header(Accept::Plain));
let requests = vec![
client.get("/").header(Accept::JSON),
client.get("/").header(Accept::HTML),
client.get("/").header(Accept::Plain),
];
b.iter(|| {
for request in requests.iter_mut() {
request.mut_dispatch();
for request in &requests {
request.clone().dispatch();
}
});
}
@ -61,14 +62,15 @@ mod benches {
#[bench]
fn content_type_format(b: &mut Bencher) {
let client = client(rocket()).unwrap();
let mut requests = vec![];
requests.push(client.post("/").header(ContentType::JSON));
requests.push(client.post("/").header(ContentType::HTML));
requests.push(client.post("/").header(ContentType::Plain));
let requests = vec![
client.post("/").header(ContentType::JSON),
client.post("/").header(ContentType::HTML),
client.post("/").header(ContentType::Plain),
];
b.iter(|| {
for request in requests.iter_mut() {
request.mut_dispatch();
for request in &requests {
request.clone().dispatch();
}
});
}

View File

@ -48,7 +48,7 @@ mod benches {
use super::{hello_world_rocket, rocket};
use self::test::Bencher;
use rocket::local::Client;
use rocket::local::blocking::Client;
fn client(_rocket: rocket::Rocket) -> Option<Client> {
unimplemented!("waiting for sync-client");
@ -57,20 +57,18 @@ mod benches {
#[bench]
fn bench_hello_world(b: &mut Bencher) {
let client = client(hello_world_rocket()).unwrap();
let mut request = client.get("/");
b.iter(|| {
request.mut_dispatch();
client.get("/").dispatch();
});
}
#[bench]
fn bench_single_get_index(b: &mut Bencher) {
let client = client(rocket()).unwrap();
let mut request = client.get("/");
b.iter(|| {
request.mut_dispatch();
client.get("/").dispatch();
});
}
@ -85,8 +83,8 @@ mod benches {
requests.push(client.post("/"));
b.iter(|| {
for request in requests.iter_mut() {
request.mut_dispatch();
for request in &requests {
request.clone().dispatch();
}
});
}
@ -102,8 +100,8 @@ mod benches {
requests.push(client.get("/123"));
b.iter(|| {
for request in requests.iter_mut() {
request.mut_dispatch();
for request in &requests {
request.clone().dispatch();
}
});
}
@ -125,8 +123,8 @@ mod benches {
requests.push(client.get("/123"));
b.iter(|| {
for request in requests.iter_mut() {
request.mut_dispatch();
for request in &requests {
request.clone().dispatch();
}
});
}

View File

@ -0,0 +1,64 @@
use std::sync::RwLock;
use std::borrow::Cow;
use crate::local::asynchronous::LocalRequest;
use crate::rocket::{Rocket, Cargo};
use crate::http::{Method, private::CookieJar};
use crate::error::LaunchError;
pub struct Client {
cargo: Cargo,
pub(crate) cookies: Option<RwLock<CookieJar>>,
}
impl Client {
pub(crate) async fn _new(
mut rocket: Rocket,
tracked: bool
) -> Result<Client, LaunchError> {
rocket.prelaunch_check().await?;
let cookies = match tracked {
true => Some(RwLock::new(CookieJar::new())),
false => None
};
Ok(Client { cargo: rocket.into_cargo().await, cookies })
}
#[doc(hidden)]
/// WARNING: This is unstable! Do not use this method outside of Rocket!
pub fn _test<T, F: FnOnce(Self) -> T + Send>(f: F) -> T {
crate::async_test(async {
let rocket = crate::ignite();
let client = Client::new(rocket).await.expect("valid rocket");
f(client)
})
}
#[inline(always)]
pub(crate) fn _cargo(&self) -> &Cargo {
&self.cargo
}
#[inline(always)]
fn _req<'c, 'u: 'c, U>(&'c self, method: Method, uri: U) -> LocalRequest<'c>
where U: Into<Cow<'u, str>>
{
LocalRequest::new(self, method, uri.into())
}
}
impl_client!("use rocket::local::asynchronous::Client;" @async await Client);
#[cfg(test)]
mod test {
use super::Client;
fn assert_sync_send<T: Sync + Send>() {}
#[test]
fn test_local_client_impl_send_sync() {
assert_sync_send::<Client>();
}
}

View File

@ -0,0 +1,7 @@
mod client;
mod request;
mod response;
pub use client::*;
pub use request::*;
pub use response::*;

View File

@ -0,0 +1,107 @@
use std::borrow::Cow;
use crate::{Request, Data};
use crate::http::{Status, Method, uri::Origin, ext::IntoOwned};
use super::{Client, LocalResponse};
pub struct LocalRequest<'c> {
client: &'c Client,
request: Request<'c>,
data: Vec<u8>,
uri: Cow<'c, str>,
}
impl<'c> LocalRequest<'c> {
pub(crate) fn new(
client: &'c Client,
method: Method,
uri: Cow<'c, str>
) -> LocalRequest<'c> {
// We set a dummy string for now and check the user's URI on dispatch.
let request = Request::new(client.rocket(), method, Origin::dummy());
// Set up any cookies we know about.
if let Some(ref jar) = client.cookies {
let cookies = jar.read().expect("LocalRequest::new() read lock");
for cookie in cookies.iter() {
request.cookies().add_original(cookie.clone().into_owned());
}
}
LocalRequest { client, request, uri, data: vec![] }
}
pub(crate) fn _request(&self) -> &Request<'c> {
&self.request
}
pub(crate) fn _request_mut(&mut self) -> &mut Request<'c> {
&mut self.request
}
pub(crate) fn _body_mut(&mut self) -> &mut Vec<u8> {
&mut self.data
}
// This method should _never_ be publicly exposed!
#[inline(always)]
fn long_lived_request<'a>(&mut self) -> &'a mut Request<'c> {
// FIXME: Whatever. I'll kill this.
unsafe { &mut *(&mut self.request as *mut _) }
}
// Performs the actual dispatch.
// TODO.async: @jebrosen suspects there might be actual UB in here after all,
// and now we just went and mixed threads into it
async fn _dispatch(mut self) -> LocalResponse<'c> {
// First, validate the URI, returning an error response (generated from
// an error catcher) immediately if it's invalid.
if let Ok(uri) = Origin::parse(&self.uri) {
self.request.set_uri(uri.into_owned());
} else {
error!("Malformed request URI: {}", self.uri);
let res = self.client.rocket()
.handle_error(Status::BadRequest, self.long_lived_request());
return LocalResponse { _request: self.request, inner: res.await };
}
// Actually dispatch the request.
let response = self.client.rocket()
.dispatch(self.long_lived_request(), Data::local(self.data))
.await;
// If the client is tracking cookies, updates the internal cookie jar
// with the changes reflected by `response`.
if let Some(ref jar) = self.client.cookies {
let mut jar = jar.write().expect("LocalRequest::_dispatch() write lock");
let current_time = time::OffsetDateTime::now_utc();
for cookie in response.cookies() {
if let Some(expires) = cookie.expires() {
if expires <= current_time {
jar.force_remove(cookie);
continue;
}
}
jar.add(cookie.into_owned());
}
}
LocalResponse { _request: self.request, inner: response }
}
}
impl<'c> Clone for LocalRequest<'c> {
fn clone(&self) -> Self {
LocalRequest {
client: self.client,
request: self.request.clone(),
data: self.data.clone(),
uri: self.uri.clone(),
}
}
}
impl_request!("use rocket::local::asynchronous::Client;" @async await LocalRequest);

View File

@ -0,0 +1,22 @@
use crate::{Request, Response};
pub struct LocalResponse<'c> {
pub(in super) _request: Request<'c>,
pub(in super) inner: Response<'c>,
}
impl<'c> LocalResponse<'c> {
fn _response(&self) -> &Response<'c> {
&self.inner
}
pub(crate) async fn _into_string(mut self) -> Option<String> {
self.inner.body_string().await
}
pub(crate) async fn _into_bytes(mut self) -> Option<Vec<u8>> {
self.inner.body_bytes().await
}
}
impl_response!("use rocket::local::asynchronous::Client;" @async await LocalResponse);

View File

@ -0,0 +1,81 @@
use std::borrow::Cow;
use std::cell::RefCell;
use crate::error::LaunchError;
use crate::http::Method;
use crate::local::{asynchronous, blocking::LocalRequest};
use crate::rocket::{Rocket, Cargo};
pub struct Client {
pub(crate) inner: asynchronous::Client,
runtime: RefCell<tokio::runtime::Runtime>,
}
impl Client {
fn _new(rocket: Rocket, tracked: bool) -> Result<Client, LaunchError> {
let mut runtime = tokio::runtime::Builder::new()
.basic_scheduler()
.enable_all()
.build()
.expect("create tokio runtime");
// Initialize the Rocket instance
let inner = runtime.block_on(asynchronous::Client::_new(rocket, tracked))?;
Ok(Self { inner, runtime: RefCell::new(runtime) })
}
#[doc(hidden)]
/// WARNING: This is unstable! Do not use this method outside of Rocket!
pub fn _test<T, F: FnOnce(Self) -> T + Send>(f: F) -> T {
let rocket = crate::ignite();
let client = Client::new(rocket).expect("valid rocket");
f(client)
}
#[inline(always)]
pub(crate) fn block_on<F, R>(&self, fut: F) -> R
where F: std::future::Future<Output=R>,
{
self.runtime.borrow_mut().block_on(fut)
}
#[inline(always)]
fn _cargo(&self) -> &Cargo {
self.inner._cargo()
}
#[inline(always)]
pub(crate) fn _req<'c, 'u: 'c, U>(
&'c self,
method: Method,
uri: U
) -> LocalRequest<'c>
where U: Into<Cow<'u, str>>
{
LocalRequest::new(self, method, uri.into())
}
}
impl_client!("use rocket::local::blocking::Client;" Client);
#[cfg(doctest)]
mod doctest {
/// ```no_run
/// // Just to ensure we get the path/form right in the following tests.
/// use rocket::local::blocking::Client;
///
/// fn test<T>() {};
/// test::<Client>();
///
/// fn is_send<T: Send>() {};
/// is_send::<Client>();
/// ```
///
/// ```compile_fail
/// use rocket::local::blocking::Client;
///
/// fn not_sync<T: Sync>() {};
/// not_sync::<Client>();
/// ```
#[allow(dead_code)]
fn test_not_sync_or_send() {}
}

View File

@ -0,0 +1,108 @@
// TODO: Explain difference from async Client
//!
//! Structures for local dispatching of requests, primarily for testing.
//!
//! This module allows for simple request dispatching against a local,
//! non-networked instance of Rocket. The primary use of this module is to unit
//! and integration test Rocket applications by crafting requests, dispatching
//! them, and verifying the response.
//!
//! # Usage
//!
//! This module contains a [`Client`] structure that is used to create
//! [`LocalRequest`] structures that can be dispatched against a given
//! [`Rocket`](crate::Rocket) instance. Usage is straightforward:
//!
//! 1. Construct a `Rocket` instance that represents the application.
//!
//! ```rust
//! let rocket = rocket::ignite();
//! # let _ = rocket;
//! ```
//!
//! 2. Construct a `Client` using the `Rocket` instance.
//!
//! ```rust
//! # use rocket::local::blocking::Client;
//! # let rocket = rocket::ignite();
//! let client = Client::new(rocket).expect("valid rocket instance");
//! # let _ = client;
//! ```
//!
//! 3. Construct requests using the `Client` instance.
//!
//! ```rust
//! # use rocket::local::blocking::Client;
//! # let rocket = rocket::ignite();
//! # let client = Client::new(rocket).unwrap();
//! let req = client.get("/");
//! # let _ = req;
//! ```
//!
//! 3. Dispatch the request to retrieve the response.
//!
//! ```rust
//! # use rocket::local::blocking::Client;
//! # let rocket = rocket::ignite();
//! # let client = Client::new(rocket).unwrap();
//! # let req = client.get("/");
//! let response = req.dispatch();
//! # let _ = response;
//! ```
//!
//! All together and in idiomatic fashion, this might look like:
//!
//! ```rust
//! use rocket::local::blocking::Client;
//!
//! let client = Client::new(rocket::ignite()).expect("valid rocket");
//! let response = client.post("/")
//! .body("Hello, world!")
//! .dispatch();
//! # let _ = response;
//! ```
//!
//! # Unit/Integration Testing
//!
//! This module can be used to test a Rocket application by constructing
//! requests via `Client` and validating the resulting response. As an example,
//! consider the following complete "Hello, world!" application, with testing.
//!
//! ```rust
//! #![feature(proc_macro_hygiene)]
//!
//! #[macro_use] extern crate rocket;
//!
//! #[get("/")]
//! fn hello() -> &'static str {
//! "Hello, world!"
//! }
//!
//! # fn main() { }
//! #[cfg(test)]
//! mod test {
//! use super::{rocket, hello};
//! use rocket::local::blocking::Client;
//!
//! fn test_hello_world() {
//! // Construct a client to use for dispatching requests.
//! let rocket = rocket::ignite().mount("/", routes![hello]);
//! let client = Client::new(rocket).expect("valid rocket instance");
//!
//! // Dispatch a request to 'GET /' and validate the response.
//! let mut response = client.get("/").dispatch();
//! assert_eq!(response.into_string(), Some("Hello, world!".into()));
//! }
//! }
//! ```
//!
//! [`Client`]: crate::local::blocking::Client
//! [`LocalRequest`]: crate::local::blocking::LocalRequest
mod client;
mod request;
mod response;
pub use self::client::*;
pub use self::request::*;
pub use self::response::*;

View File

@ -0,0 +1,43 @@
use std::borrow::Cow;
use crate::{Request, http::Method, local::asynchronous};
use super::{Client, LocalResponse};
#[derive(Clone)]
pub struct LocalRequest<'c> {
inner: asynchronous::LocalRequest<'c>,
client: &'c Client,
}
impl<'c> LocalRequest<'c> {
#[inline]
pub(crate) fn new(
client: &'c Client,
method: Method,
uri: Cow<'c, str>
) -> LocalRequest<'c> {
let inner = asynchronous::LocalRequest::new(&client.inner, method, uri);
Self { inner, client }
}
#[inline]
fn _request(&self) -> &Request<'c> {
self.inner._request()
}
#[inline]
fn _request_mut(&mut self) -> &mut Request<'c> {
self.inner._request_mut()
}
fn _body_mut(&mut self) -> &mut Vec<u8> {
self.inner._body_mut()
}
fn _dispatch(self) -> LocalResponse<'c> {
let inner = self.client.block_on(self.inner.dispatch());
LocalResponse { inner, client: self.client }
}
}
impl_request!("use rocket::local::blocking::Client;" LocalRequest);

View File

@ -0,0 +1,24 @@
use crate::{Response, local::asynchronous};
use super::Client;
pub struct LocalResponse<'c> {
pub(in super) inner: asynchronous::LocalResponse<'c>,
pub(in super) client: &'c Client,
}
impl<'c> LocalResponse<'c> {
fn _response(&self) -> &Response<'c> {
&*self.inner
}
fn _into_string(self) -> Option<String> {
self.client.block_on(self.inner._into_string())
}
fn _into_bytes(self) -> Option<Vec<u8>> {
self.client.block_on(self.inner._into_bytes())
}
}
impl_response!("use rocket::local::blocking::Client;" LocalResponse);

View File

@ -1,103 +1,121 @@
use std::sync::RwLock;
use std::borrow::Cow;
//! A structure to construct requests for local dispatching.
//!
//! # Usage
//!
//! A `Client` is constructed via the [`new()`] or [`untracked()`] methods from
//! an already constructed `Rocket` instance. Once a value of `Client` has been
//! constructed, the [`LocalRequest`] constructor methods ([`get()`], [`put()`],
//! [`post()`], and so on) can be used to create a `LocalRequest` for
//! dispatching.
//!
//! See the [top-level documentation](crate::local) for more usage information.
//!
//! ## Cookie Tracking
//!
//! A `Client` constructed using [`new()`] propagates cookie changes made by
//! responses to previously dispatched requests. In other words, if a previously
//! dispatched request resulted in a response that adds a cookie, any future
//! requests will contain that cookie. Similarly, cookies removed by a response
//! won't be propagated further.
//!
//! This is typically the desired mode of operation for a `Client` as it removes
//! the burden of manually tracking cookies. Under some circumstances, however,
//! disabling this tracking may be desired. In these cases, use the
//! [`untracked()`](Client::untracked()) constructor to create a `Client` that
//! _will not_ track cookies.
//!
//! ### Synchronization
//!
//! While `Client` implements `Sync`, using it in a multithreaded environment
//! while tracking cookies can result in surprising, non-deterministic behavior.
//! This is because while cookie modifications are serialized, the exact
//! ordering depends on when requests are dispatched. Specifically, when cookie
//! tracking is enabled, all request dispatches are serialized, which in-turn
//! serializes modifications to the internally tracked cookies.
//!
//! If possible, refrain from sharing a single instance of `Client` across
//! multiple threads. Instead, prefer to create a unique instance of `Client`
//! per thread. If it's not possible, ensure that either you are not depending
//! on cookies, the ordering of their modifications, or both, or have arranged
//! for dispatches to occur in a deterministic ordering.
//!
//! ## Example
//!
//! The following snippet creates a `Client` from a `Rocket` instance and
//! dispatches a local request to `POST /` with a body of `Hello, world!`.
//!
//! ```rust
//! use rocket::local::asynchronous::Client;
//!
//! # rocket::async_test(async {
//! let rocket = rocket::ignite();
//! let client = Client::new(rocket).await.expect("valid rocket");
//! let response = client.post("/")
//! .body("Hello, world!")
//! .dispatch().await;
//! # });
//! ```
//!
//! [`new()`]: #method.new
//! [`untracked()`]: #method.untracked
//! [`get()`]: #method.get
//! [`put()`]: #method.put
//! [`post()`]: #method.post
use crate::rocket::{Rocket, Cargo};
use crate::local::LocalRequest;
use crate::http::{Method, private::CookieJar};
use crate::error::LaunchError;
macro_rules! req_method {
($import:literal, $NAME:literal, $f:ident, $method:expr) => (
req_method!(@
$import,
$NAME,
concat!("let req = client.", stringify!($f), r#"("/hello");"#),
$f,
$method
);
);
/// A structure to construct requests for local dispatching.
(@$import:literal, $NAME:literal, $use_it:expr, $f:ident, $method:expr) => (
/// Create a local `
#[doc = $NAME]
/// ` request to the URI `uri`.
///
/// # Usage
/// When dispatched, the request will be served by the instance of Rocket
/// within `self`. The request is not dispatched automatically. To actually
/// dispatch the request, call [`LocalRequest::dispatch()`] on the returned
/// request.
///
/// A `Client` is constructed via the [`new()`] or [`untracked()`] methods from
/// an already constructed `Rocket` instance. Once a value of `Client` has been
/// constructed, the [`LocalRequest`] constructor methods ([`get()`], [`put()`],
/// [`post()`], and so on) can be used to create a `LocalRequest` for
/// dispatching.
/// # Example
///
/// See the [top-level documentation](crate::local) for more usage information.
/// ```rust,no_run
#[doc = $import]
///
/// ## Cookie Tracking
///
/// A `Client` constructed using [`new()`] propagates cookie changes made by
/// responses to previously dispatched requests. In other words, if a previously
/// dispatched request resulted in a response that adds a cookie, any future
/// requests will contain that cookie. Similarly, cookies removed by a response
/// won't be propagated further.
///
/// This is typically the desired mode of operation for a `Client` as it removes
/// the burden of manually tracking cookies. Under some circumstances, however,
/// disabling this tracking may be desired. In these cases, use the
/// [`untracked()`](Client::untracked()) constructor to create a `Client` that
/// _will not_ track cookies.
///
/// ### Synchronization
///
/// While `Client` implements `Sync`, using it in a multithreaded environment
/// while tracking cookies can result in surprising, non-deterministic behavior.
/// This is because while cookie modifications are serialized, the exact
/// ordering depends on when requests are dispatched. Specifically, when cookie
/// tracking is enabled, all request dispatches are serialized, which in-turn
/// serializes modifications to the internally tracked cookies.
///
/// If possible, refrain from sharing a single instance of `Client` across
/// multiple threads. Instead, prefer to create a unique instance of `Client`
/// per thread. If it's not possible, ensure that either you are not depending
/// on cookies, the ordering of their modifications, or both, or have arranged
/// for dispatches to occur in a deterministic ordering.
///
/// ## Example
///
/// The following snippet creates a `Client` from a `Rocket` instance and
/// dispatches a local request to `POST /` with a body of `Hello, world!`.
///
/// ```rust
/// use rocket::local::Client;
///
/// # rocket::async_test(async {
/// let rocket = rocket::ignite();
/// let client = Client::new(rocket).await.expect("valid rocket");
/// let response = client.post("/")
/// .body("Hello, world!")
/// .dispatch().await;
/// # Client::_test(|client| {
/// let client: Client = client;
#[doc = $use_it]
/// # });
/// ```
///
/// [`new()`]: #method.new
/// [`untracked()`]: #method.untracked
/// [`get()`]: #method.get
/// [`put()`]: #method.put
/// [`post()`]: #method.post
pub struct Client {
cargo: Cargo,
pub(crate) cookies: Option<RwLock<CookieJar>>,
}
impl Client {
/// Constructs a new `Client`. If `tracked` is `true`, an empty `CookieJar`
/// is created for cookie tracking. Otherwise, the internal `CookieJar` is
/// set to `None`.
async fn _new(mut rocket: Rocket, tracked: bool) -> Result<Client, LaunchError> {
rocket.prelaunch_check().await?;
let cookies = match tracked {
true => Some(RwLock::new(CookieJar::new())),
false => None
};
Ok(Client { cargo: rocket.into_cargo().await, cookies })
#[inline(always)]
pub fn $f<'c, 'u: 'c, U>(&'c self, uri: U) -> LocalRequest<'c>
where U: Into<Cow<'u, str>>
{
self.req($method, uri)
}
)
}
macro_rules! impl_client {
($import:literal $(@$prefix:tt $suffix:tt)? $name:ident) =>
{
impl $name {
/// Construct a new `Client` from an instance of `Rocket` with cookie
/// tracking.
///
/// # Cookie Tracking
///
/// By default, a `Client` propagates cookie changes made by responses to
/// previously dispatched requests. In other words, if a previously
/// dispatched request resulted in a response that adds a cookie, any future
/// requests will contain the new cookies. Similarly, cookies removed by a
/// response won't be propagated further.
/// By default, a `Client` propagates cookie changes made by responses
/// to previously dispatched requests. In other words, if a previously
/// dispatched request resulted in a response that adds a cookie, any
/// future requests will contain the new cookies. Similarly, cookies
/// removed by a response won't be propagated further.
///
/// This is typically the desired mode of operation for a `Client` as it
/// removes the burden of manually tracking cookies. Under some
@ -110,22 +128,19 @@ impl Client {
/// If launching the `Rocket` instance would fail, excepting network errors,
/// the `LaunchError` is returned.
///
/// # Example
/// ```rust,no_run
#[doc = $import]
///
/// ```rust
/// use rocket::local::Client;
///
/// # let _ = async {
/// let client = Client::new(rocket::ignite()).await.expect("valid rocket");
/// # };
/// let rocket = rocket::ignite();
/// let client = Client::new(rocket);
/// ```
#[inline(always)]
pub async fn new(rocket: Rocket) -> Result<Client, LaunchError> {
Client::_new(rocket, true).await
pub $($prefix)? fn new(rocket: Rocket) -> Result<Self, LaunchError> {
Self::_new(rocket, true) $(.$suffix)?
}
/// Construct a new `Client` from an instance of `Rocket` _without_ cookie
/// tracking.
/// Construct a new `Client` from an instance of `Rocket` _without_
/// cookie tracking.
///
/// # Cookie Tracking
///
@ -134,263 +149,90 @@ impl Client {
///
/// # Errors
///
/// If launching the `Rocket` instance would fail, excepting network errors,
/// the `LaunchError` is returned.
/// If launching the `Rocket` instance would fail, excepting network
/// errors, the `LaunchError` is returned.
///
/// # Example
/// ```rust,no_run
#[doc = $import]
///
/// ```rust
/// use rocket::local::Client;
///
/// # rocket::async_test(async {
/// let client = Client::untracked(rocket::ignite()).await.expect("valid rocket");
/// # });
/// let rocket = rocket::ignite();
/// let client = Client::untracked(rocket);
/// ```
#[inline(always)]
pub async fn untracked(rocket: Rocket) -> Result<Client, LaunchError> {
Client::_new(rocket, false).await
pub $($prefix)? fn untracked(rocket: Rocket) -> Result<Self, LaunchError> {
Self::_new(rocket, true) $(.$suffix)?
}
/// Returns a reference to the `Rocket` of the `Rocket` this client is
/// creating requests for.
/// Returns a reference to the `Rocket` this client is creating requests
/// for.
///
/// # Example
///
/// ```rust
/// use rocket::local::Client;
///
/// # rocket::async_test(async {
/// let my_rocket = rocket::ignite();
/// let client = Client::new(my_rocket).await.expect("valid rocket");
/// ```rust,no_run
#[doc = $import]
///
/// # Client::_test(|client| {
/// let client: Client = client;
/// let rocket = client.rocket();
/// # });
/// ```
#[inline(always)]
pub fn rocket(&self) -> &Rocket {
&*self.cargo
&*self._cargo()
}
/// Returns a reference to the `Rocket` of the `Rocket` this client is
/// Returns a reference to the `Cargo` of the `Rocket` this client is
/// creating requests for.
///
/// # Example
///
/// ```rust
/// use rocket::local::Client;
///
/// # rocket::async_test(async {
/// let my_rocket = rocket::ignite();
/// let client = Client::new(my_rocket).await.expect("valid rocket");
#[doc = $import]
///
/// # Client::_test(|client| {
/// let client: Client = client;
/// let cargo = client.cargo();
/// # });
/// ```
#[inline(always)]
pub fn cargo(&self) -> &Cargo {
&self.cargo
self._cargo()
}
req_method!($import, "GET", get, Method::Get);
req_method!($import, "PUT", put, Method::Put);
req_method!($import, "POST", post, Method::Post);
req_method!($import, "DELETE", delete, Method::Delete);
req_method!($import, "OPTIONS", options, Method::Options);
req_method!($import, "HEAD", head, Method::Head);
req_method!($import, "PATCH", patch, Method::Patch);
/// Create a local `GET` request to the URI `uri`.
///
/// When dispatched, the request will be served by the instance of Rocket
/// within `self`. The request is not dispatched automatically. To actually
/// dispatch the request, call [`LocalRequest::dispatch()`] on the returned
/// request.
/// When dispatched, the request will be served by the instance of
/// Rocket within `self`. The request is not dispatched automatically.
/// To actually dispatch the request, call [`LocalRequest::dispatch()`]
/// on the returned request.
///
/// # Example
///
/// ```rust
/// use rocket::local::Client;
///
/// # rocket::async_test(async {
/// let client = Client::new(rocket::ignite()).await.expect("valid rocket");
/// let req = client.get("/hello");
/// # });
/// ```
#[inline(always)]
pub fn get<'c, 'u: 'c, U: Into<Cow<'u, str>>>(&'c self, uri: U) -> LocalRequest<'c> {
self.req(Method::Get, uri)
}
/// Create a local `PUT` request to the URI `uri`.
///
/// When dispatched, the request will be served by the instance of Rocket
/// within `self`. The request is not dispatched automatically. To actually
/// dispatch the request, call [`LocalRequest::dispatch()`] on the returned
/// request.
///
/// # Example
///
/// ```rust
/// use rocket::local::Client;
///
/// # rocket::async_test(async {
/// let client = Client::new(rocket::ignite()).await.expect("valid rocket");
/// let req = client.put("/hello");
/// # });
/// ```
#[inline(always)]
pub fn put<'c, 'u: 'c, U: Into<Cow<'u, str>>>(&'c self, uri: U) -> LocalRequest<'c> {
self.req(Method::Put, uri)
}
/// Create a local `POST` request to the URI `uri`.
///
/// When dispatched, the request will be served by the instance of Rocket
/// within `self`. The request is not dispatched automatically. To actually
/// dispatch the request, call [`LocalRequest::dispatch()`] on the returned
/// request.
///
/// # Example
///
/// ```rust
/// use rocket::local::Client;
/// use rocket::http::ContentType;
///
/// # rocket::async_test(async {
/// let client = Client::new(rocket::ignite()).await.expect("valid rocket");
///
/// let req = client.post("/hello")
/// .body("field=value&otherField=123")
/// .header(ContentType::Form);
/// # });
/// ```
#[inline(always)]
pub fn post<'c, 'u: 'c, U: Into<Cow<'u, str>>>(&'c self, uri: U) -> LocalRequest<'c> {
self.req(Method::Post, uri)
}
/// Create a local `DELETE` request to the URI `uri`.
///
/// When dispatched, the request will be served by the instance of Rocket
/// within `self`. The request is not dispatched automatically. To actually
/// dispatch the request, call [`LocalRequest::dispatch()`] on the returned
/// request.
///
/// # Example
///
/// ```rust
/// use rocket::local::Client;
///
/// # rocket::async_test(async {
/// let client = Client::new(rocket::ignite()).await.expect("valid rocket");
/// let req = client.delete("/hello");
/// # });
/// ```
#[inline(always)]
pub fn delete<'c, 'u: 'c, U>(&'c self, uri: U) -> LocalRequest<'c>
where U: Into<Cow<'u, str>>
{
self.req(Method::Delete, uri)
}
/// Create a local `OPTIONS` request to the URI `uri`.
///
/// When dispatched, the request will be served by the instance of Rocket
/// within `self`. The request is not dispatched automatically. To actually
/// dispatch the request, call [`LocalRequest::dispatch()`] on the returned
/// request.
///
/// # Example
///
/// ```rust
/// use rocket::local::Client;
///
/// # rocket::async_test(async {
/// let client = Client::new(rocket::ignite()).await.expect("valid rocket");
/// let req = client.options("/hello");
/// # });
/// ```
#[inline(always)]
pub fn options<'c, 'u: 'c, U>(&'c self, uri: U) -> LocalRequest<'c>
where U: Into<Cow<'u, str>>
{
self.req(Method::Options, uri)
}
/// Create a local `HEAD` request to the URI `uri`.
///
/// When dispatched, the request will be served by the instance of Rocket
/// within `self`. The request is not dispatched automatically. To actually
/// dispatch the request, call [`LocalRequest::dispatch()`] on the returned
/// request.
///
/// # Example
///
/// ```rust
/// use rocket::local::Client;
///
/// # rocket::async_test(async {
/// let client = Client::new(rocket::ignite()).await.expect("valid rocket");
/// let req = client.head("/hello");
/// # });
/// ```
#[inline(always)]
pub fn head<'c, 'u: 'c, U>(&'c self, uri: U) -> LocalRequest<'c>
where U: Into<Cow<'u, str>>
{
self.req(Method::Head, uri)
}
/// Create a local `PATCH` request to the URI `uri`.
///
/// When dispatched, the request will be served by the instance of Rocket
/// within `self`. The request is not dispatched automatically. To actually
/// dispatch the request, call [`LocalRequest::dispatch()`] on the returned
/// request.
///
/// # Example
///
/// ```rust
/// use rocket::local::Client;
///
/// # rocket::async_test(async {
/// let client = Client::new(rocket::ignite()).await.expect("valid rocket");
/// let req = client.patch("/hello");
/// # });
/// ```
#[inline(always)]
pub fn patch<'c, 'u: 'c, U>(&'c self, uri: U) -> LocalRequest<'c>
where U: Into<Cow<'u, str>>
{
self.req(Method::Patch, uri)
}
/// Create a local request with method `method` to the URI `uri`.
///
/// When dispatched, the request will be served by the instance of Rocket
/// within `self`. The request is not dispatched automatically. To actually
/// dispatch the request, call [`LocalRequest::dispatch()`] on the returned
/// request.
///
/// # Example
///
/// ```rust
/// use rocket::local::Client;
/// ```rust,no_run
#[doc = $import]
/// use rocket::http::Method;
///
/// # rocket::async_test(async {
/// let client = Client::new(rocket::ignite()).await.expect("valid rocket");
/// let req = client.req(Method::Get, "/hello");
/// # Client::_test(|client| {
/// let client: Client = client;
/// client.req(Method::Get, "/hello");
/// # });
/// ```
#[inline(always)]
pub fn req<'c, 'u: 'c, U>(&'c self, method: Method, uri: U) -> LocalRequest<'c>
pub fn req<'c, 'u: 'c, U>(
&'c self,
method: Method,
uri: U
) -> LocalRequest<'c>
where U: Into<Cow<'u, str>>
{
LocalRequest::new(self, method, uri.into())
}
}
#[cfg(test)]
mod test {
use super::Client;
fn assert_sync<T: Sync>() {}
#[test]
fn test_local_client_impl_sync() {
assert_sync::<Client>();
self._req(method, uri)
}
}
}}

View File

@ -21,7 +21,7 @@
//! 2. Construct a `Client` using the `Rocket` instance.
//!
//! ```rust
//! # use rocket::local::Client;
//! # use rocket::local::asynchronous::Client;
//! # let rocket = rocket::ignite();
//! # rocket::async_test(async {
//! let client = Client::new(rocket).await.expect("valid rocket instance");
@ -32,7 +32,7 @@
//! 3. Construct requests using the `Client` instance.
//!
//! ```rust
//! # use rocket::local::Client;
//! # use rocket::local::asynchronous::Client;
//! # let rocket = rocket::ignite();
//! # rocket::async_test(async {
//! # let client = Client::new(rocket).await.unwrap();
@ -44,7 +44,7 @@
//! 3. Dispatch the request to retrieve the response.
//!
//! ```rust
//! # use rocket::local::Client;
//! # use rocket::local::asynchronous::Client;
//! # let rocket = rocket::ignite();
//! # rocket::async_test(async {
//! # let client = Client::new(rocket).await.unwrap();
@ -57,7 +57,7 @@
//! All together and in idiomatic fashion, this might look like:
//!
//! ```rust
//! use rocket::local::Client;
//! use rocket::local::asynchronous::Client;
//!
//! # rocket::async_test(async {
//! let client = Client::new(rocket::ignite()).await.expect("valid rocket");
@ -88,7 +88,7 @@
//! #[cfg(test)]
//! mod test {
//! use super::{rocket, hello};
//! use rocket::local::Client;
//! use rocket::local::asynchronous::Client;
//!
//! #[rocket::async_test]
//! fn test_hello_world() {
@ -98,16 +98,17 @@
//!
//! // Dispatch a request to 'GET /' and validate the response.
//! let mut response = client.get("/").dispatch().await;
//! assert_eq!(response.body_string().await, Some("Hello, world!".into()));
//! assert_eq!(response.into_string().await, Some("Hello, world!".into()));
//! }
//! }
//! ```
//!
//! [`Client`]: crate::local::Client
//! [`Client`]: crate::local::asynchronous::Client
//! [`LocalRequest`]: crate::local::LocalRequest
mod request;
mod client;
#[macro_use] mod client;
#[macro_use] mod request;
#[macro_use] mod response;
pub use self::request::{LocalResponse, LocalRequest};
pub use self::client::Client;
pub mod asynchronous;
pub mod blocking;

View File

@ -1,163 +1,90 @@
use std::fmt;
use std::sync::Arc;
use std::net::SocketAddr;
use std::ops::{Deref, DerefMut};
use std::borrow::Cow;
use crate::{Request, Response, Data};
use crate::http::{Status, Method, Header, Cookie, uri::Origin, ext::IntoOwned};
use crate::local::Client;
/// A structure representing a local request as created by [`Client`].
///
/// # Usage
///
/// A `LocalRequest` value is constructed via method constructors on [`Client`].
/// Headers can be added via the [`header`] builder method and the
/// [`add_header`] method. Cookies can be added via the [`cookie`] builder
/// method. The remote IP address can be set via the [`remote`] builder method.
/// The body of the request can be set via the [`body`] builder method or
/// [`set_body`] method.
///
/// ## Example
///
/// The following snippet uses the available builder methods to construct a
/// `POST` request to `/` with a JSON body:
///
/// ```rust
/// use rocket::local::Client;
/// use rocket::http::{ContentType, Cookie};
///
/// # rocket::async_test(async {
/// let client = Client::new(rocket::ignite()).await.expect("valid rocket");
/// let req = client.post("/")
/// .header(ContentType::JSON)
/// .remote("127.0.0.1:8000".parse().unwrap())
/// .cookie(Cookie::new("name", "value"))
/// .body(r#"{ "value": 42 }"#);
/// # });
/// ```
///
/// # Dispatching
///
/// A `LocalRequest` can be dispatched in one of two ways:
///
/// 1. [`dispatch`]
///
/// This method should always be preferred. The `LocalRequest` is consumed
/// and a response is returned.
///
/// 2. [`mut_dispatch`]
///
/// This method should _only_ be used when either it is known that the
/// application will not modify the request, or it is desired to see
/// modifications to the request. No cloning occurs, and the request is not
/// consumed.
///
/// Additionally, note that `LocalRequest` implements `Clone`. As such, if the
/// same request needs to be dispatched multiple times, the request can first be
/// cloned and then dispatched: `request.clone().dispatch()`.
///
/// [`Client`]: crate::local::Client
/// [`header`]: #method.header
/// [`add_header`]: #method.add_header
/// [`cookie`]: #method.cookie
/// [`remote`]: #method.remote
/// [`body`]: #method.body
/// [`set_body`]: #method.set_body
/// [`dispatch`]: #method.dispatch
/// [`mut_dispatch`]: #method.mut_dispatch
pub struct LocalRequest<'c> {
client: &'c Client,
// This `Arc` exists so that we can transfer ownership to the `LocalResponse`
// selectively on dispatch. This is necessary because responses may point
// into the request, and thus the request and all of its data needs to be
// alive while the response is accessible.
//
// Because both a `LocalRequest` and a `LocalResponse` can hold an `Arc` to
// the same `Request`, _and_ the `LocalRequest` can mutate the request, we
// must ensure that 1) neither `LocalRequest` not `LocalResponse` are `Sync`
// or `Send` and 2) mutations carried out in `LocalRequest` are _stable_:
// they never _remove_ data, and any reallocations (say, for vectors or
// hashmaps) result in object pointers remaining the same. This means that
// even if the `Request` is mutated by a `LocalRequest`, those mutations are
// not observable by `LocalResponse`.
//
// The first is ensured by the embedding of the `Arc` type which is neither
// `Send` nor `Sync`. The second is more difficult to argue. First, observe
// that any methods of `LocalRequest` that _remove_ values from `Request`
// only remove _Copy_ values, in particular, `SocketAddr`. Second, the
// lifetime of the `Request` object is tied to the lifetime of the
// `LocalResponse`, so references from `Request` cannot be dangling in
// `Response`. And finally, observe how all of the data stored in `Request`
// is converted into its owned counterpart before insertion, ensuring stable
// addresses. Together, these properties guarantee the second condition.
request: Arc<Request<'c>>,
data: Vec<u8>,
uri: Cow<'c, str>,
}
impl<'c> LocalRequest<'c> {
#[inline(always)]
pub(crate) fn new(
client: &'c Client,
method: Method,
uri: Cow<'c, str>
) -> LocalRequest<'c> {
// We set a dummy string for now and check the user's URI on dispatch.
let request = Request::new(client.rocket(), method, Origin::dummy());
// Set up any cookies we know about.
if let Some(ref jar) = client.cookies {
let cookies = jar.read().expect("LocalRequest::new() read lock");
for cookie in cookies.iter() {
request.cookies().add_original(cookie.clone().into_owned());
}
}
// See the comments on the structure for what's going on here.
let request = Arc::new(request);
LocalRequest { client, request, uri, data: vec![] }
}
//! A structure representing a local request as created by [`Client`].
//!
//! # Usage
//!
//! A `LocalRequest` value is constructed via method constructors on [`Client`].
//! Headers can be added via the [`header`] builder method and the
//! [`add_header`] method. Cookies can be added via the [`cookie`] builder
//! method. The remote IP address can be set via the [`remote`] builder method.
//! The body of the request can be set via the [`body`] builder method or
//! [`set_body`] method.
//!
//! ## Example
//!
//! The following snippet uses the available builder methods to construct a
//! `POST` request to `/` with a JSON body:
//!
//! ```rust
//! use rocket::local::asynchronous::Client;
//! use rocket::http::{ContentType, Cookie};
//!
//! # rocket::async_test(async {
//! let client = Client::new(rocket::ignite()).await.expect("valid rocket");
//! let req = client.post("/")
//! .header(ContentType::JSON)
//! .remote("127.0.0.1:8000".parse().unwrap())
//! .cookie(Cookie::new("name", "value"))
//! .body(r#"{ "value": 42 }"#);
//! # });
//! ```
//!
//! # Dispatching
//!
//! A `LocalRequest` can be dispatched in one of two ways:
//!
//! 1. [`dispatch`]
//!
//! This method should always be preferred. The `LocalRequest` is consumed
//! and a response is returned.
//!
//! 2. [`mut_dispatch`]
//!
//! This method should _only_ be used when either it is known that the
//! application will not modify the request, or it is desired to see
//! modifications to the request. No cloning occurs, and the request is not
//! consumed.
//!
//! Additionally, note that `LocalRequest` implements `Clone`. As such, if the
//! same request needs to be dispatched multiple times, the request can first be
//! cloned and then dispatched: `request.clone().dispatch()`.
//!
//! [`Client`]: crate::local::asynchronous::Client
//! [`header`]: #method.header
//! [`add_header`]: #method.add_header
//! [`cookie`]: #method.cookie
//! [`remote`]: #method.remote
//! [`body`]: #method.body
//! [`set_body`]: #method.set_body
//! [`dispatch`]: #method.dispatch
//! [`mut_dispatch`]: #method.mut_dispatch
macro_rules! impl_request {
($import:literal $(@$prefix:tt $suffix:tt)? $name:ident) =>
{
impl<'c> $name<'c> {
/// Retrieves the inner `Request` as seen by Rocket.
///
/// # Example
///
/// ```rust
/// use rocket::local::Client;
#[doc = $import]
/// use rocket::Request;
///
/// # rocket::async_test(async {
/// let client = Client::new(rocket::ignite()).await.expect("valid rocket");
/// # Client::_test(|client| {
/// let client: Client = client;
/// let req = client.get("/");
/// let inner_req = req.inner();
/// let inner: &Request = req.inner();
/// # });
/// ```
#[inline]
#[inline(always)]
pub fn inner(&self) -> &Request<'c> {
&*self.request
}
#[inline(always)]
fn request_mut(&mut self) -> &mut Request<'c> {
// See the comments in the structure for the argument of correctness.
Arc::get_mut(&mut self.request).expect("mutable aliasing!")
}
// This method should _never_ be publicly exposed!
#[inline(always)]
fn long_lived_request<'a>(&mut self) -> &'a mut Request<'c> {
// See the comments in the structure for the argument of correctness.
// Additionally, the caller must ensure that the owned instance of
// `Arc<Request>` remains valid as long as the returned reference can be
// accessed.
unsafe { &mut *(self.request_mut() as *mut _) }
self._request()
}
/// Add a header to this request.
///
/// Any type that implements `Into<Header>` can be used here. Among others,
/// this includes [`ContentType`] and [`Accept`].
/// Any type that implements `Into<Header>` can be used here. Among
/// others, this includes [`ContentType`] and [`Accept`].
///
/// [`ContentType`]: crate::http::ContentType
/// [`Accept`]: crate::http::Accept
@ -167,18 +94,19 @@ impl<'c> LocalRequest<'c> {
/// Add the Content-Type header:
///
/// ```rust
/// use rocket::local::Client;
#[doc = $import]
/// use rocket::http::ContentType;
///
/// # rocket::async_test(async {
/// # #[allow(unused_variables)]
/// let client = Client::new(rocket::ignite()).await.unwrap();
/// # Client::_test(|client| {
/// let client: Client = client;
/// let req = client.get("/").header(ContentType::JSON);
/// # });
/// ```
#[inline]
pub fn header<H: Into<Header<'static>>>(mut self, header: H) -> Self {
self.request_mut().add_header(header.into());
pub fn header<H>(mut self, header: H) -> Self
where H: Into<crate::http::Header<'static>>
{
self._request_mut().add_header(header.into());
self
}
@ -189,18 +117,20 @@ impl<'c> LocalRequest<'c> {
/// Add the Content-Type header:
///
/// ```rust
/// use rocket::local::Client;
#[doc = $import]
/// use rocket::http::ContentType;
///
/// # rocket::async_test(async {
/// let client = Client::new(rocket::ignite()).await.unwrap();
/// # Client::_test(|client| {
/// let client: Client = client;
/// let mut req = client.get("/");
/// req.add_header(ContentType::JSON);
/// # });
/// ```
#[inline]
pub fn add_header<H: Into<Header<'static>>>(&mut self, header: H) {
self.request_mut().add_header(header.into());
pub fn add_header<H>(&mut self, header: H)
where H: Into<crate::http::Header<'static>>
{
self._request_mut().add_header(header.into());
}
/// Set the remote address of this request.
@ -210,17 +140,18 @@ impl<'c> LocalRequest<'c> {
/// Set the remote address to "8.8.8.8:80":
///
/// ```rust
/// use rocket::local::Client;
#[doc = $import]
///
/// # rocket::async_test(async {
/// let client = Client::new(rocket::ignite()).await.unwrap();
/// # Client::_test(|client| {
/// let address = "8.8.8.8:80".parse().unwrap();
///
/// let client: Client = client;
/// let req = client.get("/").remote(address);
/// # });
/// ```
#[inline]
pub fn remote(mut self, address: SocketAddr) -> Self {
self.request_mut().set_remote(address);
pub fn remote(mut self, address: std::net::SocketAddr) -> Self {
self._request_mut().set_remote(address);
self
}
@ -231,20 +162,19 @@ impl<'c> LocalRequest<'c> {
/// Add `user_id` cookie:
///
/// ```rust
/// use rocket::local::Client;
#[doc = $import]
/// use rocket::http::Cookie;
///
/// # rocket::async_test(async {
/// let client = Client::new(rocket::ignite()).await.unwrap();
/// # #[allow(unused_variables)]
/// # Client::_test(|client| {
/// let client: Client = client;
/// let req = client.get("/")
/// .cookie(Cookie::new("username", "sb"))
/// .cookie(Cookie::new("user_id", "12"));
/// # });
/// ```
#[inline]
pub fn cookie(self, cookie: Cookie<'_>) -> Self {
self.request.cookies().add_original(cookie.into_owned());
pub fn cookie(self, cookie: crate::http::Cookie<'_>) -> Self {
self._request().cookies().add_original(cookie.into_owned());
self
}
@ -255,20 +185,19 @@ impl<'c> LocalRequest<'c> {
/// Add `user_id` cookie:
///
/// ```rust
/// use rocket::local::Client;
#[doc = $import]
/// use rocket::http::Cookie;
///
/// # rocket::async_test(async {
/// let client = Client::new(rocket::ignite()).await.unwrap();
/// # Client::_test(|client| {
/// let client: Client = client;
/// let cookies = vec![Cookie::new("a", "b"), Cookie::new("c", "d")];
/// # #[allow(unused_variables)]
/// let req = client.get("/").cookies(cookies);
/// # });
/// ```
#[inline]
pub fn cookies(self, cookies: Vec<Cookie<'_>>) -> Self {
pub fn cookies(self, cookies: Vec<crate::http::Cookie<'_>>) -> Self {
for cookie in cookies {
self.request.cookies().add_original(cookie.into_owned());
self._request().cookies().add_original(cookie.into_owned());
}
self
@ -286,27 +215,21 @@ impl<'c> LocalRequest<'c> {
/// Add `user_id` as a private cookie:
///
/// ```rust
/// use rocket::local::Client;
#[doc = $import]
/// use rocket::http::Cookie;
///
/// # rocket::async_test(async {
/// let client = Client::new(rocket::ignite()).await.unwrap();
/// # #[allow(unused_variables)]
/// # Client::_test(|client| {
/// let client: Client = client;
/// let req = client.get("/").private_cookie(Cookie::new("user_id", "sb"));
/// # });
/// ```
#[inline]
#[cfg(feature = "private-cookies")]
pub fn private_cookie(self, cookie: Cookie<'static>) -> Self {
self.request.cookies().add_original_private(cookie);
pub fn private_cookie(self, cookie: crate::http::Cookie<'static>) -> Self {
self._request().cookies().add_original_private(cookie);
self
}
// TODO: For CGI, we want to be able to set the body to be stdin without
// actually reading everything into a vector. Can we allow that here while
// keeping the simplicity? Looks like it would require us to reintroduce a
// NetStream::Local(Box<Read>) or something like that.
/// Set the body (data) of the request.
///
/// # Examples
@ -314,12 +237,11 @@ impl<'c> LocalRequest<'c> {
/// Set the body to be a JSON structure; also sets the Content-Type.
///
/// ```rust
/// use rocket::local::Client;
#[doc = $import]
/// use rocket::http::ContentType;
///
/// # rocket::async_test(async {
/// let client = Client::new(rocket::ignite()).await.unwrap();
/// # #[allow(unused_variables)]
/// # Client::_test(|client| {
/// let client: Client = client;
/// let req = client.post("/")
/// .header(ContentType::JSON)
/// .body(r#"{ "key": "value", "array": [1, 2, 3], }"#);
@ -327,7 +249,12 @@ impl<'c> LocalRequest<'c> {
/// ```
#[inline]
pub fn body<S: AsRef<[u8]>>(mut self, body: S) -> Self {
self.data = body.as_ref().into();
// TODO: For CGI, we want to be able to set the body to be stdin
// without actually reading everything into a vector. Can we allow
// that here while keeping the simplicity? Looks like it would
// require us to reintroduce a NetStream::Local(Box<Read>) or
// something like that.
*self._body_mut() = body.as_ref().into();
self
}
@ -338,18 +265,18 @@ impl<'c> LocalRequest<'c> {
/// Set the body to be a JSON structure; also sets the Content-Type.
///
/// ```rust
/// use rocket::local::Client;
#[doc = $import]
/// use rocket::http::ContentType;
///
/// # rocket::async_test(async {
/// let client = Client::new(rocket::ignite()).await.unwrap();
/// # Client::_test(|client| {
/// let client: Client = client;
/// let mut req = client.post("/").header(ContentType::JSON);
/// req.set_body(r#"{ "key": "value", "array": [1, 2, 3], }"#);
/// # });
/// ```
#[inline]
pub fn set_body<S: AsRef<[u8]>>(&mut self, body: S) {
self.data = body.as_ref().into();
*self._body_mut() = body.as_ref().into();
}
/// Dispatches the request, returning the response.
@ -360,7 +287,7 @@ impl<'c> LocalRequest<'c> {
/// # Example
///
/// ```rust
/// use rocket::local::Client;
/// use rocket::local::asynchronous::Client;
///
/// # rocket::async_test(async {
/// let client = Client::new(rocket::ignite()).await.unwrap();
@ -368,260 +295,16 @@ impl<'c> LocalRequest<'c> {
/// # });
/// ```
#[inline(always)]
pub async fn dispatch(mut self) -> LocalResponse<'c> {
let r = self.long_lived_request();
LocalRequest::_dispatch(self.client, r, self.request, &self.uri, self.data).await
}
/// Dispatches the request, returning the response.
///
/// This method _does not_ consume or clone `self`. Any changes to the
/// request that occur during handling will be visible after this method is
/// called. For instance, body data is always consumed after a request is
/// dispatched. As such, only the first call to `mut_dispatch` for a given
/// `LocalRequest` will contains the original body data.
///
/// This method should _only_ be used when either it is known that
/// the application will not modify the request, or it is desired to see
/// modifications to the request. Prefer to use [`dispatch`] instead.
///
/// [`dispatch`]: #method.dispatch
///
/// # Example
///
/// ```rust
/// use rocket::local::Client;
///
/// rocket::async_test(async {
/// let client = Client::new(rocket::ignite()).await.unwrap();
///
/// let mut req = client.get("/");
/// let response_a = req.mut_dispatch().await;
/// // TODO.async: Annoying. Is this really a good example to show?
/// drop(response_a);
/// let response_b = req.mut_dispatch().await;
/// })
/// ```
#[inline(always)]
pub async fn mut_dispatch(&mut self) -> LocalResponse<'c> {
let req = self.long_lived_request();
let data = std::mem::replace(&mut self.data, vec![]);
let rc_req = self.request.clone();
LocalRequest::_dispatch(self.client, req, rc_req, &self.uri, data).await
}
// Performs the actual dispatch.
// TODO.async: @jebrosen suspects there might be actual UB in here after all,
// and now we just went and mixed threads into it
async fn _dispatch(
client: &'c Client,
request: &'c mut Request<'c>,
owned_request: Arc<Request<'c>>,
uri: &str,
data: Vec<u8>
) -> LocalResponse<'c> {
let maybe_uri = Origin::parse(uri);
// First, validate the URI, returning an error response (generated from
// an error catcher) immediately if it's invalid.
if let Ok(uri) = maybe_uri {
request.set_uri(uri.into_owned());
} else {
error!("Malformed request URI: {}", uri);
let res = client.rocket().handle_error(Status::BadRequest, request).await;
return LocalResponse { _request: owned_request, response: res };
}
// Actually dispatch the request.
let response = client.rocket().dispatch(request, Data::local(data)).await;
// If the client is tracking cookies, updates the internal cookie jar
// with the changes reflected by `response`.
if let Some(ref jar) = client.cookies {
let mut jar = jar.write().expect("LocalRequest::_dispatch() write lock");
let current_time = time::OffsetDateTime::now_utc();
for cookie in response.cookies() {
if let Some(expires) = cookie.expires() {
if expires <= current_time {
jar.force_remove(cookie);
continue;
pub $($prefix)? fn dispatch(self) -> LocalResponse<'c> {
self._dispatch()$(.$suffix)?
}
}
jar.add(cookie.into_owned());
impl std::fmt::Debug for $name<'_> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
self._request().fmt(f)
}
}
LocalResponse {
_request: owned_request,
response: response
}
}
}
impl fmt::Debug for LocalRequest<'_> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
fmt::Debug::fmt(&self.request, f)
}
}
/// A structure representing a response from dispatching a local request.
///
/// This structure is a thin wrapper around [`Response`]. It implements no
/// methods of its own; all functionality is exposed via the [`Deref`] and
/// [`DerefMut`] implementations with a target of `Response`. In other words,
/// when invoking methods, a `LocalResponse` can be treated exactly as if it
/// were a `Response`.
pub struct LocalResponse<'c> {
_request: Arc<Request<'c>>,
response: Response<'c>,
}
impl<'c> Deref for LocalResponse<'c> {
type Target = Response<'c>;
#[inline(always)]
fn deref(&self) -> &Response<'c> {
&self.response
}
}
impl<'c> DerefMut for LocalResponse<'c> {
#[inline(always)]
fn deref_mut(&mut self) -> &mut Response<'c> {
&mut self.response
}
}
impl fmt::Debug for LocalResponse<'_> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
fmt::Debug::fmt(&self.response, f)
}
}
// TODO.async: Figure out a way to accomplish this
//impl<'c> Clone for LocalRequest<'c> {
// fn clone(&self) -> LocalRequest<'c> {
// LocalRequest {
// client: self.client,
// request: self.request.clone(),
// data: self.data.clone(),
// uri: self.uri.clone()
// }
// }
//}
#[cfg(test)]
mod tests {
use crate::Request;
use crate::local::Client;
#[test]
fn clone_unique_ptr() {
let client = Client::new(crate::ignite()).unwrap();
let r1 = client.get("/");
let r2 = r1.clone();
assert_ne!(
r1.inner() as *const Request<'_>,
r2.inner() as *const Request<'_>
);
}
// #[test]
// #[compile_fail]
// fn local_req_not_sync() {
// fn is_sync<T: Sync>() { }
// is_sync::<::local::LocalRequest>();
// }
// #[test]
// #[compile_fail]
// fn local_req_not_send() {
// fn is_send<T: Send>() { }
// is_send::<::local::LocalRequest>();
// }
// #[test]
// #[compile_fail]
// fn local_req_not_sync() {
// fn is_sync<T: Sync>() { }
// is_sync::<::local::LocalResponse>();
// }
// #[test]
// #[compile_fail]
// fn local_req_not_send() {
// fn is_send<T: Send>() { }
// is_send::<::local::LocalResponse>();
// }
// This checks that a response can't outlive the `Client`.
// #[compile_fail]
// fn test() {
// use {Rocket, local::Client};
// let rocket = Rocket::ignite();
// let res = {
// let mut client = Client::new(rocket).unwrap();
// client.get("/").dispatch()
// };
// // let client = Client::new(rocket).unwrap();
// // let res1 = client.get("/").dispatch();
// // let res2 = client.get("/").dispatch();
// }
// This checks that a response can't outlive the `Client`.
// #[compile_fail]
// fn test() {
// use {Rocket, local::Client};
// let rocket = Rocket::ignite();
// let res = {
// Client::new(rocket).unwrap()
// .get("/").dispatch();
// };
// // let client = Client::new(rocket).unwrap();
// // let res1 = client.get("/").dispatch();
// // let res2 = client.get("/").dispatch();
// }
// This checks that a response can't outlive the `Client`, in this case, by
// moving `client` while it is borrowed.
// #[compile_fail]
// fn test() {
// use {Rocket, local::Client};
// let rocket = Rocket::ignite();
// let client = Client::new(rocket).unwrap();
// let res = {
// let x = client.get("/").dispatch();
// let y = client.get("/").dispatch();
// (x, y)
// };
// let x = client;
// }
// #[compile_fail]
// fn test() {
// use {Rocket, local::Client};
// let rocket1 = Rocket::ignite();
// let rocket2 = Rocket::ignite();
// let client1 = Client::new(rocket1).unwrap();
// let client2 = Client::new(rocket2).unwrap();
// let res = {
// let mut res1 = client1.get("/");
// res1.client = &client2;
// res1
// };
// drop(client1);
// }
}
// TODO: Add test to check that `LocalRequest` is `Clone`.
}}

View File

@ -0,0 +1,67 @@
//! A structure representing a response from dispatching a local request.
//!
//! This structure is a thin wrapper around [`Response`]. It implements no
//! methods of its own; all functionality is exposed via the [`Deref`] and
//! [`DerefMut`] implementations with a target of `Response`. In other words,
//! when invoking methods, a `LocalResponse` can be treated exactly as if it
//! were a `Response`.
macro_rules! impl_response {
($import:literal $(@$prefix:tt $suffix:tt)? $name:ident) =>
{
impl<'c> $name<'c> {
/// Consumes `self` reads its body into a string. If `self` doesn't have
/// a body, reading fails, or string conversion (for non-UTF-8 bodies)
/// fails, returns `None`.
///
/// # Example
///
/// ```rust,ignore
#[doc = $import]
///
/// # Client::_test(|client| {
/// let client: Client = client;
/// let response = client.get("/").body("Hello!").dispatch();
/// assert_eq!(response.into_string().unwrap(), "Hello!");
/// # })
/// ```
#[inline(always)]
pub $($prefix)? fn into_string(self) -> Option<String> {
self._into_string() $(.$suffix)?
}
/// Consumes `self` and reads its body into a `Vec` of `u8` bytes. If
/// `self` doesn't have a body or reading fails returns `None`. Note
/// that `self`'s `body` is consumed after a call to this method.
///
/// # Example
///
/// ```rust,ignore
#[doc = $import]
///
/// # Client::_test(|client| {
/// let client: Client = client;
/// let response = client.get("/").body("Hello!").dispatch();
/// assert_eq!(response.into_bytes().unwrap(), "Hello!".as_bytes());
/// # })
/// ```
#[inline(always)]
pub $($prefix)? fn into_bytes(self) -> Option<Vec<u8>> {
self._into_bytes() $(.$suffix)?
}
}
impl std::fmt::Debug for LocalResponse<'_> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
self._response().fmt(f)
}
}
impl<'c> std::ops::Deref for LocalResponse<'c> {
type Target = Response<'c>;
fn deref(&self) -> &Response<'c> {
self._response()
}
}
}}

View File

@ -7,6 +7,7 @@ use std::str;
use yansi::Paint;
use state::{Container, Storage};
use futures::future::BoxFuture;
use atomic::Atomic;
use crate::request::{FromParam, FromSegments, FromRequest, Outcome};
use crate::request::{FromFormValue, FormItems, FormItem};
@ -27,16 +28,14 @@ type Indices = (usize, usize);
/// should likely only be used when writing [`FromRequest`] implementations. It
/// contains all of the information for a given web request except for the body
/// data. This includes the HTTP method, URI, cookies, headers, and more.
//#[derive(Clone)]
pub struct Request<'r> {
method: RwLock<Method>,
method: Atomic<Method>,
uri: Origin<'r>,
headers: HeaderMap<'r>,
remote: Option<SocketAddr>,
pub(crate) state: RequestState<'r>,
}
//#[derive(Clone)]
pub(crate) struct RequestState<'r> {
pub config: &'r Config,
pub managed: &'r Container,
@ -49,6 +48,42 @@ pub(crate) struct RequestState<'r> {
pub cache: Arc<Container>,
}
impl<'r> Request<'r> {
pub(crate) fn clone(&self) -> Self {
Request {
method: Atomic::new(self.method()),
uri: self.uri.clone(),
headers: self.headers.clone(),
remote: self.remote.clone(),
state: self.state.clone(),
}
}
}
impl<'r> RequestState<'r> {
fn clone(&self) -> RequestState<'r> {
let route = self.route.try_read()
.map(|r| r.clone())
.unwrap_or(None);
let cookies = self.cookies.try_lock()
.map(|j| j.clone())
.unwrap_or_else(|_| Some(CookieJar::new()));
RequestState {
config: self.config,
managed: self.managed,
path_segments: self.path_segments.clone(),
query_items: self.query_items.clone(),
route: RwLock::new(route),
cookies: Mutex::new(cookies),
accept: self.accept.clone(),
content_type: self.content_type.clone(),
cache: self.cache.clone(),
}
}
}
#[derive(Clone)]
pub(crate) struct IndexedFormItem {
raw: Indices,
@ -65,7 +100,7 @@ impl<'r> Request<'r> {
uri: Origin<'s>
) -> Request<'r> {
let mut request = Request {
method: RwLock::new(method),
method: Atomic::new(method),
uri: uri,
headers: HeaderMap::new(),
remote: None,
@ -101,7 +136,7 @@ impl<'r> Request<'r> {
/// ```
#[inline(always)]
pub fn method(&self) -> Method {
*self.method.read().unwrap()
self.method.load(atomic::Ordering::Acquire)
}
/// Set the method of `self`.
@ -827,7 +862,7 @@ impl<'r> Request<'r> {
/// during routing to override methods for re-routing.
#[inline(always)]
pub(crate) fn _set_method(&self, method: Method) {
*self.method.write().unwrap() = method;
self.method.store(method, atomic::Ordering::Release)
}
/// Convert from Hyper types into a Rocket Request.

View File

@ -68,6 +68,13 @@ impl<A, B> Body<A, B>
where A: AsyncRead + AsyncSeek + Send + Unpin,
B: AsyncRead + Send + Unpin
{
pub fn known_size(&self) -> Option<usize> {
match self {
Body::Sized(_, Some(known)) => Some(*known),
_ => None
}
}
/// Attempts to compute the size of `self` if it is `Body::Sized`. If it is
/// not, simply returned `None`. Also returned `None` if determining the
/// body's size failed.
@ -904,8 +911,7 @@ impl<'r> Response<'r> {
self.headers.remove(name);
}
/// Returns a mutable borrow of the body of `self`, if there is one. The
/// body is borrowed mutably to allow for reading.
/// Returns an immutable borrow of the body of `self`, if there is one.
///
/// # Example
///
@ -919,11 +925,34 @@ impl<'r> Response<'r> {
///
/// let string = "Hello, world!";
/// response.set_sized_body(string.len(), Cursor::new(string));
/// assert_eq!(response.body_string().await, Some("Hello, world!".to_string()));
/// assert!(response.body().is_some());
/// # })
/// ```
#[inline(always)]
pub fn body(&mut self) -> Option<&mut ResponseBody<'r>> {
pub fn body(&self) -> Option<&ResponseBody<'r>> {
self.body.as_ref()
}
/// Returns a mutable borrow of the body of `self`, if there is one. A
/// mutable borrow allows for reading the body.
///
/// # Example
///
/// ```rust
/// use std::io::Cursor;
/// use rocket::Response;
///
/// # rocket::async_test(async {
/// let mut response = Response::new();
/// assert!(response.body().is_none());
///
/// let string = "Hello, world!";
/// response.set_sized_body(string.len(), Cursor::new(string));
/// assert!(response.body_mut().is_some());
/// # })
/// ```
#[inline(always)]
pub fn body_mut(&mut self) -> Option<&mut ResponseBody<'r>> {
self.body.as_mut()
}
@ -1043,7 +1072,7 @@ impl<'r> Response<'r> {
///
/// let mut response = Response::new();
/// response.set_sized_body(string.len(), Cursor::new(string));
/// assert_eq!(response.body_string().await, Some("Hello, world!".to_string()));
/// assert_eq!(response.body_string().await.unwrap(), "Hello, world!");
/// # })
/// ```
pub fn set_sized_body<B, S>(&mut self, size: S, body: B)
@ -1067,7 +1096,7 @@ impl<'r> Response<'r> {
/// # rocket::async_test(async {
/// let mut response = Response::new();
/// response.set_streamed_body(repeat(97).take(5));
/// assert_eq!(response.body_string().await, Some("aaaaa".to_string()));
/// assert_eq!(response.body_string().await.unwrap(), "aaaaa");
/// # })
/// ```
#[inline(always)]
@ -1087,7 +1116,7 @@ impl<'r> Response<'r> {
/// # rocket::async_test(async {
/// let mut response = Response::new();
/// response.set_chunked_body(repeat(97).take(5), 10);
/// assert_eq!(response.body_string().await, Some("aaaaa".to_string()));
/// assert_eq!(response.body_string().await.unwrap(), "aaaaa");
/// # })
/// ```
#[inline(always)]
@ -1114,7 +1143,7 @@ impl<'r> Response<'r> {
/// let body = Body::Sized(Cursor::new(string), Some(string.len()));
/// response.set_raw_body::<Cursor<&'static str>, Cursor<&'static str>>(body);
///
/// assert_eq!(response.body_string().await, Some("Hello!".to_string()));
/// assert_eq!(response.body_string().await.unwrap(), "Hello!");
/// # })
/// ```
#[inline(always)]

View File

@ -42,7 +42,7 @@ impl<'r, R> Created<R> {
///
/// ```rust
/// # #![feature(proc_macro_hygiene)]
/// # use rocket::{get, routes, local::Client};
/// # use rocket::{get, routes, local::asynchronous::Client};
/// use rocket::response::status;
///
/// #[get("/")]
@ -53,7 +53,7 @@ impl<'r, R> Created<R> {
/// # rocket::async_test(async move {
/// # let rocket = rocket::ignite().mount("/", routes![create]);
/// # let client = Client::new(rocket).await.unwrap();
/// let mut response = client.get("/").dispatch().await;
/// let response = client.get("/").dispatch().await;
///
/// let loc = response.headers().get_one("Location");
/// assert_eq!(loc, Some("http://myservice.com/resource.json"));
@ -73,7 +73,7 @@ impl<'r, R> Created<R> {
///
/// ```rust
/// # #![feature(proc_macro_hygiene)]
/// # use rocket::{get, routes, local::Client};
/// # use rocket::{get, routes, local::asynchronous::Client};
/// use rocket::response::status;
///
/// #[get("/")]
@ -85,16 +85,16 @@ impl<'r, R> Created<R> {
/// # rocket::async_test(async move {
/// # let rocket = rocket::ignite().mount("/", routes![create]);
/// # let client = Client::new(rocket).await.unwrap();
/// let mut response = client.get("/").dispatch().await;
///
/// let body = response.body_string().await;
/// assert_eq!(body.unwrap(), "{ 'resource': 'Hello, world!' }");
/// let response = client.get("/").dispatch().await;
///
/// let loc = response.headers().get_one("Location");
/// assert_eq!(loc, Some("http://myservice.com/resource.json"));
///
/// let etag = response.headers().get_one("ETag");
/// assert_eq!(etag, None);
///
/// let body = response.into_string().await;
/// assert_eq!(body.unwrap(), "{ 'resource': 'Hello, world!' }");
/// # });
/// ```
pub fn body(mut self, responder: R) -> Self {
@ -109,7 +109,7 @@ impl<'r, R> Created<R> {
///
/// ```rust
/// # #![feature(proc_macro_hygiene)]
/// # use rocket::{get, routes, local::Client};
/// # use rocket::{get, routes, local::asynchronous::Client};
/// use rocket::response::status;
///
/// #[get("/")]
@ -121,16 +121,16 @@ impl<'r, R> Created<R> {
/// # rocket::async_test(async move {
/// # let rocket = rocket::ignite().mount("/", routes![create]);
/// # let client = Client::new(rocket).await.unwrap();
/// let mut response = client.get("/").dispatch().await;
///
/// let body = response.body_string().await;
/// assert_eq!(body.unwrap(), "{ 'resource': 'Hello, world!' }");
/// let response = client.get("/").dispatch().await;
///
/// let loc = response.headers().get_one("Location");
/// assert_eq!(loc, Some("http://myservice.com/resource.json"));
///
/// let etag = response.headers().get_one("ETag");
/// assert_eq!(etag, Some(r#""13046220615156895040""#));
///
/// let body = response.into_string().await;
/// assert_eq!(body.unwrap(), "{ 'resource': 'Hello, world!' }");
/// # });
/// ```
pub fn tagged_body(mut self, responder: R) -> Self where R: Hash {

View File

@ -263,7 +263,7 @@ impl Rocket {
})
};
match response.body() {
match response.body_mut() {
None => {
hyp_res = hyp_res.header(header::CONTENT_LENGTH, "0");
send_response(hyp_res, hyper::Body::empty())?;
@ -1112,6 +1112,7 @@ impl Cargo {
///
/// # rocket::async_test(async {
/// let mut rocket = rocket::ignite().manage(MyState("hello!"));
///
/// let cargo = rocket.inspect().await;
/// assert_eq!(cargo.state::<MyState>(), Some(&MyState("hello!")));
/// # });

View File

@ -16,7 +16,7 @@ fn rocket() -> Redirect {
mod test_absolute_uris_okay {
use super::*;
use rocket::local::Client;
use rocket::local::asynchronous::Client;
#[rocket::async_test]
async fn redirect_works() {

View File

@ -17,7 +17,7 @@ fn use_default() { }
mod conditionally_set_server_header {
use super::*;
use rocket::local::Client;
use rocket::local::asynchronous::Client;
#[rocket::async_test]
async fn do_not_overwrite_server_header() {

View File

@ -45,14 +45,14 @@ fn number(params: Form<ThingForm>) -> DerivedResponder {
#[rocket::async_test]
async fn test_derive_reexports() {
use rocket::local::Client;
use rocket::local::asynchronous::Client;
let rocket = rocket::ignite().mount("/", routes![index, number]);
let client = Client::new(rocket).await.unwrap();
let mut response = client.get("/").dispatch().await;
assert_eq!(response.body_string().await.unwrap(), "hello");
let response = client.get("/").dispatch().await;
assert_eq!(response.into_string().await.unwrap(), "hello");
let mut response = client.get("/?thing=b").dispatch().await;
assert_eq!(response.body_string().await.unwrap(), "b");
let response = client.get("/?thing=b").dispatch().await;
assert_eq!(response.into_string().await.unwrap(), "b");
}

View File

@ -22,7 +22,7 @@ mod fairing_before_head_strip {
use rocket::fairing::AdHoc;
use rocket::http::Method;
use rocket::local::Client;
use rocket::local::asynchronous::Client;
use rocket::http::Status;
use rocket::State;
@ -43,7 +43,7 @@ mod fairing_before_head_strip {
}));
let client = Client::new(rocket).await.unwrap();
let mut response = client.head("/").dispatch().await;
let response = client.head("/").dispatch().await;
assert_eq!(response.status(), Status::Ok);
assert!(response.body().is_none());
}
@ -74,7 +74,7 @@ mod fairing_before_head_strip {
}));
let client = Client::new(rocket).await.unwrap();
let mut response = client.head("/").dispatch().await;
let response = client.head("/").dispatch().await;
assert_eq!(response.status(), Status::Ok);
assert!(response.body().is_none());
}

View File

@ -23,7 +23,7 @@ fn used(flash: Option<FlashMessage<'_, '_>>) -> Option<String> {
}
mod flash_lazy_remove_tests {
use rocket::local::Client;
use rocket::local::asynchronous::Client;
use rocket::http::Status;
#[rocket::async_test]
@ -48,8 +48,8 @@ mod flash_lazy_remove_tests {
assert_eq!(response.status(), Status::Ok);
// Now use it.
let mut response = client.get("/use").dispatch().await;
assert_eq!(response.body_string().await, Some(FLASH_MESSAGE.into()));
let response = client.get("/use").dispatch().await;
assert_eq!(response.into_string().await, Some(FLASH_MESSAGE.into()));
// Now it should be gone.
let response = client.get("/unused").dispatch().await;

View File

@ -17,18 +17,18 @@ fn bug(form_data: Form<FormData>) -> &'static str {
mod tests {
use super::*;
use rocket::local::Client;
use rocket::local::asynchronous::Client;
use rocket::http::{Status, ContentType};
#[rocket::async_test]
async fn method_eval() {
let client = Client::new(rocket::ignite().mount("/", routes![bug])).await.unwrap();
let mut response = client.post("/")
let response = client.post("/")
.header(ContentType::Form)
.body("_method=patch&form_data=Form+data")
.dispatch().await;
assert_eq!(response.body_string().await, Some("OK".into()));
assert_eq!(response.into_string().await, Some("OK".into()));
}
#[rocket::async_test]

View File

@ -16,19 +16,19 @@ fn bug(form_data: Form<FormData>) -> String {
mod tests {
use super::*;
use rocket::local::Client;
use rocket::local::asynchronous::Client;
use rocket::http::ContentType;
use rocket::http::Status;
async fn check_decoding(raw: &str, decoded: &str) {
let client = Client::new(rocket::ignite().mount("/", routes![bug])).await.unwrap();
let mut response = client.post("/")
let response = client.post("/")
.header(ContentType::Form)
.body(format!("form_data={}", raw))
.dispatch().await;
assert_eq!(response.status(), Status::Ok);
assert_eq!(Some(decoded.to_string()), response.body_string().await);
assert_eq!(Some(decoded.to_string()), response.into_string().await);
}
#[rocket::async_test]

View File

@ -22,49 +22,39 @@ fn other() -> content::Json<&'static str> {
mod head_handling_tests {
use super::*;
use tokio::io::AsyncReadExt;
use rocket::Route;
use rocket::local::Client;
use rocket::local::asynchronous::Client;
use rocket::http::{Status, ContentType};
use rocket::response::ResponseBody;
fn routes() -> Vec<Route> {
routes![index, empty, other]
}
async fn assert_empty_sized_body(body: &mut ResponseBody<'_>, expected_size: usize) {
let size = body.size().await.expect("sized body");
assert_eq!(size, expected_size);
let mut buffer = vec![];
body.as_reader().read_to_end(&mut buffer).await.unwrap();
assert_eq!(buffer.len(), 0);
}
#[rocket::async_test]
async fn auto_head() {
let client = Client::new(rocket::ignite().mount("/", routes())).await.unwrap();
let mut response = client.head("/").dispatch().await;
assert_eq!(response.status(), Status::Ok);
assert_empty_sized_body(response.body().unwrap(), 13).await;
let response = client.head("/").dispatch().await;
let content_type: Vec<_> = response.headers().get("Content-Type").collect();
assert_eq!(content_type, vec![ContentType::Plain.to_string()]);
assert_eq!(response.status(), Status::Ok);
assert_eq!(response.body().unwrap().known_size(), Some(13));
assert!(response.into_bytes().await.unwrap().is_empty());
let mut response = client.head("/empty").dispatch().await;
let response = client.head("/empty").dispatch().await;
assert_eq!(response.status(), Status::NoContent);
assert!(response.body_bytes().await.is_none());
assert!(response.into_bytes().await.is_none());
}
#[rocket::async_test]
async fn user_head() {
let client = Client::new(rocket::ignite().mount("/", routes())).await.unwrap();
let mut response = client.head("/other").dispatch().await;
assert_eq!(response.status(), Status::Ok);
assert_empty_sized_body(response.body().unwrap(), 17).await;
let response = client.head("/other").dispatch().await;
let content_type: Vec<_> = response.headers().get("Content-Type").collect();
assert_eq!(content_type, vec![ContentType::JSON.to_string()]);
assert_eq!(response.status(), Status::Ok);
assert_eq!(response.body().unwrap().known_size(), Some(17));
assert!(response.into_bytes().await.unwrap().is_empty());
}
}

View File

@ -17,7 +17,7 @@ fn index(form: Form<Simple>) -> String {
mod limits_tests {
use rocket;
use rocket::config::{Environment, Config, Limits};
use rocket::local::Client;
use rocket::local::asynchronous::Client;
use rocket::http::{Status, ContentType};
fn rocket_with_forms_limit(limit: u64) -> rocket::Rocket {
@ -31,23 +31,23 @@ mod limits_tests {
#[rocket::async_test]
async fn large_enough() {
let client = Client::new(rocket_with_forms_limit(128)).await.unwrap();
let mut response = client.post("/")
let response = client.post("/")
.body("value=Hello+world")
.header(ContentType::Form)
.dispatch().await;
assert_eq!(response.body_string().await, Some("Hello world".into()));
assert_eq!(response.into_string().await, Some("Hello world".into()));
}
#[rocket::async_test]
async fn just_large_enough() {
let client = Client::new(rocket_with_forms_limit(17)).await.unwrap();
let mut response = client.post("/")
let response = client.post("/")
.body("value=Hello+world")
.header(ContentType::Form)
.dispatch().await;
assert_eq!(response.body_string().await, Some("Hello world".into()));
assert_eq!(response.into_string().await, Some("Hello world".into()));
}
#[rocket::async_test]
@ -64,11 +64,11 @@ mod limits_tests {
#[rocket::async_test]
async fn contracted() {
let client = Client::new(rocket_with_forms_limit(10)).await.unwrap();
let mut response = client.post("/")
let response = client.post("/")
.body("value=Hello+world")
.header(ContentType::Form)
.dispatch().await;
assert_eq!(response.body_string().await, Some("Hell".into()));
assert_eq!(response.into_string().await, Some("Hell".into()));
}
}

View File

@ -54,7 +54,7 @@ mod local_request_content_type_tests {
use super::*;
use rocket::Rocket;
use rocket::local::Client;
use rocket::local::asynchronous::Client;
use rocket::http::ContentType;
fn rocket() -> Rocket {
@ -65,29 +65,29 @@ mod local_request_content_type_tests {
async fn has_no_ct() {
let client = Client::new(rocket()).await.unwrap();
let mut req = client.post("/");
// assert_eq!(req.clone().dispatch().await.body_string().await, Some("Absent".to_string()));
assert_eq!(req.mut_dispatch().await.body_string().await, Some("Absent".to_string()));
assert_eq!(req.dispatch().await.body_string().await, Some("Absent".to_string()));
let req = client.post("/");
assert_eq!(req.clone().dispatch().await.into_string().await, Some("Absent".to_string()));
assert_eq!(req.clone().dispatch().await.into_string().await, Some("Absent".to_string()));
assert_eq!(req.dispatch().await.into_string().await, Some("Absent".to_string()));
let mut req = client.post("/data");
// assert_eq!(req.clone().dispatch().await.body_string().await, Some("Data Absent".to_string()));
assert_eq!(req.mut_dispatch().await.body_string().await, Some("Data Absent".to_string()));
assert_eq!(req.dispatch().await.body_string().await, Some("Data Absent".to_string()));
let req = client.post("/data");
assert_eq!(req.clone().dispatch().await.into_string().await, Some("Data Absent".to_string()));
assert_eq!(req.clone().dispatch().await.into_string().await, Some("Data Absent".to_string()));
assert_eq!(req.dispatch().await.into_string().await, Some("Data Absent".to_string()));
}
#[rocket::async_test]
async fn has_ct() {
let client = Client::new(rocket()).await.unwrap();
let mut req = client.post("/").header(ContentType::JSON);
// assert_eq!(req.clone().dispatch().await.body_string().await, Some("Present".to_string()));
assert_eq!(req.mut_dispatch().await.body_string().await, Some("Present".to_string()));
assert_eq!(req.dispatch().await.body_string().await, Some("Present".to_string()));
let req = client.post("/").header(ContentType::JSON);
assert_eq!(req.clone().dispatch().await.into_string().await, Some("Present".to_string()));
assert_eq!(req.clone().dispatch().await.into_string().await, Some("Present".to_string()));
assert_eq!(req.dispatch().await.into_string().await, Some("Present".to_string()));
let mut req = client.post("/data").header(ContentType::JSON);
// assert_eq!(req.clone().dispatch().await.body_string().await, Some("Data Present".to_string()));
assert_eq!(req.mut_dispatch().await.body_string().await, Some("Data Present".to_string()));
assert_eq!(req.dispatch().await.body_string().await, Some("Data Present".to_string()));
let req = client.post("/data").header(ContentType::JSON);
assert_eq!(req.clone().dispatch().await.into_string().await, Some("Data Present".to_string()));
assert_eq!(req.clone().dispatch().await.into_string().await, Some("Data Present".to_string()));
assert_eq!(req.dispatch().await.into_string().await, Some("Data Present".to_string()));
}
}

View File

@ -18,7 +18,7 @@ mod private_cookie_test {
mod tests {
use super::*;
use rocket::local::Client;
use rocket::local::asynchronous::Client;
use rocket::http::Cookie;
use rocket::http::Status;
@ -28,10 +28,10 @@ mod private_cookie_test {
let client = Client::new(rocket).await.unwrap();
let req = client.get("/").private_cookie(Cookie::new("cookie_name", "cookie_value"));
let mut response = req.dispatch().await;
let response = req.dispatch().await;
assert_eq!(response.body_string().await, Some("cookie_value".into()));
assert_eq!(response.headers().get_one("Set-Cookie"), None);
assert_eq!(response.into_string().await, Some("cookie_value".into()));
}
#[rocket::async_test]

View File

@ -44,20 +44,20 @@ fn rocket() -> rocket::Rocket {
mod nested_fairing_attaches_tests {
use super::*;
use rocket::local::Client;
use rocket::local::asynchronous::Client;
#[rocket::async_test]
async fn test_counts() {
let client = Client::new(rocket()).await.unwrap();
let mut response = client.get("/").dispatch().await;
assert_eq!(response.body_string().await, Some("1, 1".into()));
let response = client.get("/").dispatch().await;
assert_eq!(response.into_string().await, Some("1, 1".into()));
let mut response = client.get("/").dispatch().await;
assert_eq!(response.body_string().await, Some("1, 2".into()));
let response = client.get("/").dispatch().await;
assert_eq!(response.into_string().await, Some("1, 2".into()));
client.get("/").dispatch().await;
client.get("/").dispatch().await;
let mut response = client.get("/").dispatch().await;
assert_eq!(response.body_string().await, Some("1, 5".into()));
let response = client.get("/").dispatch().await;
assert_eq!(response.into_string().await, Some("1, 5".into()));
}
}

View File

@ -26,7 +26,7 @@ mod tests {
use super::*;
use rocket::Rocket;
use rocket::local::Client;
use rocket::local::asynchronous::Client;
use rocket::http::{Status, ContentType};
fn rocket() -> Rocket {
@ -44,12 +44,13 @@ mod tests {
req.add_header(ct);
}
let mut response = req.dispatch().await;
let body_str = response.body_string().await;
let response = req.dispatch().await;
let status = response.status();
let body_str = response.into_string().await;
let body: Option<&'static str> = $body;
match body {
Some(string) => assert_eq!(body_str, Some(string.to_string())),
None => assert_eq!(response.status(), Status::NotFound)
None => assert_eq!(status, Status::NotFound)
}
)
}

View File

@ -11,7 +11,7 @@ fn not_found() -> Redirect {
mod tests {
use super::*;
use rocket::local::Client;
use rocket::local::asynchronous::Client;
use rocket::http::Status;
#[rocket::async_test]

View File

@ -13,11 +13,11 @@ fn files(route: &Route, path: PathBuf) -> String {
mod route_guard_tests {
use super::*;
use rocket::local::Client;
use rocket::local::asynchronous::Client;
async fn assert_path(client: &Client, path: &str) {
let mut res = client.get(path).dispatch().await;
assert_eq!(res.body_string().await, Some(path.into()));
let res = client.get(path).dispatch().await;
assert_eq!(res.into_string().await, Some(path.into()));
}
#[rocket::async_test]

View File

@ -31,7 +31,7 @@ fn dual(user: String, path: Segments<'_>) -> String {
mod tests {
use super::*;
use rocket::local::Client;
use rocket::local::asynchronous::Client;
#[rocket::async_test]
async fn segments_works() {
@ -47,8 +47,8 @@ mod tests {
"/static", "/point/static"]
{
let path = "this/is/the/path/we/want";
let mut response = client.get(format!("{}/{}", prefix, path)).dispatch().await;
assert_eq!(response.body_string().await, Some(path.into()));
let response = client.get(format!("{}/{}", prefix, path)).dispatch().await;
assert_eq!(response.into_string().await, Some(path.into()));
}
}
}

View File

@ -22,7 +22,7 @@ fn lenient<'r>(form: LenientForm<MyForm<'r>>) -> String {
mod strict_and_lenient_forms_tests {
use super::*;
use rocket::local::Client;
use rocket::local::asynchronous::Client;
use rocket::http::{Status, ContentType};
const FIELD_VALUE: &str = "just_some_value";
@ -34,13 +34,13 @@ mod strict_and_lenient_forms_tests {
#[rocket::async_test]
async fn test_strict_form() {
let client = client().await;
let mut response = client.post("/strict")
let response = client.post("/strict")
.header(ContentType::Form)
.body(format!("field={}", FIELD_VALUE))
.dispatch().await;
assert_eq!(response.status(), Status::Ok);
assert_eq!(response.body_string().await, Some(FIELD_VALUE.into()));
assert_eq!(response.into_string().await, Some(FIELD_VALUE.into()));
let response = client.post("/strict")
.header(ContentType::Form)
@ -53,20 +53,20 @@ mod strict_and_lenient_forms_tests {
#[rocket::async_test]
async fn test_lenient_form() {
let client = client().await;
let mut response = client.post("/lenient")
let response = client.post("/lenient")
.header(ContentType::Form)
.body(format!("field={}", FIELD_VALUE))
.dispatch().await;
assert_eq!(response.status(), Status::Ok);
assert_eq!(response.body_string().await, Some(FIELD_VALUE.into()));
assert_eq!(response.into_string().await, Some(FIELD_VALUE.into()));
let mut response = client.post("/lenient")
let response = client.post("/lenient")
.header(ContentType::Form)
.body(format!("field={}&extra=whoops", FIELD_VALUE))
.dispatch().await;
assert_eq!(response.status(), Status::Ok);
assert_eq!(response.body_string().await, Some(FIELD_VALUE.into()));
assert_eq!(response.into_string().await, Some(FIELD_VALUE.into()));
}
}

View File

@ -29,7 +29,7 @@ fn rocket() -> rocket::Rocket {
mod tests {
use super::*;
use rocket::local::Client;
use rocket::local::asynchronous::Client;
use rocket::http::{Status, uri::Uri};
#[rocket::async_test]
@ -52,8 +52,8 @@ mod tests {
async fn uri_percent_encoding_get() {
let client = Client::new(rocket()).await.unwrap();
let name = Uri::percent_encode(NAME);
let mut response = client.get(format!("/hello/{}", name)).dispatch().await;
let response = client.get(format!("/hello/{}", name)).dispatch().await;
assert_eq!(response.status(), Status::Ok);
assert_eq!(response.body_string().await.unwrap(), format!("Hello, {}!", NAME));
assert_eq!(response.into_string().await.unwrap(), format!("Hello, {}!", NAME));
}
}

View File

@ -2,7 +2,7 @@ use rocket::{self, State};
use rocket::fairing::AdHoc;
use rocket::config::{self, Config, Environment, LoggingLevel};
use rocket::http::Status;
use rocket::local::Client;
use rocket::local::asynchronous::Client;
struct LocalConfig(Config);

View File

@ -1,14 +1,14 @@
use super::Person;
use rocket::http::{Accept, ContentType, Header, MediaType, Method, Status};
use rocket::local::Client;
use rocket::local::asynchronous::Client;
async fn test<H>(method: Method, uri: &str, header: H, status: Status, body: String)
where H: Into<Header<'static>>
{
let client = Client::new(super::rocket()).await.unwrap();
let mut response = client.req(method, uri).header(header).dispatch().await;
let response = client.req(method, uri).header(header).dispatch().await;
assert_eq!(response.status(), status);
assert_eq!(response.body_string().await, Some(body));
assert_eq!(response.into_string().await, Some(body));
}
#[rocket::async_test]

View File

@ -1,7 +1,7 @@
use std::collections::HashMap;
use super::rocket;
use rocket::local::Client;
use rocket::local::asynchronous::Client;
use rocket::http::*;
use rocket_contrib::templates::Template;
@ -24,13 +24,13 @@ async fn test_submit() {
async fn test_body(optional_cookie: Option<Cookie<'static>>, expected_body: String) {
// Attach a cookie if one is given.
let client = Client::new(rocket()).await.unwrap();
let mut response = match optional_cookie {
let response = match optional_cookie {
Some(cookie) => client.get("/").cookie(cookie).dispatch().await,
None => client.get("/").dispatch().await,
};
assert_eq!(response.status(), Status::Ok);
assert_eq!(response.body_string().await, Some(expected_body));
assert_eq!(response.into_string().await, Some(expected_body));
}
#[rocket::async_test]

View File

@ -1,4 +1,4 @@
use rocket::local::Client;
use rocket::local::asynchronous::Client;
use rocket::http::Status;
async fn test(uri: &str, status: Status, body: String) {
@ -7,9 +7,9 @@ async fn test(uri: &str, status: Status, body: String) {
.register(catchers![super::not_found]);
let client = Client::new(rocket).await.unwrap();
let mut response = client.get(uri).dispatch().await;
let response = client.get(uri).dispatch().await;
assert_eq!(response.status(), status);
assert_eq!(response.body_string().await, Some(body));
assert_eq!(response.into_string().await, Some(body));
}
#[rocket::async_test]

View File

@ -1,11 +1,11 @@
use super::rocket;
use rocket::local::Client;
use rocket::local::asynchronous::Client;
#[rocket::async_test]
async fn rewrite_get_put() {
let client = Client::new(rocket()).await.unwrap();
let mut response = client.get("/").dispatch().await;
assert_eq!(response.body_string().await, Some("Hello, fairings!".into()));
let response = client.get("/").dispatch().await;
assert_eq!(response.into_string().await, Some("Hello, fairings!".into()));
}
#[rocket::async_test]
@ -16,16 +16,16 @@ async fn counts() {
client.get("/").dispatch().await;
// Check the GET count, taking into account _this_ GET request.
let mut response = client.get("/counts").dispatch().await;
assert_eq!(response.body_string().await, Some("Get: 2\nPost: 0".into()));
let response = client.get("/counts").dispatch().await;
assert_eq!(response.into_string().await, Some("Get: 2\nPost: 0".into()));
// Issue 1 more GET request and a POST.
client.get("/").dispatch().await;
client.post("/").dispatch().await;
// Check the counts.
let mut response = client.get("/counts").dispatch().await;
assert_eq!(response.body_string().await, Some("Get: 4\nPost: 1".into()));
let response = client.get("/counts").dispatch().await;
assert_eq!(response.into_string().await, Some("Get: 4\nPost: 1".into()));
}
#[rocket::async_test]
@ -33,6 +33,6 @@ async fn token() {
let client = Client::new(rocket()).await.unwrap();
// Ensure the token is '123', which is what we have in `Rocket.toml`.
let mut res = client.get("/token").dispatch().await;
assert_eq!(res.body_string().await, Some("123".into()));
let res = client.get("/token").dispatch().await;
assert_eq!(res.into_string().await, Some("123".into()));
}

View File

@ -1,7 +1,7 @@
use std::fmt;
use super::{rocket, FormInput, FormOption};
use rocket::local::Client;
use rocket::local::blocking::Client;
use rocket::http::ContentType;
impl fmt::Display for FormOption {
@ -16,12 +16,12 @@ impl fmt::Display for FormOption {
macro_rules! assert_form_eq {
($client:expr, $form_str:expr, $expected:expr) => {{
let mut res = $client.post("/")
let res = $client.post("/")
.header(ContentType::Form)
.body($form_str)
.dispatch().await;
.dispatch();
assert_eq!(res.body_string().await, Some($expected));
assert_eq!(res.into_string(), Some($expected));
}};
}
@ -40,9 +40,9 @@ macro_rules! assert_valid_raw_form {
}};
}
#[rocket::async_test]
async fn test_good_forms() {
let client = Client::new(rocket()).await.unwrap();
#[test]
fn test_good_forms() {
let client = Client::new(rocket()).unwrap();
let mut input = FormInput {
checkbox: true,
number: 310,
@ -119,9 +119,9 @@ macro_rules! assert_invalid_raw_form {
}};
}
#[rocket::async_test]
async fn check_semantically_invalid_forms() {
let client = Client::new(rocket()).await.unwrap();
#[test]
fn check_semantically_invalid_forms() {
let client = Client::new(rocket()).unwrap();
let mut form_vals = ["true", "1", "a", "hi", "hey", "b"];
form_vals[0] = "not true";
@ -175,17 +175,17 @@ async fn check_semantically_invalid_forms() {
assert_invalid_raw_form!(&client, "");
}
#[rocket::async_test]
async fn check_structurally_invalid_forms() {
let client = Client::new(rocket()).await.unwrap();
#[test]
fn check_structurally_invalid_forms() {
let client = Client::new(rocket()).unwrap();
assert_invalid_raw_form!(&client, "==&&&&&&==");
assert_invalid_raw_form!(&client, "a&=b");
assert_invalid_raw_form!(&client, "=");
}
#[rocket::async_test]
async fn check_bad_utf8() {
let client = Client::new(rocket()).await.unwrap();
#[test]
fn check_bad_utf8() {
let client = Client::new(rocket()).unwrap();
unsafe {
let bad_str = std::str::from_utf8_unchecked(b"a=\xff");
assert_form_eq!(&client, bad_str, "Form input was invalid UTF-8.".into());

View File

@ -1,5 +1,5 @@
use super::rocket;
use rocket::local::Client;
use rocket::local::asynchronous::Client;
use rocket::http::{ContentType, Status};
fn test_login<T>(user: &str, pass: &str, age: &str, status: Status, body: T)
@ -8,14 +8,14 @@ fn test_login<T>(user: &str, pass: &str, age: &str, status: Status, body: T)
rocket::async_test(async move {
let client = Client::new(rocket()).await.unwrap();
let query = format!("username={}&password={}&age={}", user, pass, age);
let mut response = client.post("/login")
let response = client.post("/login")
.header(ContentType::Form)
.body(&query)
.dispatch().await;
assert_eq!(response.status(), status);
if let Some(expected_str) = body.into() {
let body_str = response.body_string().await;
let body_str = response.into_string().await;
assert!(body_str.map_or(false, |s| s.contains(expected_str)));
}
})

View File

@ -1,6 +1,6 @@
use super::{rocket, TemplateContext};
use rocket::local::Client;
use rocket::local::asynchronous::Client;
use rocket::http::Method::*;
use rocket::http::Status;
use rocket_contrib::templates::Template;
@ -8,7 +8,7 @@ use rocket_contrib::templates::Template;
macro_rules! dispatch {
($method:expr, $path:expr, |$client:ident, $response:ident| $body:expr) => ({
let $client = Client::new(rocket()).await.unwrap();
let mut $response = $client.req($method, $path).dispatch().await;
let $response = $client.req($method, $path).dispatch().await;
$body
})
}
@ -34,7 +34,7 @@ async fn test_root() {
let expected = Template::show(client.cargo(), "error/404", &map).unwrap();
assert_eq!(response.status(), Status::NotFound);
assert_eq!(response.body_string().await, Some(expected));
assert_eq!(response.into_string().await, Some(expected));
});
}
}
@ -52,7 +52,7 @@ async fn test_name() {
let expected = Template::show(client.cargo(), "index", &context).unwrap();
assert_eq!(response.status(), Status::Ok);
assert_eq!(response.body_string().await, Some(expected));
assert_eq!(response.into_string().await, Some(expected));
});
}
@ -65,6 +65,6 @@ async fn test_404() {
let expected = Template::show(client.cargo(), "error/404", &map).unwrap();
assert_eq!(response.status(), Status::NotFound);
assert_eq!(response.body_string().await, Some(expected));
assert_eq!(response.into_string().await, Some(expected));
});
}

View File

@ -1,10 +1,10 @@
use rocket::{self, local::Client};
use rocket::{self, local::asynchronous::Client};
#[rocket::async_test]
async fn hello_world() {
let client = Client::new(super::rocket()).await.unwrap();
let mut response = client.get("/").dispatch().await;
assert_eq!(response.body_string().await, Some("Hello, Rust 2018!".into()));
let response = client.get("/").dispatch().await;
assert_eq!(response.into_string().await, Some("Hello, Rust 2018!".into()));
}
// Tests unrelated to the example.
@ -31,19 +31,19 @@ mod scoped_uri_tests {
.mount("/", rocket::routes![inner::hello])
}
use rocket::local::Client;
use rocket::local::asynchronous::Client;
#[rocket::async_test]
async fn test_inner_hello() {
let client = Client::new(rocket()).await.unwrap();
let mut response = client.get("/").dispatch().await;
assert_eq!(response.body_string().await, Some("Hello! Try /Rust%202018.".into()));
let response = client.get("/").dispatch().await;
assert_eq!(response.into_string().await, Some("Hello! Try /Rust%202018.".into()));
}
#[rocket::async_test]
async fn test_hello_name() {
let client = Client::new(rocket()).await.unwrap();
let mut response = client.get("/Rust%202018").dispatch().await;
assert_eq!(response.body_string().await.unwrap(), "Hello, Rust 2018! This is /Rust%202018.");
let response = client.get("/Rust%202018").dispatch().await;
assert_eq!(response.into_string().await.unwrap(), "Hello, Rust 2018! This is /Rust%202018.");
}
}

View File

@ -1,9 +1,9 @@
use rocket::local::Client;
use rocket::local::asynchronous::Client;
use rocket::http::Status;
async fn test(uri: String, expected: String) {
let client = Client::new(super::rocket()).await.unwrap();
assert_eq!(client.get(&uri).dispatch().await.body_string().await, Some(expected));
assert_eq!(client.get(&uri).dispatch().await.into_string().await, Some(expected));
}
async fn test_404(uri: &'static str) {

View File

@ -1,8 +1,8 @@
use rocket::local::Client;
use rocket::local::asynchronous::Client;
#[rocket::async_test]
async fn hello_world() {
let client = Client::new(super::rocket()).await.unwrap();
let mut response = client.get("/").dispatch().await;
assert_eq!(response.body_string().await, Some("Hello, world!".into()));
let response = client.get("/").dispatch().await;
assert_eq!(response.into_string().await, Some("Hello, world!".into()));
}

View File

@ -1,5 +1,5 @@
use crate::rocket;
use rocket::local::Client;
use rocket::local::asynchronous::Client;
use rocket::http::{Status, ContentType};
#[rocket::async_test]
@ -7,18 +7,17 @@ async fn bad_get_put() {
let client = Client::new(rocket()).await.unwrap();
// Try to get a message with an ID that doesn't exist.
let mut res = client.get("/message/99").header(ContentType::JSON).dispatch().await;
let res = client.get("/message/99").header(ContentType::JSON).dispatch().await;
assert_eq!(res.status(), Status::NotFound);
let body = res.body_string().await.unwrap();
let body = res.into_string().await.unwrap();
assert!(body.contains("error"));
assert!(body.contains("Resource was not found."));
// Try to get a message with an invalid ID.
let mut res = client.get("/message/hi").header(ContentType::JSON).dispatch().await;
let body = res.body_string().await.unwrap();
let res = client.get("/message/hi").header(ContentType::JSON).dispatch().await;
assert_eq!(res.status(), Status::NotFound);
assert!(body.contains("error"));
assert!(res.into_string().await.unwrap().contains("error"));
// Try to put a message without a proper body.
let res = client.put("/message/80").header(ContentType::JSON).dispatch().await;
@ -50,9 +49,9 @@ async fn post_get_put_get() {
assert_eq!(res.status(), Status::Ok);
// Check that the message exists with the correct contents.
let mut res = client.get("/message/1").header(ContentType::JSON).dispatch().await;
let res = client.get("/message/1").header(ContentType::JSON).dispatch().await;
assert_eq!(res.status(), Status::Ok);
let body = res.body_string().await.unwrap();
let body = res.into_string().await.unwrap();
assert!(body.contains("Hello, world!"));
// Change the message contents.
@ -64,9 +63,9 @@ async fn post_get_put_get() {
assert_eq!(res.status(), Status::Ok);
// Check that the message exists with the updated contents.
let mut res = client.get("/message/1").header(ContentType::JSON).dispatch().await;
let res = client.get("/message/1").header(ContentType::JSON).dispatch().await;
assert_eq!(res.status(), Status::Ok);
let body = res.body_string().await.unwrap();
let body = res.into_string().await.unwrap();
assert!(!body.contains("Hello, world!"));
assert!(body.contains("Bye bye, world!"));
}

View File

@ -1,4 +1,4 @@
use rocket::local::Client;
use rocket::local::asynchronous::Client;
use rocket::http::Status;
#[rocket::async_test]
@ -8,6 +8,6 @@ async fn test_push_pop() {
let response = client.put("/push?event=test1").dispatch().await;
assert_eq!(response.status(), Status::Ok);
let mut response = client.get("/pop").dispatch().await;
assert_eq!(response.body_string().await, Some("test1".to_string()));
let response = client.get("/pop").dispatch().await;
assert_eq!(response.into_string().await, Some("test1".to_string()));
}

View File

@ -1,13 +1,13 @@
use super::*;
use rocket::local::Client;
use rocket::local::asynchronous::Client;
use rocket::http::{ContentType, Status};
fn test(uri: &str, content_type: ContentType, status: Status, body: String) {
rocket::async_test(async move {
let client = Client::new(rocket()).await.unwrap();
let mut response = client.get(uri).header(content_type).dispatch().await;
let response = client.get(uri).header(content_type).dispatch().await;
assert_eq!(response.status(), status);
assert_eq!(response.body_string().await, Some(body));
assert_eq!(response.into_string().await, Some(body));
})
}
@ -46,9 +46,9 @@ async fn test_upload() {
assert_eq!(response.status(), Status::Ok);
// Ensure we get back the same body.
let mut response = client.get("/upload").dispatch().await;
let response = client.get("/upload").dispatch().await;
assert_eq!(response.status(), Status::Ok);
assert_eq!(response.body_string().await, Some(expected_body));
assert_eq!(response.into_string().await, Some(expected_body));
}
#[test]

View File

@ -1,5 +1,5 @@
use crate::rocket;
use rocket::local::Client;
use rocket::local::asynchronous::Client;
use rocket::http::{Status, ContentType};
#[derive(Serialize, Deserialize)]
@ -11,12 +11,12 @@ struct Message {
#[rocket::async_test]
async fn msgpack_get() {
let client = Client::new(rocket()).await.unwrap();
let mut res = client.get("/message/1").header(ContentType::MsgPack).dispatch().await;
let res = client.get("/message/1").header(ContentType::MsgPack).dispatch().await;
assert_eq!(res.status(), Status::Ok);
assert_eq!(res.content_type(), Some(ContentType::MsgPack));
// Check that the message is `[1, "Hello, world!"]`
assert_eq!(&res.body_bytes().await.unwrap(),
assert_eq!(&res.into_bytes().await.unwrap(),
&[146, 1, 173, 72, 101, 108, 108, 111, 44, 32, 119, 111, 114, 108, 100, 33]);
}
@ -24,11 +24,11 @@ async fn msgpack_get() {
async fn msgpack_post() {
// Dispatch request with a message of `[2, "Goodbye, world!"]`.
let client = Client::new(rocket()).await.unwrap();
let mut res = client.post("/message")
let res = client.post("/message")
.header(ContentType::MsgPack)
.body(&[146, 2, 175, 71, 111, 111, 100, 98, 121, 101, 44, 32, 119, 111, 114, 108, 100, 33])
.dispatch().await;
assert_eq!(res.status(), Status::Ok);
assert_eq!(res.body_string().await, Some("Goodbye, world!".into()));
assert_eq!(res.into_string().await, Some("Goodbye, world!".into()));
}

View File

@ -1,11 +1,11 @@
use rocket::local::Client;
use rocket::local::asynchronous::Client;
use rocket::http::Status;
async fn test_200(uri: &str, expected_body: &str) {
let client = Client::new(super::rocket()).await.unwrap();
let mut response = client.get(uri).dispatch().await;
let response = client.get(uri).dispatch().await;
assert_eq!(response.status(), Status::Ok);
assert_eq!(response.body_string().await, Some(expected_body.to_string()));
assert_eq!(response.into_string().await, Some(expected_body.to_string()));
}
async fn test_303(uri: &str, expected_location: &str) {

View File

@ -1,5 +1,5 @@
use super::{rocket, index};
use rocket::local::Client;
use rocket::local::asynchronous::Client;
use rocket::http::{Status, ContentType};
fn extract_id(from: &str) -> Option<String> {
@ -11,23 +11,23 @@ async fn check_index() {
let client = Client::new(rocket()).await.unwrap();
// Ensure the index returns what we expect.
let mut response = client.get("/").dispatch().await;
let response = client.get("/").dispatch().await;
assert_eq!(response.status(), Status::Ok);
assert_eq!(response.content_type(), Some(ContentType::Plain));
assert_eq!(response.body_string().await, Some(index().into()))
assert_eq!(response.into_string().await, Some(index().into()))
}
async fn upload_paste(client: &Client, body: &str) -> String {
let mut response = client.post("/").body(body).dispatch().await;
let response = client.post("/").body(body).dispatch().await;
assert_eq!(response.status(), Status::Ok);
assert_eq!(response.content_type(), Some(ContentType::Plain));
extract_id(&response.body_string().await.unwrap()).unwrap()
extract_id(&response.into_string().await.unwrap()).unwrap()
}
async fn download_paste(client: &Client, id: &str) -> String {
let mut response = client.get(format!("/{}", id)).dispatch().await;
let response = client.get(format!("/{}", id)).dispatch().await;
assert_eq!(response.status(), Status::Ok);
response.body_string().await.unwrap()
response.into_string().await.unwrap()
}
#[rocket::async_test]

View File

@ -1,5 +1,5 @@
use super::rocket;
use rocket::local::Client;
use rocket::local::asynchronous::Client;
use rocket::http::Status;
macro_rules! run_test {
@ -14,12 +14,12 @@ macro_rules! run_test {
#[rocket::async_test]
async fn age_and_name_params() {
run_test!("?age=10&first-name=john", |response| {
assert_eq!(response.body_string().await,
assert_eq!(response.into_string().await,
Some("Hello, 10 year old named john!".into()));
});
run_test!("?age=20&first-name=john", |response| {
assert_eq!(response.body_string().await,
assert_eq!(response.into_string().await,
Some("20 years old? Hi, john!".into()));
});
}
@ -27,12 +27,12 @@ async fn age_and_name_params() {
#[rocket::async_test]
async fn age_param_only() {
run_test!("?age=10", |response| {
assert_eq!(response.body_string().await,
assert_eq!(response.into_string().await,
Some("We're gonna need a name, and only a name.".into()));
});
run_test!("?age=20", |response| {
assert_eq!(response.body_string().await,
assert_eq!(response.into_string().await,
Some("We're gonna need a name, and only a name.".into()));
});
}
@ -40,19 +40,19 @@ async fn age_param_only() {
#[rocket::async_test]
async fn name_param_only() {
run_test!("?first-name=John", |response| {
assert_eq!(response.body_string().await, Some("Hello John!".into()));
assert_eq!(response.into_string().await, Some("Hello John!".into()));
});
}
#[rocket::async_test]
async fn no_params() {
run_test!("", |response| {
assert_eq!(response.body_string().await,
assert_eq!(response.into_string().await,
Some("We're gonna need a name, and only a name.".into()));
});
run_test!("?", |response| {
assert_eq!(response.body_string().await,
assert_eq!(response.into_string().await,
Some("We're gonna need a name, and only a name.".into()));
});
}
@ -60,12 +60,12 @@ async fn no_params() {
#[rocket::async_test]
async fn extra_params() {
run_test!("?age=20&first-name=Bob&extra", |response| {
assert_eq!(response.body_string().await,
assert_eq!(response.into_string().await,
Some("20 years old? Hi, Bob!".into()));
});
run_test!("?age=30&first-name=Bob&extra", |response| {
assert_eq!(response.body_string().await,
assert_eq!(response.into_string().await,
Some("We're gonna need a name, and only a name.".into()));
});
}

View File

@ -1,9 +1,9 @@
use rocket::local::Client;
use rocket::local::asynchronous::Client;
async fn test(uri: String, expected: String) {
let client = Client::new(super::rocket()).await.unwrap();
let mut response = client.get(&uri).dispatch().await;
assert_eq!(response.body_string().await, Some(expected));
let response = client.get(&uri).dispatch().await;
assert_eq!(response.into_string().await, Some(expected));
}
#[rocket::async_test]

View File

@ -1,9 +1,9 @@
use super::rocket;
use rocket::local::Client;
use rocket::local::asynchronous::Client;
#[rocket::async_test]
async fn hello() {
let client = Client::new(rocket()).await.unwrap();
let mut response = client.get("/").dispatch().await;
assert_eq!(response.body_string().await, Some("Rocketeer".into()));
let response = client.get("/").dispatch().await;
assert_eq!(response.into_string().await, Some("Rocketeer".into()));
}

View File

@ -1,4 +1,4 @@
use rocket::local::Client;
use rocket::local::asynchronous::Client;
use rocket::http::{Status, ContentType};
use std::env;
@ -10,8 +10,8 @@ const UPLOAD_CONTENTS: &str = "Hey! I'm going to be uploaded. :D Yay!";
#[rocket::async_test]
async fn test_index() {
let client = Client::new(super::rocket()).await.unwrap();
let mut res = client.get("/").dispatch().await;
assert_eq!(res.body_string().await, Some(super::index().to_string()));
let res = client.get("/").dispatch().await;
assert_eq!(res.into_string().await, Some(super::index().to_string()));
}
#[rocket::async_test]
@ -22,13 +22,13 @@ async fn test_raw_upload() {
// Do the upload. Make sure we get the expected results.
let client = Client::new(super::rocket()).await.unwrap();
let mut res = client.post("/upload")
let res = client.post("/upload")
.header(ContentType::Plain)
.body(UPLOAD_CONTENTS)
.dispatch().await;
assert_eq!(res.status(), Status::Ok);
assert_eq!(res.body_string().await, Some(UPLOAD_CONTENTS.len().to_string()));
assert_eq!(res.into_string().await, Some(UPLOAD_CONTENTS.len().to_string()));
// Ensure we find the body in the /tmp/upload.txt file.
let mut file_contents = String::new();

View File

@ -1,4 +1,4 @@
use rocket::local::Client;
use rocket::local::asynchronous::Client;
use rocket::http::Status;
async fn client() -> Client {
@ -9,7 +9,7 @@ async fn client() -> Client {
#[rocket::async_test]
async fn test_root() {
let client = client().await;
let mut response = client.get("/").dispatch().await;
let response = client.get("/").dispatch().await;
assert!(response.body().is_none());
assert_eq!(response.status(), Status::SeeOther);
@ -25,6 +25,6 @@ async fn test_root() {
#[rocket::async_test]
async fn test_login() {
let client = client().await;
let mut r = client.get("/login").dispatch().await;
assert_eq!(r.body_string().await, Some("Hi! Please log in before continuing.".into()));
let r = client.get("/login").dispatch().await;
assert_eq!(r.into_string().await, Some("Hi! Please log in before continuing.".into()));
}

View File

@ -29,7 +29,7 @@ fn rocket() -> rocket::Rocket {
#[cfg(test)]
mod test {
use rocket::local::Client;
use rocket::local::asynchronous::Client;
use rocket::http::Header;
async fn test_header_count<'h>(headers: Vec<Header<'static>>) {
@ -39,9 +39,9 @@ mod test {
req.add_header(header);
}
let mut response = req.dispatch().await;
let response = req.dispatch().await;
let expect = format!("Your request contained {} headers!", headers.len());
assert_eq!(response.body_string().await, Some(expect));
assert_eq!(response.into_string().await, Some(expect));
}
#[rocket::async_test]

View File

@ -1,7 +1,7 @@
use std::sync::atomic::Ordering;
use super::{rocket, Atomics};
use rocket::local::Client;
use rocket::local::asynchronous::Client;
#[rocket::async_test]
async fn test() {

View File

@ -1,6 +1,6 @@
use super::rocket;
use rocket::Response;
use rocket::local::Client;
use rocket::local::asynchronous::Client;
use rocket::http::{Status, Cookie, ContentType};
fn user_id_cookie(response: &Response<'_>) -> Option<Cookie<'static>> {
@ -34,9 +34,9 @@ async fn redirect_on_index() {
async fn can_login() {
let client = Client::new(rocket()).await.unwrap();
let mut response = client.get("/login").dispatch().await;
let body = response.body_string().await.unwrap();
let response = client.get("/login").dispatch().await;
assert_eq!(response.status(), Status::Ok);
let body = response.into_string().await.unwrap();
assert!(body.contains("Please login to continue."));
}
@ -53,9 +53,9 @@ async fn login_logout_succeeds() {
let login_cookie = login(&client, "Sergio", "password").await.expect("logged in");
// Ensure we're logged in.
let mut response = client.get("/").cookie(login_cookie.clone()).dispatch().await;
let body = response.body_string().await.unwrap();
let response = client.get("/").cookie(login_cookie.clone()).dispatch().await;
assert_eq!(response.status(), Status::Ok);
let body = response.into_string().await.unwrap();
assert!(body.contains("Logged in with user ID 1"));
// One more.
@ -73,9 +73,9 @@ async fn login_logout_succeeds() {
assert_eq!(response.headers().get_one("Location"), Some("/login"));
// The page should show the success message, and no errors.
let mut response = client.get("/login").dispatch().await;
let body = response.body_string().await.unwrap();
let response = client.get("/login").dispatch().await;
assert_eq!(response.status(), Status::Ok);
let body = response.into_string().await.unwrap();
assert!(body.contains("Successfully logged out."));
assert!(!body.contains("Error"));
}

View File

@ -1,4 +1,4 @@
use rocket::local::Client;
use rocket::local::asynchronous::Client;
use rocket::http::Status;
async fn register_hit(client: &Client) {
@ -7,8 +7,8 @@ async fn register_hit(client: &Client) {
}
async fn get_count(client: &Client) -> usize {
let mut response = client.get("/count").dispatch().await;
response.body_string().await.and_then(|s| s.parse().ok()).unwrap()
let response = client.get("/count").dispatch().await;
response.into_string().await.and_then(|s| s.parse().ok()).unwrap()
}
#[rocket::async_test]

View File

@ -1,7 +1,7 @@
use std::fs::File;
use std::io::Read;
use rocket::local::Client;
use rocket::local::asynchronous::Client;
use rocket::http::Status;
use super::rocket;
@ -10,10 +10,10 @@ async fn test_query_file<T> (path: &str, file: T, status: Status)
where T: Into<Option<&'static str>>
{
let client = Client::new(rocket()).await.unwrap();
let mut response = client.get(path).dispatch().await;
let response = client.get(path).dispatch().await;
assert_eq!(response.status(), status);
let body_data = response.body_bytes().await;
let body_data = response.into_bytes().await;
if let Some(filename) = file.into() {
let expected_data = read_file_content(filename);
assert!(body_data.map_or(false, |s| s == expected_data));

View File

@ -1,15 +1,15 @@
use std::fs::{self, File};
use std::io::prelude::*;
use rocket::local::Client;
use rocket::local::asynchronous::Client;
#[rocket::async_test]
async fn test_root() {
let client = Client::new(super::rocket()).await.unwrap();
let mut res = client.get("/").dispatch().await;
let res = client.get("/").dispatch().await;
// Check that we have exactly 25,000 'a'.
let res_str = res.body_string().await.unwrap();
let res_str = res.into_string().await.unwrap();
assert_eq!(res_str.len(), 25000);
for byte in res_str.as_bytes() {
assert_eq!(*byte, b'a');
@ -25,8 +25,8 @@ async fn test_file() {
// Get the big file contents, hopefully.
let client = Client::new(super::rocket()).await.unwrap();
let mut res = client.get("/big_file").dispatch().await;
assert_eq!(res.body_string().await, Some(CONTENTS.into()));
let res = client.get("/big_file").dispatch().await;
assert_eq!(res.into_string().await, Some(CONTENTS.into()));
// Delete the 'big_file'.
fs::remove_file(super::FILENAME).expect("remove big_file");

View File

@ -1,5 +1,5 @@
use super::rocket;
use rocket::local::Client;
use rocket::local::asynchronous::Client;
use rocket::http::Method::*;
use rocket::http::Status;
use rocket_contrib::templates::Template;
@ -7,7 +7,7 @@ use rocket_contrib::templates::Template;
macro_rules! dispatch {
($method:expr, $path:expr, |$client:ident, $response:ident| $body:expr) => ({
let $client = Client::new(rocket()).await.unwrap();
let mut $response = $client.req($method, $path).dispatch().await;
let $response = $client.req($method, $path).dispatch().await;
$body
})
}
@ -33,7 +33,7 @@ async fn test_root() {
let expected = Template::show(client.cargo(), "error/404", &map).unwrap();
assert_eq!(response.status(), Status::NotFound);
assert_eq!(response.body_string().await, Some(expected));
assert_eq!(response.into_string().await, Some(expected));
});
}
}
@ -49,7 +49,7 @@ async fn test_name() {
let expected = Template::show(client.cargo(), "index", &context).unwrap();
assert_eq!(response.status(), Status::Ok);
assert_eq!(response.body_string().await, Some(expected));
assert_eq!(response.into_string().await, Some(expected));
});
}
@ -62,6 +62,6 @@ async fn test_404() {
let expected = Template::show(client.cargo(), "error/404", &map).unwrap();
assert_eq!(response.status(), Status::NotFound);
assert_eq!(response.body_string().await, Some(expected));
assert_eq!(response.into_string().await, Some(expected));
});
}

View File

@ -15,14 +15,14 @@ fn rocket() -> rocket::Rocket {
#[cfg(test)]
mod test {
use super::rocket;
use rocket::local::Client;
use rocket::local::asynchronous::Client;
use rocket::http::Status;
#[rocket::async_test]
async fn test_hello() {
let client = Client::new(rocket()).await.unwrap();
let mut response = client.get("/").dispatch().await;
let response = client.get("/").dispatch().await;
assert_eq!(response.status(), Status::Ok);
assert_eq!(response.body_string().await, Some("Hello, world!".into()));
assert_eq!(response.into_string().await, Some("Hello, world!".into()));
}
}

View File

@ -1,8 +1,8 @@
use rocket::local::Client;
use rocket::local::asynchronous::Client;
#[rocket::async_test]
async fn hello_world() {
let client = Client::new(super::rocket()).await.unwrap();
let mut response = client.get("/").dispatch().await;
assert_eq!(response.body_string().await, Some("Hello, world!".into()));
let response = client.get("/").dispatch().await;
assert_eq!(response.into_string().await, Some("Hello, world!".into()));
}

View File

@ -3,7 +3,7 @@ use super::task::Task;
use parking_lot::Mutex;
use rand::{Rng, thread_rng, distributions::Alphanumeric};
use rocket::local::Client;
use rocket::local::asynchronous::Client;
use rocket::http::{Status, ContentType};
// We use a lock to synchronize between tests so DB operations don't collide.

View File

@ -1,11 +1,11 @@
use super::rocket;
use rocket::local::Client;
use rocket::local::asynchronous::Client;
use rocket::http::Status;
async fn test(uri: &str, expected: &str) {
let client = Client::new(rocket()).await.unwrap();
let mut res = client.get(uri).dispatch().await;
assert_eq!(res.body_string().await, Some(expected.into()));
let res = client.get(uri).dispatch().await;
assert_eq!(res.into_string().await, Some(expected.into()));
}
async fn test_404(uri: &str) {

View File

@ -148,7 +148,7 @@ First, we'll create a `test` module with the proper imports:
#[cfg(test)]
mod test {
use super::rocket;
use rocket::local::Client;
use rocket::local::asynchronous::Client;
use rocket::http::Status;
#[test]
@ -217,7 +217,7 @@ use rocket::http::{ContentType, Status};
# let mut response = client.get("/").dispatch();
assert_eq!(response.status(), Status::Ok);
assert_eq!(response.body_string().await, Some("Hello, world!".into()));
assert_eq!(response.into_string().await, Some("Hello, world!".into()));
```
That's it! Altogether, this looks like:
@ -240,7 +240,7 @@ fn rocket() -> rocket::Rocket {
# */
mod test {
use super::rocket;
use rocket::local::Client;
use rocket::local::asynchronous::Client;
use rocket::http::Status;
# /*
@ -250,7 +250,7 @@ mod test {
let client = Client::new(rocket()).expect("valid rocket instance");
let mut response = client.get("/").dispatch().await;
assert_eq!(response.status(), Status::Ok);
assert_eq!(response.body_string().await, Some("Hello, world!".into()));
assert_eq!(response.into_string().await, Some("Hello, world!".into()));
}
}