Keep an op-log for sync 'CookieJar'.

In brief, this commit:

  * Updates to the latest upstream 'cookie', fixing a memory leak.
  * Make changes to 'CookieJar' observable only through 'pending()'.
  * Deprecates 'Client::new()' in favor of 'Client::tracked()'.
  * Makes 'dispatch()' on tracked 'Client's synchronize on cookies.
  * Makes 'Client::untracked()' actually untracked.

This commit updates to the latest 'cookie' which removes support for
'Sync' cookie jars. Instead of relying on 'cookie', this commit
implements an op-log based 'CookieJar' which internally keeps track of
changes. The API is such that changes are only observable through
specialized '_pending()' methods.
This commit is contained in:
Sergio Benitez 2020-10-14 21:37:16 -07:00
parent 2f330d2967
commit 5d9035ddc1
93 changed files with 465 additions and 295 deletions

View File

@ -1130,7 +1130,5 @@ mod tests {
assert_eq!(pool_size, database_config.pool_size);
assert_eq!(true, database_config.extras.contains_key("certs"));
assert_eq!(true, database_config.extras.contains_key("key"));
println!("{:#?}", database_config);
}
}

View File

@ -330,7 +330,7 @@ impl Template {
///
/// fn main() {
/// let rocket = rocket::ignite().attach(Template::fairing());
/// let client = Client::new(rocket).expect("valid rocket");
/// let client = Client::untracked(rocket).expect("valid rocket");
///
/// // Create a `context`. Here, just an empty `HashMap`.
/// let mut context = HashMap::new();

View File

@ -66,7 +66,7 @@ mod compress_responder_tests {
#[test]
fn test_prioritizes_brotli() {
let client = Client::new(rocket()).expect("valid rocket instance");
let client = Client::tracked(rocket()).expect("valid rocket instance");
let mut response = client
.get("/")
.header(Header::new("Accept-Encoding", "deflate, gzip, br"))
@ -90,7 +90,7 @@ mod compress_responder_tests {
#[test]
fn test_br_font() {
let client = Client::new(rocket()).expect("valid rocket instance");
let client = Client::tracked(rocket()).expect("valid rocket instance");
let mut response = client
.get("/font")
.header(Header::new("Accept-Encoding", "deflate, gzip, br"))
@ -114,7 +114,7 @@ mod compress_responder_tests {
#[test]
fn test_fallback_gzip() {
let client = Client::new(rocket()).expect("valid rocket instance");
let client = Client::tracked(rocket()).expect("valid rocket instance");
let mut response = client
.get("/")
.header(Header::new("Accept-Encoding", "deflate, gzip"))
@ -137,7 +137,7 @@ mod compress_responder_tests {
#[test]
fn test_does_not_recompress() {
let client = Client::new(rocket()).expect("valid rocket instance");
let client = Client::tracked(rocket()).expect("valid rocket instance");
let mut response = client
.get("/already_encoded")
.header(Header::new("Accept-Encoding", "deflate, gzip, br"))
@ -160,7 +160,7 @@ mod compress_responder_tests {
#[test]
fn test_does_not_compress_explicit_identity() {
let client = Client::new(rocket()).expect("valid rocket instance");
let client = Client::tracked(rocket()).expect("valid rocket instance");
let mut response = client
.get("/identity")
.header(Header::new("Accept-Encoding", "deflate, gzip, br"))
@ -178,7 +178,7 @@ mod compress_responder_tests {
#[test]
fn test_ignore_exceptions() {
let client = Client::new(rocket()).expect("valid rocket instance");
let client = Client::tracked(rocket()).expect("valid rocket instance");
let mut response = client
.get("/image")
.header(Header::new("Accept-Encoding", "deflate, gzip, br"))
@ -202,7 +202,7 @@ mod compress_responder_tests {
#[test]
fn test_ignores_unimplemented_encodings() {
let client = Client::new(rocket()).expect("valid rocket instance");
let client = Client::tracked(rocket()).expect("valid rocket instance");
let mut response = client
.get("/")
.header(Header::new("Accept-Encoding", "deflate"))
@ -220,7 +220,7 @@ mod compress_responder_tests {
#[test]
fn test_respects_identity_only() {
let client = Client::new(rocket()).expect("valid rocket instance");
let client = Client::tracked(rocket()).expect("valid rocket instance");
let mut response = client
.get("/")
.header(Header::new("Accept-Encoding", "identity"))

View File

@ -85,7 +85,7 @@ mod compression_fairing_tests {
#[test]
fn test_prioritizes_brotli() {
let client = Client::new(rocket()).expect("valid rocket instance");
let client = Client::tracked(rocket()).expect("valid rocket instance");
let mut response = client
.get("/")
.header(Header::new("Accept-Encoding", "deflate, gzip, br"))
@ -109,7 +109,7 @@ mod compression_fairing_tests {
#[test]
fn test_br_font() {
let client = Client::new(rocket()).expect("valid rocket instance");
let client = Client::tracked(rocket()).expect("valid rocket instance");
let mut response = client
.get("/font")
.header(Header::new("Accept-Encoding", "deflate, gzip, br"))
@ -133,7 +133,7 @@ mod compression_fairing_tests {
#[test]
fn test_fallback_gzip() {
let client = Client::new(rocket()).expect("valid rocket instance");
let client = Client::tracked(rocket()).expect("valid rocket instance");
let mut response = client
.get("/")
.header(Header::new("Accept-Encoding", "deflate, gzip"))
@ -156,7 +156,7 @@ mod compression_fairing_tests {
#[test]
fn test_does_not_recompress() {
let client = Client::new(rocket()).expect("valid rocket instance");
let client = Client::tracked(rocket()).expect("valid rocket instance");
let mut response = client
.get("/already_encoded")
.header(Header::new("Accept-Encoding", "deflate, gzip, br"))
@ -179,7 +179,7 @@ mod compression_fairing_tests {
#[test]
fn test_does_not_compress_explicit_identity() {
let client = Client::new(rocket()).expect("valid rocket instance");
let client = Client::tracked(rocket()).expect("valid rocket instance");
let mut response = client
.get("/identity")
.header(Header::new("Accept-Encoding", "deflate, gzip, br"))
@ -197,7 +197,7 @@ mod compression_fairing_tests {
#[test]
fn test_does_not_compress_image() {
let client = Client::new(rocket()).expect("valid rocket instance");
let client = Client::tracked(rocket()).expect("valid rocket instance");
let mut response = client
.get("/image")
.header(Header::new("Accept-Encoding", "deflate, gzip, br"))
@ -215,7 +215,7 @@ mod compression_fairing_tests {
#[test]
fn test_ignores_unimplemented_encodings() {
let client = Client::new(rocket()).expect("valid rocket instance");
let client = Client::tracked(rocket()).expect("valid rocket instance");
let mut response = client
.get("/")
.header(Header::new("Accept-Encoding", "deflate"))
@ -233,7 +233,7 @@ mod compression_fairing_tests {
#[test]
fn test_respects_identity_only() {
let client = Client::new(rocket()).expect("valid rocket instance");
let client = Client::tracked(rocket()).expect("valid rocket instance");
let mut response = client
.get("/")
.header(Header::new("Accept-Encoding", "identity"))
@ -251,7 +251,7 @@ mod compression_fairing_tests {
#[test]
fn test_does_not_compress_custom_exception() {
let client = Client::new(rocket_tar_exception()).expect("valid rocket instance");
let client = Client::tracked(rocket_tar_exception()).expect("valid rocket instance");
let mut response = client
.get("/tar")
.header(Header::new("Accept-Encoding", "deflate, gzip, br"))
@ -269,7 +269,7 @@ mod compression_fairing_tests {
#[test]
fn test_compress_custom_removed_exception() {
let client = Client::new(rocket_tar_exception()).expect("valid rocket instance");
let client = Client::tracked(rocket_tar_exception()).expect("valid rocket instance");
let mut response = client
.get("/image")
.header(Header::new("Accept-Encoding", "deflate, gzip, br"))

View File

@ -32,7 +32,7 @@ mod helmet_tests {
macro_rules! dispatch {
($helmet:expr, $closure:expr) => {{
let rocket = rocket::ignite().mount("/", routes![hello]).attach($helmet);
let client = Client::new(rocket).unwrap();
let client = Client::tracked(rocket).unwrap();
let response = client.get("/").dispatch();
assert_eq!(response.status(), Status::Ok);
$closure(response)

View File

@ -69,7 +69,7 @@ mod static_tests {
#[test]
fn test_static_no_index() {
let client = Client::new(rocket()).expect("valid rocket");
let client = Client::tracked(rocket()).expect("valid rocket");
assert_all(&client, "no_index", REGULAR_FILES, true);
assert_all(&client, "no_index", HIDDEN_FILES, false);
assert_all(&client, "no_index", INDEXED_DIRECTORIES, false);
@ -77,7 +77,7 @@ mod static_tests {
#[test]
fn test_static_hidden() {
let client = Client::new(rocket()).expect("valid rocket");
let client = Client::tracked(rocket()).expect("valid rocket");
assert_all(&client, "dots", REGULAR_FILES, true);
assert_all(&client, "dots", HIDDEN_FILES, true);
assert_all(&client, "dots", INDEXED_DIRECTORIES, false);
@ -85,7 +85,7 @@ mod static_tests {
#[test]
fn test_static_index() {
let client = Client::new(rocket()).expect("valid rocket");
let client = Client::tracked(rocket()).expect("valid rocket");
assert_all(&client, "index", REGULAR_FILES, true);
assert_all(&client, "index", HIDDEN_FILES, false);
assert_all(&client, "index", INDEXED_DIRECTORIES, true);
@ -97,7 +97,7 @@ mod static_tests {
#[test]
fn test_static_all() {
let client = Client::new(rocket()).expect("valid rocket");
let client = Client::tracked(rocket()).expect("valid rocket");
assert_all(&client, "both", REGULAR_FILES, true);
assert_all(&client, "both", HIDDEN_FILES, true);
assert_all(&client, "both", INDEXED_DIRECTORIES, true);
@ -129,7 +129,7 @@ mod static_tests {
fn catch_two(a: &RawStr, b: &RawStr) -> String { format!("{}/{}", a, b) }
let rocket = rocket().mount("/default", routes![catch_one, catch_two]);
let client = Client::new(rocket).expect("valid rocket");
let client = Client::tracked(rocket).expect("valid rocket");
let response = client.get("/default/ireallydontexist").dispatch();
assert_eq!(response.status(), Status::Ok);
@ -146,7 +146,7 @@ mod static_tests {
#[test]
fn test_redirection() {
let client = Client::new(rocket()).expect("valid rocket");
let client = Client::tracked(rocket()).expect("valid rocket");
// Redirection only happens if enabled, and doesn't affect index behaviour.
let response = client.get("/no_index/inner").dispatch();

View File

@ -66,7 +66,7 @@ mod templates_tests {
#[test]
fn test_template_metadata_with_tera() {
let client = Client::new(rocket()).unwrap();
let client = Client::tracked(rocket()).unwrap();
let response = client.get("/tera/txt_test").dispatch();
assert_eq!(response.status(), Status::Ok);
@ -107,7 +107,7 @@ mod templates_tests {
#[test]
fn test_template_metadata_with_handlebars() {
let client = Client::new(rocket()).unwrap();
let client = Client::tracked(rocket()).unwrap();
let response = client.get("/hbs/test").dispatch();
assert_eq!(response.status(), Status::Ok);
@ -144,7 +144,7 @@ mod templates_tests {
write_file(&reload_path, INITIAL_TEXT);
// set up the client. if we can't reload templates, then just quit
let client = Client::new(rocket()).unwrap();
let client = Client::tracked(rocket()).unwrap();
let res = client.get("/is_reloading").dispatch();
if res.status() != Status::Ok {
return;

View File

@ -34,7 +34,7 @@ foo!("/hello/<name>", name);
#[test]
fn test_reexpansion() {
let rocket = rocket::ignite().mount("/", routes![easy, hard, hi]);
let client = Client::new(rocket).unwrap();
let client = Client::tracked(rocket).unwrap();
let response = client.get("/easy/327").dispatch();
assert_eq!(response.into_string().unwrap(), "easy id: 327");
@ -60,7 +60,7 @@ index!(i32);
#[test]
fn test_index() {
let rocket = rocket::ignite().mount("/", routes![index]).manage(100i32);
let client = Client::new(rocket).unwrap();
let client = Client::tracked(rocket).unwrap();
let response = client.get("/").dispatch();
assert_eq!(response.into_string().unwrap(), "Thing: 100");

View File

@ -21,7 +21,7 @@ pub enum Foo<'r> {
#[rocket::async_test]
async fn responder_foo() {
let client = Client::new(rocket::ignite()).await.expect("valid rocket");
let client = Client::tracked(rocket::ignite()).await.expect("valid rocket");
let local_req = client.get("/");
let req = local_req.inner();
@ -70,7 +70,7 @@ pub struct Bar<'r> {
#[rocket::async_test]
async fn responder_bar() {
let client = Client::new(rocket::ignite()).await.expect("valid rocket");
let client = Client::tracked(rocket::ignite()).await.expect("valid rocket");
let local_req = client.get("/");
let req = local_req.inner();
@ -95,7 +95,7 @@ pub struct Baz {
#[rocket::async_test]
async fn responder_baz() {
let client = Client::new(rocket::ignite()).await.expect("valid rocket");
let client = Client::tracked(rocket::ignite()).await.expect("valid rocket");
let local_req = client.get("/");
let req = local_req.inner();

View File

@ -36,7 +36,7 @@ fn simple(simple: Simple) -> String { simple.0 }
#[test]
fn test_data() {
let rocket = rocket::ignite().mount("/", routes![form, simple]);
let client = Client::new(rocket).unwrap();
let client = Client::tracked(rocket).unwrap();
let response = client.post("/f")
.header(ContentType::Form)

View File

@ -37,7 +37,7 @@ fn test_formats() {
.mount("/", routes![json, xml, json_long, msgpack_long, msgpack,
plain, binary, other]);
let client = Client::new(rocket).unwrap();
let client = Client::tracked(rocket).unwrap();
let response = client.post("/").header(ContentType::JSON).dispatch();
assert_eq!(response.into_string().unwrap(), "json");
@ -83,7 +83,7 @@ fn test_custom_formats() {
let rocket = rocket::ignite()
.mount("/", routes![get_foo, post_foo, get_bar_baz, put_bar_baz]);
let client = Client::new(rocket).unwrap();
let client = Client::tracked(rocket).unwrap();
let foo_a = Accept::new(&[MediaType::new("application", "foo").into()]);
let foo_ct = ContentType::new("application", "foo");

View File

@ -19,7 +19,7 @@ fn get3(_number: u64) -> &'static str { "3" }
#[test]
fn test_ranking() {
let rocket = rocket::ignite().mount("/", routes![get0, get1, get2, get3]);
let client = Client::new(rocket).unwrap();
let client = Client::tracked(rocket).unwrap();
let response = client.get("/0").dispatch();
assert_eq!(response.into_string().unwrap(), "0");
@ -44,7 +44,7 @@ fn test_rank_collision() {
use rocket::error::LaunchErrorKind;
let rocket = rocket::ignite().mount("/", routes![get0, get0b]);
let client_result = Client::new(rocket);
let client_result = Client::tracked(rocket);
match client_result.as_ref().map_err(|e| e.kind()) {
Err(LaunchErrorKind::Collision(..)) => { /* o.k. */ },
Ok(_) => panic!("client succeeded unexpectedly"),

View File

@ -77,7 +77,7 @@ fn test_full_route() {
.mount("/1", routes![post1])
.mount("/2", routes![post2]);
let client = Client::new(rocket).unwrap();
let client = Client::tracked(rocket).unwrap();
let a = "A%20A";
let name = "Bob%20McDonald";

View File

@ -14,9 +14,7 @@ error[E0277]: the trait bound `Header<'_>: From<u8>` is not satisfied
|
= help: the following implementations were found:
<Header<'static> as From<&Cookie<'_>>>
<Header<'static> as From<&CookieCrumb>>
<Header<'static> as From<Cookie<'_>>>
<Header<'static> as From<CookieCrumb>>
= note: required because of the requirements on the impl of `Into<Header<'_>>` for `u8`
error[E0277]: the trait bound `u8: Responder<'_, '_>` is not satisfied
@ -35,9 +33,7 @@ error[E0277]: the trait bound `Header<'_>: From<u8>` is not satisfied
|
= help: the following implementations were found:
<Header<'static> as From<&Cookie<'_>>>
<Header<'static> as From<&CookieCrumb>>
<Header<'static> as From<Cookie<'_>>>
<Header<'static> as From<CookieCrumb>>
= note: required because of the requirements on the impl of `Into<Header<'_>>` for `u8`
error[E0277]: the trait bound `Header<'_>: From<std::string::String>` is not satisfied
@ -48,9 +44,7 @@ error[E0277]: the trait bound `Header<'_>: From<std::string::String>` is not sat
|
= help: the following implementations were found:
<Header<'static> as From<&Cookie<'_>>>
<Header<'static> as From<&CookieCrumb>>
<Header<'static> as From<Cookie<'_>>>
<Header<'static> as From<CookieCrumb>>
= note: required because of the requirements on the impl of `Into<Header<'_>>` for `std::string::String`
error[E0277]: the trait bound `usize: Responder<'_, '_>` is not satisfied

View File

@ -14,9 +14,7 @@ error[E0277]: the trait bound `rocket::http::Header<'_>: std::convert::From<u8>`
|
= help: the following implementations were found:
<rocket::http::Header<'static> as std::convert::From<&rocket::http::Cookie<'_>>>
<rocket::http::Header<'static> as std::convert::From<&rocket::http::CookieCrumb>>
<rocket::http::Header<'static> as std::convert::From<rocket::http::Cookie<'_>>>
<rocket::http::Header<'static> as std::convert::From<rocket::http::CookieCrumb>>
= note: required because of the requirements on the impl of `std::convert::Into<rocket::http::Header<'_>>` for `u8`
error[E0277]: the trait bound `u8: rocket::response::Responder<'_, '_>` is not satisfied
@ -35,9 +33,7 @@ error[E0277]: the trait bound `rocket::http::Header<'_>: std::convert::From<u8>`
|
= help: the following implementations were found:
<rocket::http::Header<'static> as std::convert::From<&rocket::http::Cookie<'_>>>
<rocket::http::Header<'static> as std::convert::From<&rocket::http::CookieCrumb>>
<rocket::http::Header<'static> as std::convert::From<rocket::http::Cookie<'_>>>
<rocket::http::Header<'static> as std::convert::From<rocket::http::CookieCrumb>>
= note: required because of the requirements on the impl of `std::convert::Into<rocket::http::Header<'_>>` for `u8`
error[E0277]: the trait bound `rocket::http::Header<'_>: std::convert::From<std::string::String>` is not satisfied
@ -48,9 +44,7 @@ error[E0277]: the trait bound `rocket::http::Header<'_>: std::convert::From<std:
|
= help: the following implementations were found:
<rocket::http::Header<'static> as std::convert::From<&rocket::http::Cookie<'_>>>
<rocket::http::Header<'static> as std::convert::From<&rocket::http::CookieCrumb>>
<rocket::http::Header<'static> as std::convert::From<rocket::http::Cookie<'_>>>
<rocket::http::Header<'static> as std::convert::From<rocket::http::CookieCrumb>>
= note: required because of the requirements on the impl of `std::convert::Into<rocket::http::Header<'_>>` for `std::string::String`
error[E0277]: the trait bound `usize: rocket::response::Responder<'_, '_>` is not satisfied

View File

@ -34,10 +34,11 @@ unicode-xid = "0.2"
log = "0.4"
ref-cast = "1.0"
uncased = "0.9"
parking_lot = "0.11"
[dependencies.cookie]
git = "https://github.com/SergioBenitez/cookie-rs.git"
rev = "3795f2e"
rev = "9675944"
features = ["percent-encode"]
[dependencies.pear]

View File

@ -1,8 +1,10 @@
use std::fmt;
use parking_lot::Mutex;
use crate::Header;
pub use cookie::{Cookie, CookieCrumb, SameSite, Iter};
pub use cookie::{Cookie, SameSite, Iter};
#[doc(hidden)] pub use self::key::*;
/// Types and methods to manage a `Key` when private cookies are enabled.
@ -26,21 +28,68 @@ mod key {
/// Collection of one or more HTTP cookies.
///
/// The `CookieJar` type allows for retrieval of cookies from an incoming
/// request as well as modifications to cookies to be reflected by Rocket on
/// outgoing responses.
/// `CookieJar` allows for retrieval of cookies from an incoming request. It
/// also tracks modifications (additions and removals) and marks them as
/// pending.
///
/// # Pending
///
/// Changes to a `CookieJar` are _not_ visible via the normal [`get()`] and
/// [`get_private()`] methods. This is typically the desired effect as a
/// `CookieJar` always reflects the cookies in an incoming request. In cases
/// where this is not desired, the [`get_pending()`] and
/// [`get_private_pending()`] methods are available, which always return the
/// latest changes.
///
/// ```rust
/// # #[macro_use] extern crate rocket;
/// use rocket::http::{CookieJar, Cookie};
///
/// #[get("/message")]
/// fn message(jar: &CookieJar<'_>) {
/// jar.add(Cookie::new("message", "hello!"));
/// jar.add(Cookie::new("other", "bye!"));
///
/// // `get()` does not reflect changes.
/// assert!(jar.get("other").is_none());
/// # assert_eq!(jar.get("message").map(|c| c.value()), Some("hi"));
///
/// // `get_pending()` does.
/// let other_pending = jar.get_pending("other");
/// let message_pending = jar.get_pending("message");
/// assert_eq!(other_pending.as_ref().map(|c| c.value()), Some("bye!"));
/// assert_eq!(message_pending.as_ref().map(|c| c.value()), Some("hello!"));
/// # jar.remove(Cookie::named("message"));
/// # assert_eq!(jar.get("message").map(|c| c.value()), Some("hi"));
/// # assert!(jar.get_pending("message").is_none());
/// }
/// # fn main() {
/// # use rocket::local::blocking::Client;
/// # let rocket = rocket::ignite().mount("/", routes![message]);
/// # let client = Client::tracked(rocket).unwrap();
/// # let response = client.get("/message")
/// # .cookie(Cookie::new("message", "hi"))
/// # .dispatch();
/// #
/// # assert!(response.status().class().is_success());
/// # }
/// ```
///
/// # Usage
///
/// A type of `&CookieJar` can be retrieved via its `FromRequest` implementation
/// as a request guard or via the [`Request::cookies()`] method. Individual
/// cookies can be retrieved via the [`get()`] and [`get_private()`] methods.
/// Cookies can be added or removed via the [`add()`], [`add_private()`],
/// [`remove()`], and [`remove_private()`] methods.
/// Pending changes can be observed via the [`get_pending()`] and
/// [`get_private_pending()`] methods. Cookies can be added or removed via the
/// [`add()`], [`add_private()`], [`remove()`], and [`remove_private()`]
/// methods.
///
/// [`Request::cookies()`]: rocket::Request::cookies()
/// [`get()`]: #method.get
/// [`get_private()`]: #method.get_private
/// [`get_pending()`]: #method.get_pending
/// [`get_private_pending()`]: #method.get_private_pending
/// [`add()`]: #method.add
/// [`add_private()`]: #method.add_private
/// [`remove()`]: #method.remove
@ -56,8 +105,8 @@ mod key {
/// use rocket::http::CookieJar;
///
/// #[get("/message")]
/// fn message(jar: &CookieJar<'_>) -> Option<String> {
/// jar.get("message").map(|c| format!("Message: {}", c.value()))
/// fn message<'a>(jar: &'a CookieJar<'_>) -> Option<&'a str> {
/// jar.get("message").map(|cookie| cookie.value())
/// }
/// # fn main() { }
/// ```
@ -123,15 +172,43 @@ mod key {
/// is usually done through tools like `openssl`. Using `openssl`, for instance,
/// a 256-bit base64 key can be generated with the command `openssl rand -base64
/// 32`.
#[derive(Clone)]
pub struct CookieJar<'a> {
jar: cookie::CookieJar,
key: &'a Key,
ops: Mutex<Vec<Op>>,
}
impl<'a> Clone for CookieJar<'a> {
fn clone(&self) -> Self {
CookieJar {
jar: self.jar.clone(),
key: self.key,
ops: Mutex::new(self.ops.lock().clone()),
}
}
}
#[derive(Clone)]
enum Op {
Add(Cookie<'static>, bool),
Remove(Cookie<'static>, bool),
}
impl Op {
fn cookie(&self) -> &Cookie<'static> {
match self {
Op::Add(c, _) | Op::Remove(c, _) => c
}
}
}
impl<'a> CookieJar<'a> {
/// Returns a reference to the `Cookie` inside this container with the name
/// `name`. If no such cookie exists, returns `None`.
/// Returns a reference to the _original_ `Cookie` inside this container
/// with the name `name`. If no such cookie exists, returns `None`.
///
/// **Note:** This method _does not_ obverse changes made via additions and
/// removals to the cookie jar. To observe those changes, use
/// [`CookieJar::get_pending()`].
///
/// # Example
///
@ -144,14 +221,18 @@ impl<'a> CookieJar<'a> {
/// let cookie = jar.get("name");
/// }
/// ```
pub fn get(&self, name: &str) -> Option<CookieCrumb> {
pub fn get(&self, name: &str) -> Option<&Cookie<'static>> {
self.jar.get(name)
}
/// Returns a reference to the `Cookie` inside this collection with the name
/// `name` and authenticates and decrypts the cookie's value, returning a
/// `Cookie` with the decrypted value. If the cookie cannot be found, or the
/// cookie fails to authenticate or decrypt, `None` is returned.
/// Retrives the _original_ `Cookie` inside this collection with the name
/// `name` and authenticates and decrypts the cookie's value. If the cookie
/// cannot be found, or the cookie fails to authenticate or decrypt, `None`
/// is returned.
///
/// **Note:** This method _does not_ obverse changes made via additions and
/// removals to the cookie jar. To observe those changes, use
/// [`CookieJar::get_private_pending()`].
///
/// # Example
///
@ -170,6 +251,55 @@ impl<'a> CookieJar<'a> {
self.jar.private(&*self.key).get(name)
}
/// Returns a reference to the _original or pending_ `Cookie` inside this
/// container with the name `name`. If no such cookie exists, returns
/// `None`.
///
/// # Example
///
/// ```rust
/// # #[macro_use] extern crate rocket;
/// use rocket::http::{Cookie, CookieJar};
///
/// #[get("/")]
/// fn handler(jar: &CookieJar<'_>) {
/// let pending_cookie = jar.get_pending("name");
/// }
/// ```
pub fn get_pending(&self, name: &str) -> Option<Cookie<'static>> {
let ops = self.ops.lock();
for op in ops.iter().rev().filter(|op| op.cookie().name() == name) {
match op {
Op::Add(c, _) => return Some(c.clone()),
Op::Remove(_, _) => return None,
}
}
drop(ops);
self.get(name).map(|c| c.clone())
}
/// Retrives the _original or pending_ `Cookie` inside this collection with
/// the name `name` and authenticates and decrypts the cookie's value. If
/// the cookie cannot be found, or the cookie fails to authenticate or
/// decrypt, `None` is returned.
///
/// # Example
///
/// ```rust
/// # #[macro_use] extern crate rocket;
/// use rocket::http::{Cookie, CookieJar};
///
/// #[get("/")]
/// fn handler(jar: &CookieJar<'_>) {
/// let pending_cookie = jar.get_private_pending("name");
/// }
/// ```
pub fn get_private_pending(&self, name: &str) -> Option<Cookie<'static>> {
let cookie = self.get_pending(name)?;
self.jar.private(&*self.key).decrypt(cookie)
}
/// Adds `cookie` to this collection.
///
/// Unless a value is set for the given property, the following defaults are
@ -198,7 +328,7 @@ impl<'a> CookieJar<'a> {
/// ```
pub fn add(&self, mut cookie: Cookie<'static>) {
Self::set_defaults(&mut cookie);
self.jar.add(cookie)
self.ops.lock().push(Op::Add(cookie, false));
}
/// Adds `cookie` to the collection. The cookie's value is encrypted with
@ -233,7 +363,7 @@ impl<'a> CookieJar<'a> {
#[cfg_attr(nightly, doc(cfg(feature = "secrets")))]
pub fn add_private(&self, mut cookie: Cookie<'static>) {
Self::set_private_defaults(&mut cookie);
self.jar.private(&*self.key).add(cookie)
self.ops.lock().push(Op::Add(cookie, true));
}
/// Removes `cookie` from this collection and generates a "removal" cookies
@ -263,7 +393,7 @@ impl<'a> CookieJar<'a> {
cookie.set_path("/");
}
self.jar.remove(cookie)
self.ops.lock().push(Op::Remove(cookie, false));
}
/// Removes the private `cookie` from the collection.
@ -290,10 +420,14 @@ impl<'a> CookieJar<'a> {
cookie.set_path("/");
}
self.jar.private(&*self.key).remove(cookie)
self.ops.lock().push(Op::Remove(cookie, true));
}
/// Returns an iterator over all of the cookies present in this collection.
/// Returns an iterator over all of the _original_ cookies present in this
/// collection.
///
/// **Note:** This method _does not_ obverse changes made via additions and
/// removals to the cookie jar.
///
/// # Example
///
@ -308,38 +442,64 @@ impl<'a> CookieJar<'a> {
/// }
/// }
/// ```
pub fn iter(&self) -> impl Iterator<Item = CookieCrumb> + '_ {
pub fn iter(&self) -> impl Iterator<Item=&Cookie<'static>> {
self.jar.iter()
}
}
/// WARNING: These is unstable! Do not use outside of Rocket!
/// WARNING: These are unstable! Do not use outside of Rocket!
#[doc(hidden)]
impl<'a> CookieJar<'a> {
#[inline(always)]
pub fn new(key: &'a Key) -> CookieJar<'a> {
CookieJar { jar: cookie::CookieJar::new(), key }
pub fn new(key: &'a Key) -> Self {
CookieJar {
jar: cookie::CookieJar::new(),
key, ops: Mutex::new(Vec::new()),
}
}
#[inline(always)]
pub fn from(jar: cookie::CookieJar, key: &'a Key) -> CookieJar<'a> {
CookieJar { jar, key }
CookieJar { jar, key, ops: Mutex::new(Vec::new()) }
}
/// Removes all delta cookies.
#[inline(always)]
pub fn reset_delta(&self) {
self.jar.reset_delta()
self.ops.lock().clear();
}
/// TODO: This could be faster by just returning the cookies directly via
/// an ordered hash-set of sorts.
#[inline(always)]
pub fn delta(&self) -> cookie::Delta {
self.jar.delta()
pub fn take_delta_jar(&self) -> cookie::CookieJar {
let ops = std::mem::replace(&mut *self.ops.lock(), Vec::new());
let mut jar = cookie::CookieJar::new();
for op in ops {
match op {
Op::Add(c, false) => jar.add(c),
#[cfg(feature = "private-cookies")]
Op::Add(c, true) => jar.private_mut(self.key).add(c),
Op::Remove(mut c, _) => {
if self.jar.get(c.name()).is_some() {
c.make_removal();
jar.add(c);
} else {
jar.remove(c);
}
}
#[allow(unreachable_patterns)]
_ => unreachable!()
}
}
jar
}
/// Adds an original `cookie` to this collection.
#[inline(always)]
pub fn add_original(&self, cookie: Cookie<'static>) {
pub fn add_original(&mut self, cookie: Cookie<'static>) {
self.jar.add_original(cookie)
}
@ -347,8 +507,8 @@ impl<'a> CookieJar<'a> {
#[inline(always)]
#[cfg(feature = "private-cookies")]
#[cfg_attr(nightly, doc(cfg(feature = "secrets")))]
pub fn add_original_private(&self, cookie: Cookie<'static>) {
self.jar.private(&*self.key).add_original(cookie);
pub fn add_original_private(&mut self, cookie: Cookie<'static>) {
self.jar.private_mut(&*self.key).add_original(cookie);
}
/// For each property mentioned below, this method checks if there is a
@ -400,7 +560,16 @@ impl<'a> CookieJar<'a> {
impl fmt::Debug for CookieJar<'_> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
self.jar.fmt(f)
let pending: Vec<_> = self.ops.lock()
.iter()
.map(|c| c.cookie())
.cloned()
.collect();
f.debug_struct("CookieJar")
.field("original", &self.jar)
.field("pending", &pending)
.finish()
}
}
@ -415,15 +584,3 @@ impl From<&Cookie<'_>> for Header<'static> {
Header::new("Set-Cookie", cookie.encoded().to_string())
}
}
impl From<CookieCrumb> for Header<'static> {
fn from(cookie: CookieCrumb) -> Header<'static> {
Header::new("Set-Cookie", cookie.encoded().to_string())
}
}
impl From<&CookieCrumb> for Header<'static> {
fn from(cookie: &CookieCrumb) -> Header<'static> {
Header::new("Set-Cookie", cookie.encoded().to_string())
}
}

View File

@ -74,4 +74,4 @@ pub use crate::status::{Status, StatusClass};
pub use crate::header::{Header, HeaderMap};
pub use crate::raw_str::RawStr;
pub use crate::media_type::MediaType;
pub use crate::cookies::{Cookie, CookieJar, CookieCrumb, SameSite};
pub use crate::cookies::{Cookie, CookieJar, SameSite};

View File

@ -40,11 +40,7 @@ async-trait = "0.1"
ref-cast = "1.0"
atomic = "0.5"
ubyte = "0.10"
[dependencies.cookie]
git = "https://github.com/SergioBenitez/cookie-rs.git"
rev = "3795f2e"
features = ["percent-encode"]
parking_lot = "0.11"
[dependencies.pear]
git = "https://github.com/SergioBenitez/Pear.git"

View File

@ -19,25 +19,25 @@ use bencher::Bencher;
use rocket::http::{Accept, ContentType};
fn accept_format(b: &mut Bencher) {
let client = Client::new(rocket()).unwrap();
let client = Client::tracked(rocket()).unwrap();
let request = client.get("/").header(Accept::JSON);
b.iter(|| { request.clone().dispatch(); });
}
fn wrong_accept_format(b: &mut Bencher) {
let client = Client::new(rocket()).unwrap();
let client = Client::tracked(rocket()).unwrap();
let request = client.get("/").header(Accept::HTML);
b.iter(|| { request.clone().dispatch(); });
}
fn content_type_format(b: &mut Bencher) {
let client = Client::new(rocket()).unwrap();
let client = Client::tracked(rocket()).unwrap();
let request = client.post("/").header(ContentType::JSON);
b.iter(|| { request.clone().dispatch(); });
}
fn wrong_content_type_format(b: &mut Bencher) {
let client = Client::new(rocket()).unwrap();
let client = Client::tracked(rocket()).unwrap();
let request = client.post("/").header(ContentType::Plain);
b.iter(|| { request.clone().dispatch(); });
}

View File

@ -33,7 +33,7 @@ use rocket::local::blocking::Client;
use rocket::http::{Accept, ContentType};
fn accept_format(b: &mut Bencher) {
let client = Client::new(rocket()).unwrap();
let client = Client::tracked(rocket()).unwrap();
let requests = vec![
client.get("/").header(Accept::JSON),
client.get("/").header(Accept::HTML),
@ -48,7 +48,7 @@ fn accept_format(b: &mut Bencher) {
}
fn content_type_format(b: &mut Bencher) {
let client = Client::new(rocket()).unwrap();
let client = Client::tracked(rocket()).unwrap();
let requests = vec![
client.post("/").header(ContentType::JSON),
client.post("/").header(ContentType::HTML),

View File

@ -44,7 +44,7 @@ use bencher::Bencher;
use rocket::local::blocking::Client;
fn bench_hello_world(b: &mut Bencher) {
let client = Client::new(hello_world_rocket()).unwrap();
let client = Client::tracked(hello_world_rocket()).unwrap();
b.iter(|| {
client.get("/").dispatch();
@ -52,7 +52,7 @@ fn bench_hello_world(b: &mut Bencher) {
}
fn bench_single_get_index(b: &mut Bencher) {
let client = Client::new(rocket()).unwrap();
let client = Client::tracked(rocket()).unwrap();
b.iter(|| {
client.get("/").dispatch();
@ -60,7 +60,7 @@ fn bench_single_get_index(b: &mut Bencher) {
}
fn bench_get_put_post_index(b: &mut Bencher) {
let client = Client::new(rocket()).unwrap();
let client = Client::tracked(rocket()).unwrap();
// Hold all of the requests we're going to make during the benchmark.
let mut requests = vec![];
@ -76,7 +76,7 @@ fn bench_get_put_post_index(b: &mut Bencher) {
}
fn bench_dynamic(b: &mut Bencher) {
let client = Client::new(rocket()).unwrap();
let client = Client::tracked(rocket()).unwrap();
// Hold all of the requests we're going to make during the benchmark.
let mut requests = vec![];
@ -92,7 +92,7 @@ fn bench_dynamic(b: &mut Bencher) {
}
fn bench_simple_routing(b: &mut Bencher) {
let client = Client::new(rocket()).unwrap();
let client = Client::tracked(rocket()).unwrap();
// Hold all of the requests we're going to make during the benchmark.
let mut requests = vec![];

View File

@ -1,5 +1,7 @@
use std::borrow::Cow;
use parking_lot::RwLock;
use crate::local::asynchronous::{LocalRequest, LocalResponse};
use crate::rocket::{Rocket, Cargo};
use crate::http::{private::cookie, Method};
@ -11,7 +13,7 @@ use crate::error::LaunchError;
/// For the `blocking` version, see
/// [`blocking::Client`](crate::local::blocking::Client).
///
/// ## Multithreaded Synchronization Pitfalls
/// ## Multithreaded Tracking Synchronization Pitfalls
///
/// Unlike its [`blocking`](crate::local::blocking) variant, this `async`
/// `Client` implements `Sync`. However, using it in a multithreaded environment
@ -19,11 +21,14 @@ use crate::error::LaunchError;
/// This is because while cookie modifications are serialized, the ordering
/// depends on the ordering of request dispatch.
///
/// 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 this is not possible, ensure that you are not depending on
/// the ordering of cookie modifications or have arranged for request dispatch
/// to occur in a deterministic manner.
/// If possible, refrain from sharing a single instance of a tracking `Client`
/// across multiple threads. Instead, prefer to create a unique instance of
/// `Client` per thread. If this is not possible, ensure that you are not
/// depending on the ordering of cookie modifications or have arranged for
/// request dispatch to occur in a deterministic manner.
///
/// Alternatively, use an untracked client, which does not suffer from these
/// pitfalls.
///
/// ## Example
///
@ -35,7 +40,7 @@ use crate::error::LaunchError;
///
/// # rocket::async_test(async {
/// let rocket = rocket::ignite();
/// let client = Client::new(rocket).await.expect("valid rocket");
/// let client = Client::tracked(rocket).await.expect("valid rocket");
/// let response = client.post("/")
/// .body("Hello, world!")
/// .dispatch()
@ -44,8 +49,8 @@ use crate::error::LaunchError;
/// ```
pub struct Client {
cargo: Cargo,
cookies: RwLock<cookie::CookieJar>,
pub(in super) tracked: bool,
pub(in super) cookies: cookie::CookieJar,
}
impl Client {
@ -55,8 +60,8 @@ impl Client {
) -> Result<Client, LaunchError> {
rocket.prelaunch_check().await?;
let cargo = rocket.into_cargo().await;
Ok(Client { cargo, tracked, cookies: cookie::CookieJar::new() })
let cookies = RwLock::new(cookie::CookieJar::new());
Ok(Client { cargo, tracked, cookies })
}
// WARNING: This is unstable! Do not use this method outside of Rocket!
@ -66,7 +71,7 @@ impl Client {
{
crate::async_test(async {
let rocket = crate::ignite();
let client = Client::new(rocket).await.expect("valid rocket");
let client = Client::untracked(rocket).await.expect("valid rocket");
let request = client.get("/");
let response = request.clone().dispatch().await;
f(&client, request, response)
@ -79,8 +84,17 @@ impl Client {
}
#[inline(always)]
pub(crate) fn _cookies(&self) -> &cookie::CookieJar {
&self.cookies
pub(crate) fn _with_raw_cookies<F, T>(&self, f: F) -> T
where F: FnOnce(&cookie::CookieJar) -> T
{
f(&*self.cookies.read())
}
#[inline(always)]
pub(crate) fn _with_raw_cookies_mut<F, T>(&self, f: F) -> T
where F: FnOnce(&mut cookie::CookieJar) -> T
{
f(&mut *self.cookies.write())
}
#[inline(always)]

View File

@ -19,7 +19,7 @@ use super::{Client, LocalResponse};
/// use rocket::http::{ContentType, Cookie};
///
/// # rocket::async_test(async {
/// let client = Client::new(rocket::ignite()).await.expect("valid rocket");
/// let client = Client::tracked(rocket::ignite()).await.expect("valid rocket");
/// let req = client.post("/")
/// .header(ContentType::JSON)
/// .remote("127.0.0.1:8000".parse().unwrap())
@ -45,13 +45,15 @@ impl<'c> LocalRequest<'c> {
// We try to validate the URI now so that the inner `Request` contains a
// valid URI. If it doesn't, we set a dummy one.
let origin = Origin::parse(&uri).unwrap_or_else(|_| Origin::dummy());
let request = Request::new(client.rocket(), method, origin.into_owned());
let mut request = Request::new(client.rocket(), method, origin.into_owned());
// Add any cookies we know about.
if client.tracked {
for crumb in client.cookies.iter() {
request.cookies().add_original(crumb.into_cookie());
}
client._with_raw_cookies(|jar| {
for cookie in jar.iter() {
request.cookies_mut().add_original(cookie.clone());
}
})
}
LocalRequest { client, request, uri, data: vec![] }
@ -92,18 +94,19 @@ impl<'c> LocalRequest<'c> {
// If the client is tracking cookies, updates the internal cookie jar
// with the changes reflected by `response`.
if self.client.tracked {
let jar = &self.client.cookies;
let current_time = time::OffsetDateTime::now_utc();
for cookie in response.cookies().iter() {
if let Some(expires) = cookie.expires() {
if expires <= current_time {
jar.force_remove(&cookie);
continue;
self.client._with_raw_cookies_mut(|jar| {
let current_time = time::OffsetDateTime::now_utc();
for cookie in response.cookies().iter() {
if let Some(expires) = cookie.expires() {
if expires <= current_time {
jar.force_remove(cookie);
continue;
}
}
}
jar.add(cookie.into_cookie());
}
jar.add_original(cookie.clone());
}
})
}
response

View File

@ -36,7 +36,7 @@ use crate::{Request, Response};
///
/// # async fn read_body_manually() -> io::Result<()> {
/// // Dispatch a `GET /` request.
/// let client = Client::new(rocket()).await.expect("valid rocket");
/// let client = Client::tracked(rocket()).await.expect("valid rocket");
/// let mut response = client.get("/").dispatch().await;
///
/// // Check metadata validity.
@ -89,9 +89,9 @@ impl<'c> LocalResponse<'c> {
async move {
let response: Response<'c> = f(request).await;
let cookies = CookieJar::new(request.state.config.secret_key());
let mut cookies = CookieJar::new(request.state.config.secret_key());
for cookie in response.cookies() {
cookies.add(cookie.into_owned());
cookies.add_original(cookie.into_owned());
}
LocalResponse { cookies, _request: boxed_req, response, }

View File

@ -20,7 +20,7 @@ use crate::http::Method;
/// use rocket::local::blocking::Client;
///
/// let rocket = rocket::ignite();
/// let client = Client::new(rocket).expect("valid rocket");
/// let client = Client::tracked(rocket).expect("valid rocket");
/// let response = client.post("/")
/// .body("Hello, world!")
/// .dispatch();
@ -49,7 +49,7 @@ impl Client {
where F: FnOnce(&Self, LocalRequest<'_>, LocalResponse<'_>) -> T + Send
{
let rocket = crate::ignite();
let client = Client::new(rocket).expect("valid rocket");
let client = Client::untracked(rocket).expect("valid rocket");
let request = client.get("/");
let response = request.clone().dispatch();
f(&client, request, response)
@ -68,8 +68,10 @@ impl Client {
}
#[inline(always)]
fn _cookies(&self) -> &cookie::CookieJar {
self.inner._cookies()
pub(crate) fn _with_raw_cookies<F, T>(&self, f: F) -> T
where F: FnOnce(&crate::http::private::cookie::CookieJar) -> T
{
self.inner._with_raw_cookies(f)
}
#[inline(always)]

View File

@ -17,7 +17,7 @@ use super::{Client, LocalResponse};
/// use rocket::local::blocking::{Client, LocalRequest};
/// use rocket::http::{ContentType, Cookie};
///
/// let client = Client::new(rocket::ignite()).expect("valid rocket");
/// let client = Client::tracked(rocket::ignite()).expect("valid rocket");
/// let req = client.post("/")
/// .header(ContentType::JSON)
/// .remote("127.0.0.1:8000".parse().unwrap())

View File

@ -33,7 +33,7 @@ use super::Client;
///
/// # fn read_body_manually() -> io::Result<()> {
/// // Dispatch a `GET /` request.
/// let client = Client::new(rocket()).expect("valid rocket");
/// let client = Client::tracked(rocket()).expect("valid rocket");
/// let mut response = client.get("/").dispatch();
///
/// // Check metadata validity.

View File

@ -41,22 +41,19 @@ macro_rules! req_method {
macro_rules! pub_client_impl {
($import:literal $(@$prefix:tt $suffix:tt)?) =>
{
/// Construct a new `Client` from an instance of `Rocket` with cookie
/// tracking.
/// Construct a new `Client` from an instance of `Rocket` _with_ cookie
/// tracking. This is typically the desired mode of operation for testing.
///
/// # 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.
/// With cookie tracking enabled, a `Client` propagates cookie changes made
/// by responses to previously dispatched requests. In other words,
/// succeeding requests reflect changes (additions and removals) made by any
/// prior responses.
///
/// 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 tracking may be desired. The
/// [`untracked()`](Client::untracked()) method creates a `Client` that
/// _will not_ track cookies.
/// Cookie tracking requires synchronization between dispatches. **As such,
/// cookie tracking _should not_ be enabled if a local client is being used
/// to serve requests on multiple threads.**
///
/// # Errors
///
@ -67,10 +64,10 @@ macro_rules! pub_client_impl {
#[doc = $import]
///
/// let rocket = rocket::ignite();
/// let client = Client::new(rocket);
/// let client = Client::tracked(rocket);
/// ```
#[inline(always)]
pub $($prefix)? fn new(rocket: Rocket) -> Result<Self, LaunchError> {
pub $($prefix)? fn tracked(rocket: Rocket) -> Result<Self, LaunchError> {
Self::_new(rocket, true) $(.$suffix)?
}
@ -79,8 +76,9 @@ macro_rules! pub_client_impl {
///
/// # Cookie Tracking
///
/// Unlike the [`new()`](Client::new()) constructor, a `Client` returned
/// from this method _does not_ automatically propagate cookie changes.
/// Unlike the [`tracked()`](Client::tracked()) constructor, a `Client`
/// returned from this method _does not_ automatically propagate cookie
/// changes and thus requires no synchronization between dispatches.
///
/// # Errors
///
@ -94,7 +92,16 @@ macro_rules! pub_client_impl {
/// let client = Client::untracked(rocket);
/// ```
pub $($prefix)? fn untracked(rocket: Rocket) -> Result<Self, LaunchError> {
Self::_new(rocket, true) $(.$suffix)?
Self::_new(rocket, false) $(.$suffix)?
}
/// Deprecated alias to [`Client::tracked()`].
#[deprecated(
since = "0.5",
note = "choose between `Client::untracked()` and `Client::tracked()`"
)]
pub $($prefix)? fn new(rocket: Rocket) -> Result<Self, LaunchError> {
Self::tracked(rocket) $(.$suffix)?
}
/// Returns a reference to the `Rocket` this client is creating requests
@ -153,7 +160,8 @@ macro_rules! pub_client_impl {
#[inline(always)]
pub fn cookies(&self) -> crate::http::CookieJar<'_> {
let key = self.rocket().config.secret_key();
crate::http::CookieJar::from(self._cookies().clone(), key)
let jar = self._with_raw_cookies(|jar| jar.clone());
crate::http::CookieJar::from(jar, key)
}
req_method!($import, "GET", get, Method::Get);

View File

@ -55,7 +55,7 @@
//! fn test_hello_world_blocking() {
//! // Construct a client to use for dispatching requests.
//! use rocket::local::blocking::Client;
//! let client = Client::new(super::rocket())
//! let client = Client::tracked(super::rocket())
//! .expect("valid `Rocket`");
//!
//! // Dispatch a request to 'GET /' and validate the response.
@ -68,7 +68,7 @@
//! async fn test_hello_world_async() {
//! // Construct a client to use for dispatching requests.
//! use rocket::local::asynchronous::Client;
//! let client = Client::new(super::rocket()).await
//! let client = Client::tracked(super::rocket()).await
//! .expect("valid `Rocket`");
//!
//! // Dispatch a request to 'GET /' and validate the response.
@ -91,7 +91,7 @@
//!
//! **Usage**
//!
//! A `Client` is constructed via the [`new()`] ([`async` `new()`]) or
//! A `Client` is constructed via the [`tracked()`] ([`async` `tracked()`]) or
//! [`untracked()`] ([`async` `untracked()`]) methods from an already
//! constructed `Rocket` instance. Once a value of `Client` has been
//! constructed, [`get()`], [`put()`], [`post()`], and so on ([`async` `get()`],
@ -100,7 +100,7 @@
//!
//! **Cookie Tracking**
//!
//! A `Client` constructed using `new()` propagates cookie changes made by
//! A `Client` constructed using `tracked()` 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
@ -117,9 +117,9 @@
//! [`LocalRequest`]: blocking::LocalRequest
//! [`async` `LocalRequest`]: asynchronous::LocalRequest
//!
//! [`new()`]: blocking::Client::new()
//! [`tracked()`]: blocking::Client::tracked()
//! [`untracked()`]: blocking::Client::untracked()
//! [`async` `new()`]: asynchronous::Client::new()
//! [`async` `tracked()`]: asynchronous::Client::tracked()
//! [`async` `untracked()`]: asynchronous::Client::untracked()
//!
//! [`get()`]: blocking::Client::get()

View File

@ -111,8 +111,8 @@ macro_rules! pub_request_impl {
/// # });
/// ```
#[inline]
pub fn cookie(self, cookie: crate::http::Cookie<'_>) -> Self {
self._request().cookies().add_original(cookie.into_owned());
pub fn cookie(mut self, cookie: crate::http::Cookie<'_>) -> Self {
self._request_mut().cookies_mut().add_original(cookie.into_owned());
self
}
@ -131,11 +131,11 @@ macro_rules! pub_request_impl {
/// # });
/// ```
#[inline]
pub fn cookies<'a, C>(self, cookies: C) -> Self
pub fn cookies<'a, C>(mut self, cookies: C) -> Self
where C: IntoIterator<Item = crate::http::Cookie<'a>>
{
for cookie in cookies {
self._request().cookies().add_original(cookie.into_owned());
self._request_mut().cookies_mut().add_original(cookie.into_owned());
}
self
@ -161,8 +161,8 @@ macro_rules! pub_request_impl {
#[cfg(feature = "secrets")]
#[cfg_attr(nightly, doc(cfg(feature = "secrets")))]
#[inline]
pub fn private_cookie(self, cookie: crate::http::Cookie<'static>) -> Self {
self._request().cookies().add_original_private(cookie);
pub fn private_cookie(mut self, cookie: crate::http::Cookie<'static>) -> Self {
self._request_mut().cookies_mut().add_original_private(cookie);
self
}

View File

@ -832,6 +832,10 @@ impl<'r> Request<'r> {
self.method.store(method, Ordering::Release)
}
pub(crate) fn cookies_mut(&mut self) -> &mut CookieJar<'r> {
&mut self.state.cookies
}
/// Convert from Hyper types into a Rocket Request.
pub(crate) fn from_hyp(
rocket: &'r Rocket,

View File

@ -182,7 +182,6 @@ impl<R> Flash<R> {
Cookie::build(FLASH_COOKIE_NAME, content)
.max_age(Duration::minutes(5))
.path("/")
.finish()
}
}
@ -215,8 +214,7 @@ impl<'a, 'r> Flash<&'a Request<'r>> {
fn clear_cookie_if_needed(&self) {
// Remove the cookie if it hasn't already been removed.
if !self.consumed.swap(true, Ordering::Relaxed) {
let cookie = Cookie::build(FLASH_COOKIE_NAME, "").path("/").finish();
self.inner.cookies().remove(cookie);
self.inner.cookies().remove(Cookie::named(FLASH_COOKIE_NAME));
}
}

View File

@ -731,7 +731,7 @@ impl<'r> Response<'r> {
self.status = Some(Status::new(code, reason));
}
/// Returns a vector of the cookies set in `self` as identified by the
/// Returns an iterator over the cookies in `self` as identified by the
/// `Set-Cookie` header. Malformed cookies are skipped.
///
/// # Example

View File

@ -50,7 +50,7 @@ impl<'r, R> Created<R> {
/// }
///
/// # let rocket = rocket::ignite().mount("/", routes![create]);
/// # let client = Client::new(rocket).unwrap();
/// # let client = Client::tracked(rocket).unwrap();
/// let response = client.get("/").dispatch();
///
/// let loc = response.headers().get_one("Location");
@ -79,7 +79,7 @@ impl<'r, R> Created<R> {
/// }
///
/// # let rocket = rocket::ignite().mount("/", routes![create]);
/// # let client = Client::new(rocket).unwrap();
/// # let client = Client::tracked(rocket).unwrap();
/// let response = client.get("/").dispatch();
///
/// let loc = response.headers().get_one("Location");
@ -112,7 +112,7 @@ impl<'r, R> Created<R> {
/// }
///
/// # let rocket = rocket::ignite().mount("/", routes![create]);
/// # let client = Client::new(rocket).unwrap();
/// # let client = Client::tracked(rocket).unwrap();
/// let response = client.get("/").dispatch();
///
/// let loc = response.headers().get_one("Location");

View File

@ -347,7 +347,8 @@ impl Rocket {
request._set_method(Method::Get);
// Return early so we don't set cookies twice.
let try_next: BoxFuture<'_, _> = Box::pin(self.route_and_process(request, data));
let try_next: BoxFuture<'_, _> =
Box::pin(self.route_and_process(request, data));
return try_next.await;
} else {
// No match was found and it can't be autohandled. 404.
@ -359,8 +360,9 @@ impl Rocket {
// Set the cookies. Note that error responses will only include
// cookies set by the error handler. See `handle_error` for more.
for crumb in request.cookies().delta() {
response.adjoin_header(crumb)
let delta_jar = request.cookies().take_delta_jar();
for cookie in delta_jar.delta() {
response.adjoin_header(cookie);
}
response

View File

@ -19,7 +19,7 @@ mod test_absolute_uris_okay {
#[test]
fn redirect_works() {
let rocket = rocket::ignite().mount("/", routes![google, redirect]);
let client = Client::new(rocket).unwrap();
let client = Client::tracked(rocket).unwrap();
let response = client.get("/google").dispatch();
let location = response.headers().get_one("Location");

View File

@ -25,11 +25,11 @@ mod tests {
let rocket = rocket::ignite()
.mount("/", routes![index])
.register(catchers![not_found])
.attach(AdHoc::on_request("Add Fairing Cookie", |req, _| Box::pin(async move {
.attach(AdHoc::on_request("Add Cookie", |req, _| Box::pin(async move {
req.cookies().add(Cookie::new("fairing", "woo"));
})));
let client = Client::new(rocket).unwrap();
let client = Client::tracked(rocket).unwrap();
// Check that the index returns the `index` and `fairing` cookie.
let response = client.get("/").dispatch();

View File

@ -20,7 +20,7 @@ mod conditionally_set_server_header {
#[test]
fn do_not_overwrite_server_header() {
let rocket = rocket::ignite().mount("/", routes![do_not_overwrite, use_default]);
let client = Client::new(rocket).unwrap();
let client = Client::tracked(rocket).unwrap();
let response = client.get("/do_not_overwrite").dispatch();
let server = response.headers().get_one("Server");

View File

@ -46,7 +46,7 @@ fn test_derive_reexports() {
use rocket::local::blocking::Client;
let rocket = rocket::ignite().mount("/", routes![index, number]);
let client = Client::new(rocket).unwrap();
let client = Client::tracked(rocket).unwrap();
let response = client.get("/").dispatch();
assert_eq!(response.into_string().unwrap(), "hello");

View File

@ -40,7 +40,7 @@ mod fairing_before_head_strip {
})
}));
let client = Client::new(rocket).unwrap();
let client = Client::tracked(rocket).unwrap();
let response = client.head("/").dispatch();
assert_eq!(response.status(), Status::Ok);
assert!(response.body().is_none());
@ -71,7 +71,7 @@ mod fairing_before_head_strip {
})
}));
let client = Client::new(rocket).unwrap();
let client = Client::tracked(rocket).unwrap();
let response = client.head("/").dispatch();
assert_eq!(response.status(), Status::Ok);
assert!(response.body().is_none());

View File

@ -28,7 +28,7 @@ mod flash_lazy_remove_tests {
fn test() {
use super::*;
let r = rocket::ignite().mount("/", routes![set, unused, used]);
let client = Client::new(r).unwrap();
let client = Client::tracked(r).unwrap();
// Ensure the cookie's not there at first.
let response = client.get("/unused").dispatch();

View File

@ -20,7 +20,7 @@ mod tests {
#[test]
fn method_eval() {
let client = Client::new(rocket::ignite().mount("/", routes![bug])).unwrap();
let client = Client::tracked(rocket::ignite().mount("/", routes![bug])).unwrap();
let response = client.post("/")
.header(ContentType::Form)
.body("_method=patch&form_data=Form+data")
@ -31,7 +31,7 @@ mod tests {
#[test]
fn get_passes_through() {
let client = Client::new(rocket::ignite().mount("/", routes![bug])).unwrap();
let client = Client::tracked(rocket::ignite().mount("/", routes![bug])).unwrap();
let response = client.get("/")
.header(ContentType::Form)
.body("_method=patch&form_data=Form+data")

View File

@ -19,7 +19,7 @@ mod tests {
use rocket::http::Status;
fn check_decoding(raw: &str, decoded: &str) {
let client = Client::new(rocket::ignite().mount("/", routes![bug])).unwrap();
let client = Client::tracked(rocket::ignite().mount("/", routes![bug])).unwrap();
let response = client.post("/")
.header(ContentType::Form)
.body(format!("form_data={}", raw))

View File

@ -30,7 +30,7 @@ mod head_handling_tests {
#[test]
fn auto_head() {
let client = Client::new(rocket::ignite().mount("/", routes())).unwrap();
let client = Client::tracked(rocket::ignite().mount("/", routes())).unwrap();
let response = client.head("/").dispatch();
let content_type: Vec<_> = response.headers().get("Content-Type").collect();
@ -46,7 +46,7 @@ mod head_handling_tests {
#[test]
fn user_head() {
let client = Client::new(rocket::ignite().mount("/", routes())).unwrap();
let client = Client::tracked(rocket::ignite().mount("/", routes())).unwrap();
let response = client.head("/other").dispatch();
let content_type: Vec<_> = response.headers().get("Content-Type").collect();

View File

@ -29,7 +29,7 @@ mod limits_tests {
#[test]
fn large_enough() {
let client = Client::new(rocket_with_forms_limit(128)).unwrap();
let client = Client::tracked(rocket_with_forms_limit(128)).unwrap();
let response = client.post("/")
.body("value=Hello+world")
.header(ContentType::Form)
@ -40,7 +40,7 @@ mod limits_tests {
#[test]
fn just_large_enough() {
let client = Client::new(rocket_with_forms_limit(17)).unwrap();
let client = Client::tracked(rocket_with_forms_limit(17)).unwrap();
let response = client.post("/")
.body("value=Hello+world")
.header(ContentType::Form)
@ -51,7 +51,7 @@ mod limits_tests {
#[test]
fn much_too_small() {
let client = Client::new(rocket_with_forms_limit(4)).unwrap();
let client = Client::tracked(rocket_with_forms_limit(4)).unwrap();
let response = client.post("/")
.body("value=Hello+world")
.header(ContentType::Form)
@ -62,7 +62,7 @@ mod limits_tests {
#[test]
fn contracted() {
let client = Client::new(rocket_with_forms_limit(10)).unwrap();
let client = Client::tracked(rocket_with_forms_limit(10)).unwrap();
let response = client.post("/")
.body("value=Hello+world")
.header(ContentType::Form)

View File

@ -54,7 +54,7 @@ mod local_request_content_type_tests {
#[test]
fn has_no_ct() {
let client = Client::new(rocket()).unwrap();
let client = Client::tracked(rocket()).unwrap();
let req = client.post("/");
assert_eq!(req.clone().dispatch().into_string(), Some("Absent".to_string()));
@ -69,7 +69,7 @@ mod local_request_content_type_tests {
#[test]
fn has_ct() {
let client = Client::new(rocket()).unwrap();
let client = Client::tracked(rocket()).unwrap();
let req = client.post("/").header(ContentType::JSON);
assert_eq!(req.clone().dispatch().into_string(), Some("Present".to_string()));

View File

@ -20,7 +20,7 @@ mod private_cookies {
fn private_cookie_is_returned() {
let rocket = rocket::ignite().mount("/", routes![return_private_cookie]);
let client = Client::new(rocket).unwrap();
let client = Client::tracked(rocket).unwrap();
let req = client.get("/").private_cookie(Cookie::new("cookie_name", "cookie_value"));
let response = req.dispatch();
@ -32,7 +32,7 @@ mod private_cookies {
fn regular_cookie_is_not_returned() {
let rocket = rocket::ignite().mount("/", routes![return_private_cookie]);
let client = Client::new(rocket).unwrap();
let client = Client::tracked(rocket).unwrap();
let req = client.get("/").cookie(Cookie::new("cookie_name", "cookie_value"));
let response = req.dispatch();

View File

@ -28,7 +28,7 @@ mod many_cookie_jars_tests {
#[test]
fn test_mutli_add() {
let client = Client::new(rocket()).unwrap();
let client = Client::tracked(rocket()).unwrap();
let response = client.post("/").dispatch();
let cookies = response.cookies();
assert_eq!(cookies.iter().count(), 2);
@ -38,7 +38,7 @@ mod many_cookie_jars_tests {
#[test]
fn test_mutli_get() {
let client = Client::new(rocket()).unwrap();
let client = Client::tracked(rocket()).unwrap();
let response = client.get("/")
.cookie(Cookie::new("a", "a_val"))
.cookie(Cookie::new("b", "hi!"))

View File

@ -30,7 +30,7 @@ mod mapped_base_tests {
#[test]
fn only_prefix() {
let client = Client::new(super::rocket()).unwrap();
let client = Client::tracked(super::rocket()).unwrap();
let response = client.get("/a/b/3").dispatch();
assert_eq!(response.into_string().unwrap(), "3");
@ -44,7 +44,7 @@ mod mapped_base_tests {
#[test]
fn prefix_and_base() {
let client = Client::new(super::rocket()).unwrap();
let client = Client::tracked(super::rocket()).unwrap();
let response = client.get("/foo/a/b/23").dispatch();
assert_eq!(response.into_string().unwrap(), "23");

View File

@ -46,7 +46,7 @@ mod nested_fairing_attaches_tests {
#[test]
fn test_counts() {
let client = Client::new(rocket()).unwrap();
let client = Client::tracked(rocket()).unwrap();
let response = client.get("/").dispatch();
assert_eq!(response.into_string(), Some("1, 1".into()));

View File

@ -35,7 +35,7 @@ mod tests {
macro_rules! check_dispatch {
($mount:expr, $ct:expr, $body:expr) => (
let client = Client::new(rocket()).unwrap();
let client = Client::tracked(rocket()).unwrap();
let mut req = client.post($mount);
let ct: Option<ContentType> = $ct;
if let Some(ct) = ct {

View File

@ -14,9 +14,8 @@ mod tests {
#[test]
fn error_catcher_redirect() {
let client = Client::new(rocket::ignite().register(catchers![not_found])).unwrap();
let client = Client::tracked(rocket::ignite().register(catchers![not_found])).unwrap();
let response = client.get("/unknown").dispatch();
println!("Response:\n{:?}", response);
let location: Vec<_> = response.headers().get("location").collect();
assert_eq!(response.status(), Status::SeeOther);

View File

@ -24,7 +24,7 @@ mod route_guard_tests {
.mount("/first", routes![files])
.mount("/second", routes![files]);
let client = Client::new(rocket).unwrap();
let client = Client::tracked(rocket).unwrap();
assert_path(&client, "/first/some/path");
assert_path(&client, "/second/some/path");
assert_path(&client, "/first/second/b/c");

View File

@ -36,7 +36,7 @@ mod tests {
let rocket = rocket::ignite()
.mount("/", routes![test, two, one_two, none, dual])
.mount("/point", routes![test, two, one_two, dual]);
let client = Client::new(rocket).unwrap();
let client = Client::tracked(rocket).unwrap();
// We construct a path that matches each of the routes above. We ensure the
// prefix is stripped, confirming that dynamic segments are working.

View File

@ -26,7 +26,7 @@ mod strict_and_lenient_forms_tests {
const FIELD_VALUE: &str = "just_some_value";
fn client() -> Client {
Client::new(rocket::ignite().mount("/", routes![strict, lenient])).unwrap()
Client::tracked(rocket::ignite().mount("/", routes![strict, lenient])).unwrap()
}
#[test]

View File

@ -3,7 +3,7 @@ use rocket::local::blocking::Client;
#[test]
fn test_local_request_clone_soundness() {
let client = Client::new(rocket::ignite()).unwrap();
let client = Client::tracked(rocket::ignite()).unwrap();
// creates two LocalRequest instances that shouldn't share the same req
let r1 = client.get("/").header(Header::new("key", "val1"));

View File

@ -33,7 +33,7 @@ mod tests {
#[test]
fn uri_percent_encoding_redirect() {
let expected_location = vec!["/hello/John%5B%5D%7C%5C%25@%5E"];
let client = Client::new(rocket()).unwrap();
let client = Client::tracked(rocket()).unwrap();
let response = client.get("/raw").dispatch();
let location: Vec<_> = response.headers().get("location").collect();
@ -48,7 +48,7 @@ mod tests {
#[test]
fn uri_percent_encoding_get() {
let client = Client::new(rocket()).unwrap();
let client = Client::tracked(rocket()).unwrap();
let name = Uri::percent_encode(NAME);
let response = client.get(format!("/hello/{}", name)).dispatch();
assert_eq!(response.status(), Status::Ok);

View File

@ -62,7 +62,7 @@ pub fn test_config(environment: Environment) {
}))
.mount("/", routes![check_config]);
let client = Client::new(rocket).unwrap();
let client = Client::tracked(rocket).unwrap();
let response = client.get("/check_config").dispatch();
assert_eq!(response.status(), Status::Ok);
}

View File

@ -5,7 +5,7 @@ use rocket::local::blocking::Client;
fn test<H>(method: Method, uri: &str, header: H, status: Status, body: String)
where H: Into<Header<'static>>
{
let client = Client::new(super::rocket()).unwrap();
let client = Client::tracked(super::rocket()).unwrap();
let response = client.req(method, uri).header(header).dispatch();
assert_eq!(response.status(), status);
assert_eq!(response.into_string(), Some(body));

View File

@ -7,7 +7,7 @@ use rocket_contrib::templates::Template;
#[test]
fn test_submit() {
let client = Client::new(rocket()).unwrap();
let client = Client::tracked(rocket()).unwrap();
let response = client.post("/submit")
.header(ContentType::Form)
.body("message=Hello from Rocket!")
@ -24,7 +24,7 @@ fn test_submit() {
fn test_body(optional_cookie: Option<Cookie<'static>>, expected_body: String) {
// Attach a cookie if one is given.
let client = Client::new(rocket()).unwrap();
let client = Client::tracked(rocket()).unwrap();
let response = match optional_cookie {
Some(cookie) => client.get("/").cookie(cookie).dispatch(),
None => client.get("/").dispatch(),
@ -36,7 +36,7 @@ fn test_body(optional_cookie: Option<Cookie<'static>>, expected_body: String) {
#[test]
fn test_index() {
let client = Client::new(rocket()).unwrap();
let client = Client::tracked(rocket()).unwrap();
// Render the template with an empty context.
let mut context: HashMap<&str, &str> = HashMap::new();

View File

@ -3,7 +3,7 @@ use rocket::http::Status;
#[test]
fn test_hello() {
let client = Client::new(super::rocket()).unwrap();
let client = Client::tracked(super::rocket()).unwrap();
let (name, age) = ("Arthur", 42);
let uri = format!("/hello/{}/{}", name, age);
@ -15,7 +15,7 @@ fn test_hello() {
#[test]
fn forced_error_and_default_catcher() {
let client = Client::new(super::rocket()).unwrap();
let client = Client::tracked(super::rocket()).unwrap();
let request = client.get("/404");
let expected = super::not_found(request.inner());
@ -44,7 +44,7 @@ fn forced_error_and_default_catcher() {
#[test]
fn test_hello_invalid_age() {
let client = Client::new(super::rocket()).unwrap();
let client = Client::tracked(super::rocket()).unwrap();
for &(name, age) in &[("Ford", -129), ("Trillian", 128)] {
let request = client.get(format!("/hello/{}/{}", name, age));

View File

@ -3,14 +3,14 @@ use rocket::local::blocking::Client;
#[test]
fn rewrite_get_put() {
let client = Client::new(rocket()).unwrap();
let client = Client::tracked(rocket()).unwrap();
let response = client.get("/").dispatch();
assert_eq!(response.into_string(), Some("Hello, fairings!".into()));
}
#[test]
fn counts() {
let client = Client::new(rocket()).unwrap();
let client = Client::tracked(rocket()).unwrap();
// Issue 1 GET request.
client.get("/").dispatch();
@ -30,7 +30,7 @@ fn counts() {
#[test]
fn token() {
let client = Client::new(rocket()).unwrap();
let client = Client::tracked(rocket()).unwrap();
// Ensure the token is '123', which is what we have in `Rocket.toml`.
let res = client.get("/token").dispatch();

View File

@ -42,7 +42,7 @@ macro_rules! assert_valid_raw_form {
#[test]
fn test_good_forms() {
let client = Client::new(rocket()).unwrap();
let client = Client::tracked(rocket()).unwrap();
let mut input = FormInput {
checkbox: true,
number: 310,
@ -121,7 +121,7 @@ macro_rules! assert_invalid_raw_form {
#[test]
fn check_semantically_invalid_forms() {
let client = Client::new(rocket()).unwrap();
let client = Client::tracked(rocket()).unwrap();
let mut form_vals = ["true", "1", "a", "hi", "hey", "b"];
form_vals[0] = "not true";
@ -177,7 +177,7 @@ fn check_semantically_invalid_forms() {
#[test]
fn check_structurally_invalid_forms() {
let client = Client::new(rocket()).unwrap();
let client = Client::tracked(rocket()).unwrap();
assert_invalid_raw_form!(&client, "==&&&&&&==");
assert_invalid_raw_form!(&client, "a&=b");
assert_invalid_raw_form!(&client, "=");
@ -185,7 +185,7 @@ fn check_structurally_invalid_forms() {
#[test]
fn check_bad_utf8() {
let client = Client::new(rocket()).unwrap();
let client = Client::tracked(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

@ -5,7 +5,7 @@ use rocket::http::{ContentType, Status};
fn test_login<T>(user: &str, pass: &str, age: &str, status: Status, body: T)
where T: Into<Option<&'static str>> + Send
{
let client = Client::new(rocket()).unwrap();
let client = Client::tracked(rocket()).unwrap();
let query = format!("username={}&password={}&age={}", user, pass, age);
let response = client.post("/login")
.header(ContentType::Form)
@ -44,7 +44,7 @@ fn test_invalid_age() {
}
fn check_bad_form(form_str: &str, status: Status) {
let client = Client::new(rocket()).unwrap();
let client = Client::tracked(rocket()).unwrap();
let response = client.post("/login")
.header(ContentType::Form)
.body(form_str)

View File

@ -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()).unwrap();
let $client = Client::tracked(rocket()).unwrap();
let $response = $client.req($method, $path).dispatch();
$body
})

View File

@ -2,7 +2,7 @@ use rocket::{self, local::blocking::Client};
#[test]
fn hello_world() {
let client = Client::new(super::rocket()).unwrap();
let client = Client::tracked(super::rocket()).unwrap();
let response = client.get("/").dispatch();
assert_eq!(response.into_string(), Some("Hello, Rust 2018!".into()));
}
@ -35,14 +35,14 @@ mod scoped_uri_tests {
#[test]
fn test_inner_hello() {
let client = Client::new(rocket()).unwrap();
let client = Client::tracked(rocket()).unwrap();
let response = client.get("/").dispatch();
assert_eq!(response.into_string(), Some("Hello! Try /Rust%202018.".into()));
}
#[test]
fn test_hello_name() {
let client = Client::new(rocket()).unwrap();
let client = Client::tracked(rocket()).unwrap();
let response = client.get("/Rust%202018").dispatch();
assert_eq!(response.into_string().unwrap(), "Hello, Rust 2018! This is /Rust%202018.");
}

View File

@ -2,12 +2,12 @@ use rocket::local::blocking::Client;
use rocket::http::Status;
fn test(uri: String, expected: String) {
let client = Client::new(super::rocket()).unwrap();
let client = Client::tracked(super::rocket()).unwrap();
assert_eq!(client.get(&uri).dispatch().into_string(), Some(expected));
}
fn test_404(uri: &'static str) {
let client = Client::new(super::rocket()).unwrap();
let client = Client::tracked(super::rocket()).unwrap();
assert_eq!(client.get(uri).dispatch().status(), Status::NotFound);
}

View File

@ -2,7 +2,7 @@ use rocket::local::blocking::Client;
#[test]
fn hello_world() {
let client = Client::new(super::rocket()).unwrap();
let client = Client::tracked(super::rocket()).unwrap();
let response = client.get("/").dispatch();
assert_eq!(response.into_string(), Some("Hello, world!".into()));
}

View File

@ -4,7 +4,7 @@ use rocket::http::{Status, ContentType};
#[test]
fn bad_get_put() {
let client = Client::new(rocket()).unwrap();
let client = Client::tracked(rocket()).unwrap();
// Try to get a message with an ID that doesn't exist.
let res = client.get("/message/99").header(ContentType::JSON).dispatch();
@ -34,7 +34,7 @@ fn bad_get_put() {
#[test]
fn post_get_put_get() {
let client = Client::new(rocket()).unwrap();
let client = Client::tracked(rocket()).unwrap();
// Check that a message with ID 1 doesn't exist.
let res = client.get("/message/1").header(ContentType::JSON).dispatch();

View File

@ -3,7 +3,7 @@ use rocket::http::Status;
#[test]
fn test_push_pop() {
let client = Client::new(super::rocket()).unwrap();
let client = Client::tracked(super::rocket()).unwrap();
let response = client.put("/push?event=test1").dispatch();
assert_eq!(response.status(), Status::Ok);

View File

@ -3,7 +3,7 @@ use rocket::local::blocking::Client;
use rocket::http::{ContentType, Status};
fn test(uri: &str, content_type: ContentType, status: Status, body: String) {
let client = Client::new(rocket()).unwrap();
let client = Client::tracked(rocket()).unwrap();
let response = client.get(uri).header(content_type).dispatch();
assert_eq!(response.status(), status);
assert_eq!(response.into_string(), Some(body));
@ -30,7 +30,7 @@ fn test_echo() {
#[test]
fn test_upload() {
let client = Client::new(rocket()).unwrap();
let client = Client::tracked(rocket()).unwrap();
let expected_body = "Lorem ipsum dolor sit amet, consectetur adipiscing elit, \
sed do eiusmod tempor incididunt ut labore et dolore \
magna aliqua".to_string();

View File

@ -12,7 +12,7 @@ struct Message {
#[test]
fn msgpack_get() {
let client = Client::new(rocket()).unwrap();
let client = Client::tracked(rocket()).unwrap();
let res = client.get("/message/1").header(ContentType::MsgPack).dispatch();
assert_eq!(res.status(), Status::Ok);
assert_eq!(res.content_type(), Some(ContentType::MsgPack));
@ -25,7 +25,7 @@ fn msgpack_get() {
#[test]
fn msgpack_post() {
// Dispatch request with a message of `[2, "Goodbye, world!"]`.
let client = Client::new(rocket()).unwrap();
let client = Client::tracked(rocket()).unwrap();
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])

View File

@ -2,14 +2,14 @@ use rocket::local::blocking::Client;
use rocket::http::Status;
fn test_200(uri: &str, expected_body: &str) {
let client = Client::new(super::rocket()).unwrap();
let client = Client::tracked(super::rocket()).unwrap();
let response = client.get(uri).dispatch();
assert_eq!(response.status(), Status::Ok);
assert_eq!(response.into_string(), Some(expected_body.to_string()));
}
fn test_303(uri: &str, expected_location: &str) {
let client = Client::new(super::rocket()).unwrap();
let client = Client::tracked(super::rocket()).unwrap();
let response = client.get(uri).dispatch();
let location_headers: Vec<_> = response.headers().get("Location").collect();
assert_eq!(response.status(), Status::SeeOther);

View File

@ -8,7 +8,7 @@ fn extract_id(from: &str) -> Option<String> {
#[test]
fn check_index() {
let client = Client::new(rocket()).unwrap();
let client = Client::tracked(rocket()).unwrap();
// Ensure the index returns what we expect.
let response = client.get("/").dispatch();
@ -32,7 +32,7 @@ fn download_paste(client: &Client, id: &str) -> String {
#[test]
fn pasting() {
let client = Client::new(rocket()).unwrap();
let client = Client::tracked(rocket()).unwrap();
// Do a trivial upload, just to make sure it works.
let body_1 = "Hello, world!";

View File

@ -4,7 +4,7 @@ use rocket::http::Status;
macro_rules! run_test {
($query:expr, |$response:ident| $body:expr) => ({
let client = Client::new(rocket()).unwrap();
let client = Client::tracked(rocket()).unwrap();
#[allow(unused_mut)]
let mut $response = client.get(format!("/hello{}", $query)).dispatch();
$body

View File

@ -1,7 +1,7 @@
use rocket::local::blocking::Client;
fn test(uri: String, expected: String) {
let client = Client::new(super::rocket()).unwrap();
let client = Client::tracked(super::rocket()).unwrap();
let response = client.get(&uri).dispatch();
assert_eq!(response.into_string(), Some(expected));
}

View File

@ -3,7 +3,7 @@ use rocket::local::blocking::Client;
#[test]
fn hello() {
let client = Client::new(rocket()).unwrap();
let client = Client::tracked(rocket()).unwrap();
let response = client.get("/").dispatch();
assert_eq!(response.into_string(), Some("Rocketeer".into()));
}

View File

@ -9,7 +9,7 @@ const UPLOAD_CONTENTS: &str = "Hey! I'm going to be uploaded. :D Yay!";
#[test]
fn test_index() {
let client = Client::new(super::rocket()).unwrap();
let client = Client::tracked(super::rocket()).unwrap();
let res = client.get("/").dispatch();
assert_eq!(res.into_string(), Some(super::index().to_string()));
}
@ -21,7 +21,7 @@ fn test_raw_upload() {
let _ = fs::remove_file(&upload_file);
// Do the upload. Make sure we get the expected results.
let client = Client::new(super::rocket()).unwrap();
let client = Client::tracked(super::rocket()).unwrap();
let res = client.post("/upload")
.header(ContentType::Plain)
.body(UPLOAD_CONTENTS)

View File

@ -3,7 +3,7 @@ use rocket::http::Status;
fn client() -> Client {
let rocket = rocket::ignite().mount("/", routes![super::root, super::login]);
Client::new(rocket).unwrap()
Client::tracked(rocket).unwrap()
}
#[test]

View File

@ -31,7 +31,7 @@ mod test {
use rocket::http::Header;
fn test_header_count<'h>(headers: Vec<Header<'static>>) {
let client = Client::new(super::rocket()).unwrap();
let client = Client::tracked(super::rocket()).unwrap();
let mut req = client.get("/");
for header in headers.iter().cloned() {
req.add_header(header);

View File

@ -5,7 +5,7 @@ use rocket::local::blocking::Client;
#[test]
fn test() {
let client = Client::new(rocket()).unwrap();
let client = Client::tracked(rocket()).unwrap();
client.get("/sync").dispatch();
let atomics = client.cargo().state::<Atomics>().unwrap();

View File

@ -23,7 +23,7 @@ fn login(client: &Client, user: &str, pass: &str) -> Option<Cookie<'static>> {
#[test]
fn redirect_on_index() {
let client = Client::new(rocket()).unwrap();
let client = Client::tracked(rocket()).unwrap();
let response = client.get("/").dispatch();
assert_eq!(response.status(), Status::SeeOther);
assert_eq!(response.headers().get_one("Location"), Some("/login"));
@ -31,7 +31,7 @@ fn redirect_on_index() {
#[test]
fn can_login() {
let client = Client::new(rocket()).unwrap();
let client = Client::tracked(rocket()).unwrap();
let response = client.get("/login").dispatch();
assert_eq!(response.status(), Status::Ok);
@ -41,14 +41,14 @@ fn can_login() {
#[test]
fn login_fails() {
let client = Client::new(rocket()).unwrap();
let client = Client::tracked(rocket()).unwrap();
assert!(login(&client, "Seergio", "password").is_none());
assert!(login(&client, "Sergio", "idontknow").is_none());
}
#[test]
fn login_logout_succeeds() {
let client = Client::new(rocket()).unwrap();
let client = Client::tracked(rocket()).unwrap();
let login_cookie = login(&client, "Sergio", "password").expect("logged in");
// Ensure we're logged in.

View File

@ -13,7 +13,7 @@ fn get_count(client: &Client) -> usize {
#[test]
fn test_count() {
let client = Client::new(super::rocket()).unwrap();
let client = Client::tracked(super::rocket()).unwrap();
// Count should start at 0.
assert_eq!(get_count(&client), 0);

View File

@ -9,7 +9,7 @@ use super::rocket;
fn test_query_file<T> (path: &str, file: T, status: Status)
where T: Into<Option<&'static str>>
{
let client = Client::new(rocket()).unwrap();
let client = Client::tracked(rocket()).unwrap();
let response = client.get(path).dispatch();
assert_eq!(response.status(), status);

View File

@ -5,7 +5,7 @@ use rocket::local::blocking::Client;
#[test]
fn test_root() {
let client = Client::new(super::rocket()).unwrap();
let client = Client::tracked(super::rocket()).unwrap();
let res = client.get("/").dispatch();
// Check that we have exactly 25,000 'a'.
@ -24,7 +24,7 @@ fn test_file() {
file.write_all(CONTENTS.as_bytes()).expect("write to big_file");
// Get the big file contents, hopefully.
let client = Client::new(super::rocket()).unwrap();
let client = Client::tracked(super::rocket()).unwrap();
let res = client.get("/big_file").dispatch();
assert_eq!(res.into_string(), Some(CONTENTS.into()));

View File

@ -6,7 +6,7 @@ use rocket_contrib::templates::Template;
macro_rules! dispatch {
($method:expr, $path:expr, |$client:ident, $response:ident| $body:expr) => ({
let $client = Client::new(rocket()).unwrap();
let $client = Client::tracked(rocket()).unwrap();
let $response = $client.req($method, $path).dispatch();
$body
})

View File

@ -26,7 +26,7 @@ mod test {
async fn test_rendezvous() {
use rocket::local::asynchronous::Client;
let client = Client::new(rocket()).await.unwrap();
let client = Client::tracked(rocket()).await.unwrap();
let req = client.get("/barrier");
let (r1, r2) = rocket::tokio::join!(req.clone().dispatch(), req.dispatch());

View File

@ -21,7 +21,7 @@ mod test {
fn test_hello() {
use rocket::local::blocking::Client;
let client = Client::new(rocket()).unwrap();
let client = Client::tracked(rocket()).unwrap();
let response = client.get("/").dispatch();
assert_eq!(response.status(), Status::Ok);
assert_eq!(response.into_string(), Some("Hello, world!".into()));

View File

@ -2,7 +2,7 @@ use rocket::local::blocking::Client;
#[test]
fn hello_world() {
let client = Client::new(super::rocket()).unwrap();
let client = Client::tracked(super::rocket()).unwrap();
let response = client.get("/").dispatch();
assert_eq!(response.into_string(), Some("Hello, world!".into()));
}

View File

@ -17,7 +17,7 @@ macro_rules! run_test {
rocket::async_test(async move {
let rocket = super::rocket();
let $client = Client::new(rocket).await.expect("Rocket client");
let $client = Client::tracked(rocket).await.expect("Rocket client");
let db = super::DbConn::get_one($client.cargo()).await;
let $conn = db.expect("failed to get database connection for testing");
Task::delete_all(&$conn).await.expect("failed to delete all tasks for testing");

View File

@ -3,13 +3,13 @@ use rocket::local::blocking::Client;
use rocket::http::Status;
fn test(uri: &str, expected: &str) {
let client = Client::new(rocket()).unwrap();
let client = Client::tracked(rocket()).unwrap();
let res = client.get(uri).dispatch();
assert_eq!(res.into_string(), Some(expected.into()));
}
fn test_404(uri: &str) {
let client = Client::new(rocket()).unwrap();
let client = Client::tracked(rocket()).unwrap();
let res = client.get(uri).dispatch();
assert_eq!(res.status(), Status::NotFound);
}

View File

@ -24,7 +24,7 @@ instance. Usage is straightforward:
```rust
# use rocket::local::blocking::Client;
# let rocket = rocket::ignite();
let client = Client::new(rocket).unwrap();
let client = Client::tracked(rocket).unwrap();
# let _ = client;
```
@ -33,7 +33,7 @@ instance. Usage is straightforward:
```rust
# use rocket::local::blocking::Client;
# let rocket = rocket::ignite();
# let client = Client::new(rocket).unwrap();
# let client = Client::tracked(rocket).unwrap();
let req = client.get("/");
# let _ = req;
```
@ -43,7 +43,7 @@ instance. Usage is straightforward:
```rust
# use rocket::local::blocking::Client;
# let rocket = rocket::ignite();
# let client = Client::new(rocket).unwrap();
# let client = Client::tracked(rocket).unwrap();
# let req = client.get("/");
let response = req.dispatch();
# let _ = response;
@ -101,7 +101,7 @@ use rocket::local::blocking::Client;
use rocket::http::{ContentType, Status};
let rocket = rocket::ignite().mount("/", routes![hello]);
let client = Client::new(rocket).expect("valid rocket instance");
let client = Client::tracked(rocket).expect("valid rocket instance");
let mut response = client.get("/").dispatch();
assert_eq!(response.status(), Status::Ok);
@ -168,7 +168,7 @@ testing: we _want_ our tests to panic when something goes wrong.
# fn rocket() -> rocket::Rocket { rocket::ignite() }
# use rocket::local::blocking::Client;
let client = Client::new(rocket()).expect("valid rocket instance");
let client = Client::tracked(rocket()).expect("valid rocket instance");
```
Then, we create a new `GET /` request and dispatch it, getting back our
@ -177,7 +177,7 @@ application's response:
```rust
# fn rocket() -> rocket::Rocket { rocket::ignite() }
# use rocket::local::blocking::Client;
# let client = Client::new(rocket()).expect("valid rocket instance");
# let client = Client::tracked(rocket()).expect("valid rocket instance");
let mut response = client.get("/").dispatch();
```
@ -199,7 +199,7 @@ We do this by checking the `Response` object directly:
use rocket::http::{ContentType, Status};
#
# let rocket = rocket::ignite().mount("/", routes![hello]);
# let client = Client::new(rocket).expect("valid rocket instance");
# let client = Client::tracked(rocket).expect("valid rocket instance");
# let mut response = client.get("/").dispatch();
assert_eq!(response.status(), Status::Ok);
@ -232,7 +232,7 @@ mod test {
#[test]
# */ pub
fn hello_world() {
let client = Client::new(rocket()).expect("valid rocket instance");
let client = Client::tracked(rocket()).expect("valid rocket instance");
let mut response = client.get("/").dispatch();
assert_eq!(response.status(), Status::Ok);
assert_eq!(response.into_string(), Some("Hello, world!".into()));