Remove Session in favor of private cookies. New testing API.

Sessions
--------

This commit removes the `Session` type in favor of methods on the
`Cookies` types that allow for adding, removing, and getting private
(signed and encrypted) cookies. These methods provide a superset of
the functionality of `Session` while also being a minimal addition to
the existing API. They can be used to implement the previous `Session`
type as well as other forms of session storage. The new methods are:

  * Cookie::add_private(&mut self, Cookie)
  * Cookie::remove_private(&mut self, Cookie)
  * Cookie::get_private(&self, &str)

Resolves #20

Testing
-------

This commit removes the `rocket::testing` module. It adds the
`rocket::local` module which provides a `Client` type for local
dispatching of requests against a `Rocket` instance. This `local`
package subsumes the previous `testing` package.

Rocket Examples
---------------

The `forms`, `optional_result`, and `hello_alt_methods` examples have
been removed. The following example have been renamed:

  * extended_validation -> form_validation
  * hello_ranks -> ranking
  * from_request -> request_guard
  * hello_tls -> tls

Other Changes
-------------

This commit also includes the following smaller changes:

  * Config::{development, staging, production} constructors have been
    added for easier creation of default `Config` structures.
  * The `Config` type is exported from the root.
  * `Request` implements `Clone` and `Debug`.
  * `Request::new` is no longer exported.
  * A `Response::body_bytes` method was added to easily retrieve a
    response's body as a `Vec<u8>`.
This commit is contained in:
Sergio Benitez 2017-06-06 13:41:04 -07:00
parent 504a7fe583
commit b8ba7b855f
89 changed files with 1243 additions and 1454 deletions

View File

@ -8,28 +8,25 @@ members = [
"contrib/",
"examples/cookies",
"examples/errors",
"examples/extended_validation",
"examples/forms",
"examples/form_validation",
"examples/hello_person",
"examples/query_params",
"examples/hello_world",
"examples/manual_routes",
"examples/optional_redirect",
"examples/optional_result",
"examples/redirect",
"examples/static_files",
"examples/todo",
"examples/content_types",
"examples/hello_ranks",
"examples/ranking",
"examples/testing",
"examples/from_request",
"examples/request_guard",
"examples/stream",
"examples/json",
"examples/msgpack",
"examples/handlebars_templates",
"examples/form_kitchen_sink",
"examples/config",
"examples/hello_alt_methods",
"examples/raw_upload",
"examples/pastebin",
"examples/state",
@ -37,6 +34,6 @@ members = [
"examples/uuid",
"examples/session",
"examples/raw_sqlite",
"examples/hello_tls",
"examples/tls",
"examples/fairings",
]

View File

@ -1,9 +1,9 @@
use rocket::{self, State};
use rocket::fairing::AdHoc;
use rocket::config::{self, Config, Environment};
use rocket::http::{Method, Status};
use rocket::http::Status;
use rocket::LoggingLevel;
use rocket::testing::MockRequest;
use rocket::local::Client;
struct LocalConfig(Config);
@ -55,8 +55,6 @@ pub fn test_config(environment: Environment) {
// environment in `ignite()`. We'll read this back in the handler to config.
::std::env::set_var("ROCKET_ENV", environment.to_string());
// FIXME: launch fairings aren't run during tests since...the Rocket isn't
// being launch
let rocket = rocket::ignite()
.attach(AdHoc::on_attach(|rocket| {
println!("Attaching local config.");
@ -65,7 +63,7 @@ pub fn test_config(environment: Environment) {
}))
.mount("/", routes![check_config]);
let mut request = MockRequest::new(Method::Get, "/check_config");
let response = request.dispatch_with(&rocket);
let client = Client::new(rocket).unwrap();
let response = client.get("/check_config").dispatch();
assert_eq!(response.status(), Status::Ok);
}

View File

@ -2,7 +2,7 @@ use super::rocket;
use super::serde_json;
use super::Person;
use rocket::http::{Accept, ContentType, Header, MediaType, Method, Status};
use rocket::testing::MockRequest;
use rocket::local::Client;
fn test<H>(method: Method, uri: &str, header: H, status: Status, body: String)
where H: Into<Header<'static>>
@ -10,9 +10,9 @@ fn test<H>(method: Method, uri: &str, header: H, status: Status, body: String)
let rocket = rocket::ignite()
.mount("/hello", routes![super::get_hello, super::post_hello])
.catch(errors![super::not_found]);
let mut request = MockRequest::new(method, uri).header(header);
let mut response = request.dispatch_with(&rocket);
let client = Client::new(rocket).unwrap();
let mut response = client.req(method, uri).header(header).dispatch();
assert_eq!(response.status(), status);
assert_eq!(response.body_string(), Some(body));
}

View File

@ -1,7 +1,7 @@
use std::collections::HashMap;
use super::rocket;
use rocket::testing::MockRequest;
use rocket::local::Client;
use rocket::http::*;
use rocket_contrib::Template;
@ -9,11 +9,12 @@ const TEMPLATE_ROOT: &'static str = "templates/";
#[test]
fn test_submit() {
let rocket = rocket();
let mut request = MockRequest::new(Method::Post, "/submit")
let client = Client::new(rocket()).unwrap();
let response = client.post("/submit")
.header(ContentType::Form)
.body("message=Hello from Rocket!");
let response = request.dispatch_with(&rocket);
.body("message=Hello from Rocket!")
.dispatch();
let cookie_headers: Vec<_> = response.headers().get("Set-Cookie").collect();
let location_headers: Vec<_> = response.headers().get("Location").collect();
@ -23,33 +24,26 @@ fn test_submit() {
}
fn test_body(optional_cookie: Option<Cookie<'static>>, expected_body: String) {
let rocket = rocket();
let mut request = MockRequest::new(Method::Get, "/");
// Attach a cookie if one is given.
if let Some(cookie) = optional_cookie {
request = request.cookie(cookie);
}
let client = Client::new(rocket()).unwrap();
let mut response = match optional_cookie {
Some(cookie) => client.get("/").cookie(cookie).dispatch(),
None => client.get("/").dispatch(),
};
let mut response = request.dispatch_with(&rocket);
assert_eq!(response.status(), Status::Ok);
assert_eq!(response.body_string(), Some(expected_body));
}
#[test]
fn test_index() {
// Render the template with an empty context to test against.
// Render the template with an empty context.
let mut context: HashMap<&str, &str> = HashMap::new();
let template = Template::show(TEMPLATE_ROOT, "index", &context).unwrap();
// Test the route without sending the "message" cookie.
test_body(None, template);
// Render the template with a context that contains the message.
context.insert("message", "Hello from Rocket!");
// Test the route with the "message" cookie.
let cookie = Cookie::new("message", "Hello from Rocket!");
let template = Template::show(TEMPLATE_ROOT, "index", &context).unwrap();
test_body(Some(cookie), template);
test_body(Some(Cookie::new("message", "Hello from Rocket!")), template);
}

View File

@ -1,14 +1,14 @@
use super::rocket;
use rocket::testing::MockRequest;
use rocket::http::{Method, Status};
use rocket::local::Client;
use rocket::http::Status;
fn test(uri: &str, status: Status, body: String) {
let rocket = rocket::ignite()
.mount("/", routes![super::hello])
.catch(errors![super::not_found]);
let mut req = MockRequest::new(Method::Get, uri);
let mut response = req.dispatch_with(&rocket);
let client = Client::new(rocket).unwrap();
let mut response = client.get(uri).dispatch();
assert_eq!(response.status(), status);
assert_eq!(response.body_string(), Some(body));
}

View File

@ -1,8 +0,0 @@
[package]
name = "extended_validation"
version = "0.0.0"
workspace = "../../"
[dependencies]
rocket = { path = "../../lib" }
rocket_codegen = { path = "../../codegen" }

View File

@ -1,46 +1,38 @@
use super::rocket;
use rocket::testing::MockRequest;
use rocket::http::Method::*;
use rocket::local::Client;
#[test]
fn rewrite_get_put() {
let rocket = rocket();
let mut req = MockRequest::new(Get, "/");
let mut response = req.dispatch_with(&rocket);
let client = Client::new(rocket()).unwrap();
let mut response = client.get("/").dispatch();
assert_eq!(response.body_string(), Some("Hello, fairings!".into()));
}
#[test]
fn counts() {
let rocket = rocket();
let client = Client::new(rocket()).unwrap();
// Issue 1 GET request.
let mut req = MockRequest::new(Get, "/");
req.dispatch_with(&rocket);
client.get("/").dispatch();
// Check the GET count, taking into account _this_ GET request.
let mut req = MockRequest::new(Get, "/counts");
let mut response = req.dispatch_with(&rocket);
let mut response = client.get("/counts").dispatch();
assert_eq!(response.body_string(), Some("Get: 2\nPost: 0".into()));
// Issue 1 more GET request and a POST.
let mut req = MockRequest::new(Get, "/");
req.dispatch_with(&rocket);
let mut req = MockRequest::new(Post, "/");
req.dispatch_with(&rocket);
client.get("/").dispatch();
client.post("/").dispatch();
// Check the counts.
let mut req = MockRequest::new(Get, "/counts");
let mut response = req.dispatch_with(&rocket);
let mut response = client.get("/counts").dispatch();
assert_eq!(response.body_string(), Some("Get: 4\nPost: 1".into()));
}
#[test]
fn token() {
let rocket = rocket();
let client = Client::new(rocket()).unwrap();
// Ensure the token is '123', which is what we have in `Rocket.toml`.
let mut req = MockRequest::new(Get, "/token");
let mut res = req.dispatch_with(&rocket);
let mut res = client.get("/token").dispatch();
assert_eq!(res.body_string(), Some("123".into()));
}

View File

@ -33,19 +33,19 @@ impl<'v> FromFormValue<'v> for FormOption {
}
#[derive(Debug, FromForm)]
struct FormInput {
struct FormInput<'r> {
checkbox: bool,
number: usize,
#[form(field = "type")]
radio: FormOption,
password: String,
password: &'r RawStr,
#[form(field = "textarea")]
text_area: String,
select: FormOption,
}
#[post("/", data = "<sink>")]
fn sink(sink: Result<Form<FormInput>, Option<String>>) -> String {
fn sink<'r>(sink: Result<Form<'r, FormInput<'r>>, Option<String>>) -> String {
match sink {
Ok(form) => format!("{:?}", form.get()),
Err(Some(f)) => format!("Invalid form input: {}", f),

View File

@ -1,10 +1,8 @@
use std::fmt;
use super::{rocket, FormInput, FormOption};
use rocket::Rocket;
use rocket::testing::MockRequest;
use rocket::local::Client;
use rocket::http::ContentType;
use rocket::http::Method::*;
impl fmt::Display for FormOption {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
@ -16,167 +14,169 @@ impl fmt::Display for FormOption {
}
}
fn assert_form_eq(rocket: &Rocket, form_str: &str, expected: String) {
let mut req = MockRequest::new(Post, "/")
fn assert_form_eq(client: &Client, form_str: &str, expected: String) {
let mut res = client.post("/")
.header(ContentType::Form)
.body(form_str);
let mut res = req.dispatch_with(&rocket);
.body(form_str)
.dispatch();
assert_eq!(res.body_string(), Some(expected));
}
fn assert_valid_form(rocket: &Rocket, input: &FormInput) {
fn assert_valid_form(client: &Client, input: &FormInput) {
let f = format!("checkbox={}&number={}&type={}&password={}&textarea={}&select={}",
input.checkbox, input.number, input.radio, input.password,
input.text_area, input.select);
assert_form_eq(rocket, &f, format!("{:?}", input));
assert_form_eq(client, &f, format!("{:?}", input));
}
fn assert_valid_raw_form(rocket: &Rocket, form_str: &str, input: &FormInput) {
assert_form_eq(rocket, form_str, format!("{:?}", input));
fn assert_valid_raw_form(client: &Client, form_str: &str, input: &FormInput) {
assert_form_eq(client, form_str, format!("{:?}", input));
}
#[test]
fn test_good_forms() {
let rocket = rocket();
let client = Client::new(rocket()).unwrap();
let mut input = FormInput {
checkbox: true,
number: 310,
radio: FormOption::A,
password: "beep".to_string(),
password: "beep".into(),
text_area: "bop".to_string(),
select: FormOption::B
};
assert_valid_form(&rocket, &input);
assert_valid_form(&client, &input);
input.checkbox = false;
assert_valid_form(&rocket, &input);
assert_valid_form(&client, &input);
input.number = 0;
assert_valid_form(&rocket, &input);
assert_valid_form(&client, &input);
input.number = 120;
assert_valid_form(&rocket, &input);
assert_valid_form(&client, &input);
input.number = 133;
assert_valid_form(&rocket, &input);
assert_valid_form(&client, &input);
input.radio = FormOption::B;
assert_valid_form(&rocket, &input);
assert_valid_form(&client, &input);
input.radio = FormOption::C;
assert_valid_form(&rocket, &input);
assert_valid_form(&client, &input);
input.password = "".to_string();
assert_valid_form(&rocket, &input);
input.password = "----90138490285u2o3hndslkv".to_string();
assert_valid_form(&rocket, &input);
input.password = "hi".to_string();
assert_valid_form(&rocket, &input);
input.password = "".into();
assert_valid_form(&client, &input);
input.password = "----90138490285u2o3hndslkv".into();
assert_valid_form(&client, &input);
input.password = "hi".into();
assert_valid_form(&client, &input);
input.text_area = "".to_string();
assert_valid_form(&rocket, &input);
assert_valid_form(&client, &input);
input.text_area = "----90138490285u2o3hndslkv".to_string();
assert_valid_form(&rocket, &input);
assert_valid_form(&client, &input);
input.text_area = "hey".to_string();
assert_valid_form(&rocket, &input);
assert_valid_form(&client, &input);
input.select = FormOption::A;
assert_valid_form(&rocket, &input);
assert_valid_form(&client, &input);
input.select = FormOption::C;
assert_valid_form(&rocket, &input);
assert_valid_form(&client, &input);
// checkbox need not be present; defaults to false; accepts 'on' and 'off'
assert_valid_raw_form(&rocket,
assert_valid_raw_form(&client,
"number=133&type=c&password=hi&textarea=hey&select=c",
&input);
assert_valid_raw_form(&rocket,
assert_valid_raw_form(&client,
"checkbox=off&number=133&type=c&password=hi&textarea=hey&select=c",
&input);
input.checkbox = true;
assert_valid_raw_form(&rocket,
assert_valid_raw_form(&client,
"checkbox=on&number=133&type=c&password=hi&textarea=hey&select=c",
&input);
}
fn assert_invalid_form(rocket: &Rocket, vals: &mut [&str; 6]) {
fn assert_invalid_form(client: &Client, vals: &mut [&str; 6]) {
let s = format!("checkbox={}&number={}&type={}&password={}&textarea={}&select={}",
vals[0], vals[1], vals[2], vals[3], vals[4], vals[5]);
assert_form_eq(rocket, &s, format!("Invalid form input: {}", s));
assert_form_eq(client, &s, format!("Invalid form input: {}", s));
*vals = ["true", "1", "a", "hi", "hey", "b"];
}
fn assert_invalid_raw_form(rocket: &Rocket, form_str: &str) {
assert_form_eq(rocket, form_str, format!("Invalid form input: {}", form_str));
fn assert_invalid_raw_form(client: &Client, form_str: &str) {
assert_form_eq(client, form_str, format!("Invalid form input: {}", form_str));
}
#[test]
fn check_semantically_invalid_forms() {
let rocket = rocket();
let client = Client::new(rocket()).unwrap();
let mut form_vals = ["true", "1", "a", "hi", "hey", "b"];
form_vals[0] = "not true";
assert_invalid_form(&rocket, &mut form_vals);
assert_invalid_form(&client, &mut form_vals);
form_vals[0] = "bing";
assert_invalid_form(&rocket, &mut form_vals);
assert_invalid_form(&client, &mut form_vals);
form_vals[0] = "true0";
assert_invalid_form(&rocket, &mut form_vals);
assert_invalid_form(&client, &mut form_vals);
form_vals[0] = " false";
assert_invalid_form(&rocket, &mut form_vals);
assert_invalid_form(&client, &mut form_vals);
form_vals[1] = "-1";
assert_invalid_form(&rocket, &mut form_vals);
assert_invalid_form(&client, &mut form_vals);
form_vals[1] = "1e10";
assert_invalid_form(&rocket, &mut form_vals);
assert_invalid_form(&client, &mut form_vals);
form_vals[1] = "-1-1";
assert_invalid_form(&rocket, &mut form_vals);
assert_invalid_form(&client, &mut form_vals);
form_vals[1] = "NaN";
assert_invalid_form(&rocket, &mut form_vals);
assert_invalid_form(&client, &mut form_vals);
form_vals[2] = "A";
assert_invalid_form(&rocket, &mut form_vals);
assert_invalid_form(&client, &mut form_vals);
form_vals[2] = "B";
assert_invalid_form(&rocket, &mut form_vals);
assert_invalid_form(&client, &mut form_vals);
form_vals[2] = "d";
assert_invalid_form(&rocket, &mut form_vals);
assert_invalid_form(&client, &mut form_vals);
form_vals[2] = "100";
assert_invalid_form(&rocket, &mut form_vals);
assert_invalid_form(&client, &mut form_vals);
form_vals[2] = "";
assert_invalid_form(&rocket, &mut form_vals);
assert_invalid_form(&client, &mut form_vals);
// password and textarea are always valid, so we skip them
form_vals[5] = "A";
assert_invalid_form(&rocket, &mut form_vals);
assert_invalid_form(&client, &mut form_vals);
form_vals[5] = "b ";
assert_invalid_form(&rocket, &mut form_vals);
assert_invalid_form(&client, &mut form_vals);
form_vals[5] = "d";
assert_invalid_form(&rocket, &mut form_vals);
assert_invalid_form(&client, &mut form_vals);
form_vals[5] = "-a";
assert_invalid_form(&rocket, &mut form_vals);
assert_invalid_form(&client, &mut form_vals);
form_vals[5] = "";
assert_invalid_form(&rocket, &mut form_vals);
assert_invalid_form(&client, &mut form_vals);
// now forms with missing fields
assert_invalid_raw_form(&rocket, "number=10&type=a&password=hi&textarea=hey");
assert_invalid_raw_form(&rocket, "number=10&radio=a&password=hi&textarea=hey&select=b");
assert_invalid_raw_form(&rocket, "number=10&password=hi&select=b");
assert_invalid_raw_form(&rocket, "number=10&select=b");
assert_invalid_raw_form(&rocket, "password=hi&select=b");
assert_invalid_raw_form(&rocket, "password=hi");
assert_invalid_raw_form(&rocket, "");
assert_invalid_raw_form(&client, "number=10&type=a&password=hi&textarea=hey");
assert_invalid_raw_form(&client, "number=10&radio=a&password=hi&textarea=hey&select=b");
assert_invalid_raw_form(&client, "number=10&password=hi&select=b");
assert_invalid_raw_form(&client, "number=10&select=b");
assert_invalid_raw_form(&client, "password=hi&select=b");
assert_invalid_raw_form(&client, "password=hi");
assert_invalid_raw_form(&client, "");
}
#[test]
fn check_structurally_invalid_forms() {
let rocket = rocket();
assert_invalid_raw_form(&rocket, "==&&&&&&==");
assert_invalid_raw_form(&rocket, "a&=b");
assert_invalid_raw_form(&rocket, "=");
let client = Client::new(rocket()).unwrap();
assert_invalid_raw_form(&client, "==&&&&&&==");
assert_invalid_raw_form(&client, "a&=b");
assert_invalid_raw_form(&client, "=");
}
#[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(&rocket(), bad_str, "Form input was invalid UTF8.".into());
assert_form_eq(&client, bad_str, "Form input was invalid UTF8.".into());
}
}

View File

@ -1,5 +1,5 @@
[package]
name = "optional_result"
name = "form_validation"
version = "0.0.0"
workspace = "../../"

View File

@ -1,24 +1,20 @@
use rocket::testing::MockRequest;
use rocket::http::Method::*;
use rocket::http::{ContentType, Status};
use super::rocket;
use rocket::local::Client;
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>>
{
let rocket = rocket();
let client = Client::new(rocket()).unwrap();
let query = format!("username={}&password={}&age={}", user, pass, age);
let mut req = MockRequest::new(Post, "/login")
let mut response = client.post("/login")
.header(ContentType::Form)
.body(&query);
.body(&query)
.dispatch();
let mut response = req.dispatch_with(&rocket);
assert_eq!(response.status(), status);
let body_str = response.body_string();
if let Some(expected_str) = body.into() {
let body_str = response.body_string();
assert!(body_str.map_or(false, |s| s.contains(expected_str)));
}
}
@ -48,12 +44,12 @@ fn test_invalid_age() {
}
fn check_bad_form(form_str: &str, status: Status) {
let rocket = rocket();
let mut req = MockRequest::new(Post, "/login")
let client = Client::new(rocket()).unwrap();
let response = client.post("/login")
.header(ContentType::Form)
.body(form_str);
.body(form_str)
.dispatch();
let response = req.dispatch_with(&rocket);
assert_eq!(response.status(), status);
}
@ -82,5 +78,6 @@ fn test_bad_form_missing_fields() {
#[test]
fn test_bad_form_additional_fields() {
check_bad_form("username=Sergio&password=pass&age=30&addition=1", Status::UnprocessableEntity);
check_bad_form("username=Sergio&password=pass&age=30&addition=1",
Status::UnprocessableEntity);
}

View File

@ -1,14 +0,0 @@
use rocket::response::NamedFile;
use std::io;
use std::path::{Path, PathBuf};
#[get("/")]
fn index() -> io::Result<NamedFile> {
NamedFile::open("static/index.html")
}
#[get("/<file..>", rank = 5)]
fn files(file: PathBuf) -> io::Result<NamedFile> {
NamedFile::open(Path::new("static/").join(file))
}

View File

@ -1,52 +0,0 @@
#![feature(plugin, custom_derive)]
#![plugin(rocket_codegen)]
extern crate rocket;
mod files;
#[cfg(test)] mod tests;
use rocket::request::Form;
use rocket::response::Redirect;
use rocket::http::RawStr;
#[derive(FromForm)]
struct UserLogin<'r> {
username: &'r RawStr,
password: String,
age: Result<usize, &'r RawStr>,
}
#[post("/login", data = "<user_form>")]
fn login<'a>(user_form: Form<'a, UserLogin<'a>>) -> Result<Redirect, String> {
let user = user_form.get();
match user.age {
Ok(age) if age < 21 => return Err(format!("Sorry, {} is too young!", age)),
Ok(age) if age > 120 => return Err(format!("Are you sure you're {}?", age)),
Err(e) => return Err(format!("'{}' is not a valid integer.", e)),
Ok(_) => { /* Move along, adult. */ }
};
if user.username == "Sergio" {
match user.password.as_str() {
"password" => Ok(Redirect::to("/user/Sergio")),
_ => Err("Wrong password!".to_string())
}
} else {
Err(format!("Unrecognized user, '{}'.", user.username))
}
}
#[get("/user/<username>")]
fn user_page(username: String) -> String {
format!("This is {}'s page.", username)
}
fn rocket() -> rocket::Rocket {
rocket::ignite()
.mount("/", routes![files::index, files::files, user_page, login])
}
fn main() {
rocket().launch();
}

View File

@ -1,62 +0,0 @@
use rocket::testing::MockRequest;
use rocket::http::Method::*;
use rocket::http::{ContentType, Status};
use super::rocket;
fn test_login(username: &str, password: &str, age: isize, status: Status,
body: Option<&'static str>) {
let rocket = rocket();
let mut req = MockRequest::new(Post, "/login")
.header(ContentType::Form)
.body(&format!("username={}&password={}&age={}", username, password, age));
let mut response = req.dispatch_with(&rocket);
let body_str = response.body_string();
println!("Checking: {:?}/{:?}/{:?}/{:?}", username, password, age, body_str);
assert_eq!(response.status(), status);
if let Some(string) = body {
assert!(body_str.map_or(true, |s| s.contains(string)));
}
}
#[test]
fn test_good_login() {
test_login("Sergio", "password", 30, Status::SeeOther, None);
}
const OK: Status = self::Status::Ok;
#[test]
fn test_bad_login() {
test_login("Sergio", "password", 20, OK, Some("Sorry, 20 is too young!"));
test_login("Sergio", "password", 200, OK, Some("Are you sure you're 200?"));
test_login("Sergio", "jk", -100, OK, Some("'-100' is not a valid integer."));
test_login("Sergio", "ok", 30, OK, Some("Wrong password!"));
test_login("Mike", "password", 30, OK, Some("Unrecognized user, 'Mike'."));
}
fn check_bad_form(form_str: &str, status: Status) {
let rocket = rocket();
let mut req = MockRequest::new(Post, "/login")
.header(ContentType::Form)
.body(form_str);
let response = req.dispatch_with(&rocket);
assert_eq!(response.status(), status);
}
#[test]
fn test_bad_form() {
check_bad_form("&", Status::BadRequest);
check_bad_form("=", Status::BadRequest);
check_bad_form("&&&===&", Status::BadRequest);
check_bad_form("username=Sergio", Status::UnprocessableEntity);
check_bad_form("username=Sergio&", Status::UnprocessableEntity);
check_bad_form("username=Sergio&pass=something", Status::UnprocessableEntity);
check_bad_form("user=Sergio&password=something", Status::UnprocessableEntity);
check_bad_form("password=something", Status::UnprocessableEntity);
}

View File

@ -1,8 +0,0 @@
<h1>Login</h1>
<form action="/login" method="post" accept-charset="utf-8">
Username:<input type="text" name="username">
Password:<input type="password" name="password">
Age:<input type="number" name="age">
<input type="submit" value="Login">
</form>

View File

@ -1,8 +0,0 @@
[package]
name = "from_request"
version = "0.0.0"
workspace = "../../"
[dependencies]
rocket = { path = "../../lib" }
rocket_codegen = { path = "../../codegen" }

View File

@ -1,17 +1,15 @@
use super::rocket;
use rocket::testing::MockRequest;
use rocket::local::{Client, LocalResponse};
use rocket::http::Method::*;
use rocket::http::Status;
use rocket::Response;
use rocket_contrib::Template;
const TEMPLATE_ROOT: &'static str = "templates/";
macro_rules! dispatch {
($method:expr, $path:expr, $test_fn:expr) => ({
let rocket = rocket();
let mut req = MockRequest::new($method, $path);
$test_fn(req.dispatch_with(&rocket));
let client = Client::new(rocket()).unwrap();
$test_fn(client.req($method, $path).dispatch());
})
}
@ -19,7 +17,7 @@ macro_rules! dispatch {
fn test_root() {
// Check that the redirect works.
for method in &[Get, Head] {
dispatch!(*method, "/", |mut response: Response| {
dispatch!(*method, "/", |mut response: LocalResponse| {
assert_eq!(response.status(), Status::SeeOther);
assert!(response.body().is_none());
@ -30,7 +28,7 @@ fn test_root() {
// Check that other request methods are not accepted (and instead caught).
for method in &[Post, Put, Delete, Options, Trace, Connect, Patch] {
dispatch!(*method, "/", |mut response: Response| {
dispatch!(*method, "/", |mut response: LocalResponse| {
let mut map = ::std::collections::HashMap::new();
map.insert("path", "/");
let expected = Template::show(TEMPLATE_ROOT, "error/404", &map).unwrap();
@ -44,7 +42,7 @@ fn test_root() {
#[test]
fn test_name() {
// Check that the /hello/<name> route works.
dispatch!(Get, "/hello/Jack", |mut response: Response| {
dispatch!(Get, "/hello/Jack", |mut response: LocalResponse| {
let context = super::TemplateContext {
name: "Jack".to_string(),
items: vec!["One", "Two", "Three"].iter().map(|s| s.to_string()).collect()
@ -59,7 +57,7 @@ fn test_name() {
#[test]
fn test_404() {
// Check that the error catcher works.
dispatch!(Get, "/hello/", |mut response: Response| {
dispatch!(Get, "/hello/", |mut response: LocalResponse| {
let mut map = ::std::collections::HashMap::new();
map.insert("path", "/hello/");

View File

@ -1,8 +0,0 @@
[package]
name = "hello_alt_methods"
version = "0.0.0"
workspace = "../../"
[dependencies]
rocket = { path = "../../lib" }
rocket_codegen = { path = "../../codegen" }

View File

@ -1,26 +0,0 @@
#![feature(plugin)]
#![plugin(rocket_codegen)]
extern crate rocket;
use std::io;
use rocket::response::NamedFile;
#[cfg(test)] mod tests;
#[get("/")]
fn index() -> io::Result<NamedFile> {
NamedFile::open("static/index.html")
}
#[put("/")]
fn put() -> &'static str {
"Hello, PUT request!"
}
fn main() {
rocket::ignite()
.mount("/", routes![index, put])
.launch();
}

View File

@ -1,25 +0,0 @@
use super::rocket;
use rocket::testing::MockRequest;
use rocket::http::Method;
use rocket::http::Status;
fn test(method: Method, status: Status, body_prefix: Option<&str>) {
let rocket = rocket::ignite()
.mount("/", routes![super::index, super::put]);
let mut req = MockRequest::new(method, "/");
let mut response = req.dispatch_with(&rocket);
assert_eq!(response.status(), status);
if let Some(expected_body_string) = body_prefix {
let body_str = response.body_string().unwrap();
assert!(body_str.starts_with(expected_body_string));
}
}
#[test]
fn hello_world_alt_methods() {
test(Method::Get, Status::Ok, Some("<!DOCTYPE html>"));
test(Method::Put, Status::Ok, Some("Hello, PUT request!"));
test(Method::Post, Status::NotFound, None);
}

View File

@ -1,14 +0,0 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width" />
<title>Hello Alt Methods</title>
</head>
<body>
<form action="/" method="post" accept-charset="utf-8">
<input type="hidden" name="_method" id="_method" value="put" />
<input type="submit" name="Submit" id="Submit" value="submit" />
</form>
</body>
</html>

View File

@ -1,20 +1,19 @@
use super::rocket;
use rocket::testing::MockRequest;
use rocket::http::Method::*;
use rocket::local::Client;
use rocket::http::Status;
fn client() -> Client {
Client::new(rocket::ignite().mount("/", routes![super::hello, super::hi])).unwrap()
}
fn test(uri: &str, expected: String) {
let rocket = rocket::ignite().mount("/", routes![super::hello, super::hi]);
let mut req = MockRequest::new(Get, uri);
let mut response = req.dispatch_with(&rocket);
assert_eq!(response.body_string(), Some(expected));
let client = client();
assert_eq!(client.get(uri).dispatch().body_string(), Some(expected));
}
fn test_404(uri: &str) {
let rocket = rocket::ignite().mount("/", routes![super::hello, super::hi]);
let mut req = MockRequest::new(Get, uri);
let response = req.dispatch_with(&rocket);
assert_eq!(response.status(), Status::NotFound);
let client = client();
assert_eq!(client.get(uri).dispatch().status(), Status::NotFound);
}
#[test]

View File

@ -1,12 +1,10 @@
use super::rocket;
use rocket::testing::MockRequest;
use rocket::http::Method::*;
use rocket::local::Client;
#[test]
fn hello_world() {
let rocket = rocket::ignite().mount("/", routes![super::hello]);
let mut req = MockRequest::new(Get, "/");
let mut response = req.dispatch_with(&rocket);
let client = Client::new(rocket).unwrap();
let mut response = client.get("/").dispatch();
assert_eq!(response.body_string(), Some("Hello, world!".into()));
}

View File

@ -1,95 +1,72 @@
use rocket;
use rocket::testing::MockRequest;
use rocket::http::Method::*;
use rocket::local::Client;
use rocket::http::{Status, ContentType};
use rocket::Response;
macro_rules! run_test {
($rocket: expr, $req:expr, $test_fn:expr) => ({
let mut req = $req;
$test_fn(req.dispatch_with($rocket));
})
}
#[test]
fn bad_get_put() {
let rocket = rocket();
let client = Client::new(rocket()).unwrap();
// Try to get a message with an ID that doesn't exist.
let req = MockRequest::new(Get, "/message/99").header(ContentType::JSON);
run_test!(&rocket, req, |mut response: Response| {
assert_eq!(response.status(), Status::NotFound);
let mut res = client.get("/message/99").header(ContentType::JSON).dispatch();
assert_eq!(res.status(), Status::NotFound);
let body = response.body().unwrap().into_string().unwrap();
assert!(body.contains("error"));
assert!(body.contains("Resource was not found."));
});
let body = res.body_string().unwrap();
assert!(body.contains("error"));
assert!(body.contains("Resource was not found."));
// Try to get a message with an invalid ID.
let req = MockRequest::new(Get, "/message/hi").header(ContentType::JSON);
run_test!(&rocket, req, |mut response: Response| {
assert_eq!(response.status(), Status::NotFound);
let body = response.body().unwrap().into_string().unwrap();
assert!(body.contains("error"));
});
let mut res = client.get("/message/hi").header(ContentType::JSON).dispatch();
let body = res.body_string().unwrap();
assert_eq!(res.status(), Status::NotFound);
assert!(body.contains("error"));
// Try to put a message without a proper body.
let req = MockRequest::new(Put, "/message/80").header(ContentType::JSON);
run_test!(&rocket, req, |response: Response| {
assert_eq!(response.status(), Status::BadRequest);
});
let res = client.put("/message/80").header(ContentType::JSON).dispatch();
assert_eq!(res.status(), Status::BadRequest);
// Try to put a message for an ID that doesn't exist.
let req = MockRequest::new(Put, "/message/80")
let res = client.put("/message/80")
.header(ContentType::JSON)
.body(r#"{ "contents": "Bye bye, world!" }"#);
.body(r#"{ "contents": "Bye bye, world!" }"#)
.dispatch();
run_test!(&rocket, req, |response: Response| {
assert_eq!(response.status(), Status::NotFound);
});
assert_eq!(res.status(), Status::NotFound);
}
#[test]
fn post_get_put_get() {
let rocket = rocket();
let client = Client::new(rocket()).unwrap();
// Check that a message with ID 1 doesn't exist.
let req = MockRequest::new(Get, "/message/1").header(ContentType::JSON);
run_test!(&rocket, req, |response: Response| {
assert_eq!(response.status(), Status::NotFound);
});
let res = client.get("/message/1").header(ContentType::JSON).dispatch();
assert_eq!(res.status(), Status::NotFound);
// Add a new message with ID 1.
let req = MockRequest::new(Post, "/message/1")
let res = client.post("/message/1")
.header(ContentType::JSON)
.body(r#"{ "contents": "Hello, world!" }"#);
.body(r#"{ "contents": "Hello, world!" }"#)
.dispatch();
run_test!(&rocket, req, |response: Response| {
assert_eq!(response.status(), Status::Ok);
});
assert_eq!(res.status(), Status::Ok);
// Check that the message exists with the correct contents.
let req = MockRequest::new(Get, "/message/1") .header(ContentType::JSON);
run_test!(&rocket, req, |mut response: Response| {
assert_eq!(response.status(), Status::Ok);
let body = response.body().unwrap().into_string().unwrap();
assert!(body.contains("Hello, world!"));
});
let mut res = client.get("/message/1").header(ContentType::JSON).dispatch();
assert_eq!(res.status(), Status::Ok);
let body = res.body().unwrap().into_string().unwrap();
assert!(body.contains("Hello, world!"));
// Change the message contents.
let req = MockRequest::new(Put, "/message/1")
let res = client.put("/message/1")
.header(ContentType::JSON)
.body(r#"{ "contents": "Bye bye, world!" }"#);
.body(r#"{ "contents": "Bye bye, world!" }"#)
.dispatch();
run_test!(&rocket, req, |response: Response| {
assert_eq!(response.status(), Status::Ok);
});
assert_eq!(res.status(), Status::Ok);
// Check that the message exists with the updated contents.
let req = MockRequest::new(Get, "/message/1") .header(ContentType::JSON);
run_test!(&rocket, req, |mut response: Response| {
assert_eq!(response.status(), Status::Ok);
let body = response.body().unwrap().into_string().unwrap();
assert!(!body.contains("Hello, world!"));
assert!(body.contains("Bye bye, world!"));
});
let mut res = client.get("/message/1").header(ContentType::JSON).dispatch();
assert_eq!(res.status(), Status::Ok);
let body = res.body().unwrap().into_string().unwrap();
assert!(!body.contains("Hello, world!"));
assert!(body.contains("Bye bye, world!"));
}

View File

@ -1,18 +1,13 @@
use super::rocket;
use rocket::testing::MockRequest;
use rocket::local::Client;
use rocket::http::Status;
use rocket::http::Method::*;
#[test]
fn test_push_pop() {
let rocket = rocket();
let client = Client::new(super::rocket()).unwrap();
let mut req = MockRequest::new(Put, "/push?description=test1");
let response = req.dispatch_with(&rocket);
let response = client.put("/push?description=test1").dispatch();
assert_eq!(response.status(), Status::Ok);
let mut req = MockRequest::new(Get, "/pop");
let mut response = req.dispatch_with(&rocket);
let mut response = client.get("/pop").dispatch();
assert_eq!(response.body_string(), Some("test1".to_string()));
}

View File

@ -1,13 +1,10 @@
use super::*;
use rocket::testing::MockRequest;
use rocket::local::Client;
use rocket::http::{ContentType, Status};
use rocket::http::Method::*;
fn test(uri: &str, content_type: ContentType, status: Status, body: String) {
let rocket = rocket();
let mut request = MockRequest::new(Get, uri).header(content_type);
let mut response = request.dispatch_with(&rocket);
let client = Client::new(rocket()).unwrap();;
let mut response = client.get(uri).header(content_type).dispatch();
assert_eq!(response.status(), status);
assert_eq!(response.body_string(), Some(body));
}
@ -34,21 +31,21 @@ fn test_echo() {
#[test]
fn test_upload() {
let rocket = rocket();
let client = Client::new(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();
// Upload the body.
let mut request = MockRequest::new(Post, "/upload")
let response = client.post("/upload")
.header(ContentType::Plain)
.body(&expected_body);
let response = request.dispatch_with(&rocket);
.body(&expected_body)
.dispatch();
assert_eq!(response.status(), Status::Ok);
// Ensure we get back the same body.
let mut request = MockRequest::new(Get, "/upload");
let mut response = request.dispatch_with(&rocket);
let mut response = client.get("/upload").dispatch();
assert_eq!(response.status(), Status::Ok);
assert_eq!(response.body_string(), Some(expected_body));
}

View File

@ -1,8 +1,6 @@
use rocket;
use rocket::testing::MockRequest;
use rocket::http::Method::*;
use rocket::local::Client;
use rocket::http::{Status, ContentType};
use rocket::Response;
#[derive(Serialize, Deserialize)]
struct Message {
@ -10,35 +8,26 @@ struct Message {
contents: String
}
macro_rules! run_test {
($rocket: expr, $req:expr, $test_fn:expr) => ({
let mut req = $req;
$test_fn(req.dispatch_with($rocket));
})
}
#[test]
fn msgpack_get() {
let rocket = rocket();
let req = MockRequest::new(Get, "/message/1").header(ContentType::MsgPack);
run_test!(&rocket, req, |mut response: Response| {
assert_eq!(response.status(), Status::Ok);
let body = response.body().unwrap().into_bytes().unwrap();
// Represents a message of `[1, "Hello, world!"]`
assert_eq!(&body, &[146, 1, 173, 72, 101, 108, 108, 111, 44, 32, 119, 111,
114, 108, 100, 33]);
});
let client = Client::new(rocket()).unwrap();
let mut res = client.get("/message/1").header(ContentType::MsgPack).dispatch();
assert_eq!(res.status(), Status::Ok);
// Check that the message is `[1, "Hello, world!"]`
assert_eq!(&res.body_bytes().unwrap(),
&[146, 1, 173, 72, 101, 108, 108, 111, 44, 32, 119, 111, 114, 108, 100, 33]);
}
#[test]
fn msgpack_post() {
let rocket = rocket();
let req = MockRequest::new(Post, "/message")
// Dispatch request with a message of `[2, "Goodbye, world!"]`.
let client = Client::new(rocket()).unwrap();
let mut res = client.post("/message")
.header(ContentType::MsgPack)
// Represents a message of `[2, "Goodbye, world!"]`
.body(&[146, 2, 175, 71, 111, 111, 100, 98, 121, 101, 44, 32, 119, 111, 114, 108, 100, 33]);
run_test!(&rocket, req, |mut response: Response| {
assert_eq!(response.status(), Status::Ok);
assert_eq!(response.body().unwrap().into_string().unwrap(), "Goodbye, world!");
});
.body(&[146, 2, 175, 71, 111, 111, 100, 98, 121, 101, 44, 32, 119, 111, 114, 108, 100, 33])
.dispatch();
assert_eq!(res.status(), Status::Ok);
assert_eq!(res.body_string(), Some("Goodbye, world!".into()));
}

View File

@ -1,24 +1,25 @@
use super::rocket;
use rocket::testing::MockRequest;
use rocket::http::{Method, Status};
use rocket::local::Client;
use rocket::http::Status;
fn test_200(uri: &str, expected_body: &str) {
fn client() -> Client {
let rocket = rocket::ignite()
.mount("/", routes![super::root, super::user, super::login]);
let mut request = MockRequest::new(Method::Get, uri);
let mut response = request.dispatch_with(&rocket);
Client::new(rocket).unwrap()
}
fn test_200(uri: &str, expected_body: &str) {
let client = client();
let mut response = client.get(uri).dispatch();
assert_eq!(response.status(), Status::Ok);
assert_eq!(response.body_string(), Some(expected_body.to_string()));
}
fn test_303(uri: &str, expected_location: &str) {
let rocket = rocket::ignite()
.mount("/", routes![super::root, super::user, super::login]);
let mut request = MockRequest::new(Method::Get, uri);
let response = request.dispatch_with(&rocket);
let client = client();
let response = client.get(uri).dispatch();
let location_headers: Vec<_> = response.headers().get("Location").collect();
assert_eq!(response.status(), Status::SeeOther);
assert_eq!(location_headers, vec![expected_location]);
}

View File

@ -1,21 +0,0 @@
#![feature(plugin)]
#![plugin(rocket_codegen)]
extern crate rocket;
#[cfg(test)] mod tests;
use rocket::http::RawStr;
#[get("/users/<name>")]
fn user(name: &RawStr) -> Option<&'static str> {
if name == "Sergio" {
Some("Hello, Sergio!")
} else {
None
}
}
fn main() {
rocket::ignite().mount("/", routes![user]).launch();
}

View File

@ -1,23 +0,0 @@
use super::rocket;
use rocket::testing::MockRequest;
use rocket::http::{Method, Status};
#[test]
fn test_200() {
let rocket = rocket::ignite().mount("/", routes![super::user]);
let mut request = MockRequest::new(Method::Get, "/users/Sergio");
let mut response = request.dispatch_with(&rocket);
assert_eq!(response.status(), Status::Ok);
assert_eq!(response.body_string(), Some("Hello, Sergio!".into()));
}
#[test]
fn test_404() {
let rocket = rocket::ignite().mount("/", routes![super::user]);
let mut request = MockRequest::new(Method::Get, "/users/unknown");
let response = request.dispatch_with(&rocket);
// Only test the status because the body is the default 404.
assert_eq!(response.status(), Status::NotFound);
}

View File

@ -1,8 +1,6 @@
use super::{rocket, index};
use rocket::testing::MockRequest;
use rocket::http::Method::*;
use rocket::local::Client;
use rocket::http::{Status, ContentType};
use rocket::Rocket;
fn extract_id(from: &str) -> Option<String> {
from.rfind('/').map(|i| &from[(i + 1)..]).map(|s| s.trim_right().to_string())
@ -10,56 +8,52 @@ fn extract_id(from: &str) -> Option<String> {
#[test]
fn check_index() {
let rocket = rocket();
let client = Client::new(rocket()).unwrap();
// Ensure the index returns what we expect.
let mut req = MockRequest::new(Get, "/");
let mut response = req.dispatch_with(&rocket);
let mut response = client.get("/").dispatch();
assert_eq!(response.status(), Status::Ok);
assert_eq!(response.content_type(), Some(ContentType::Plain));
assert_eq!(response.body_string(), Some(index().into()))
}
fn upload_paste(rocket: &Rocket, body: &str) -> String {
let mut req = MockRequest::new(Post, "/").body(body);
let mut response = req.dispatch_with(rocket);
fn upload_paste(client: &Client, body: &str) -> String {
let mut response = client.post("/").body(body).dispatch();
assert_eq!(response.status(), Status::Ok);
assert_eq!(response.content_type(), Some(ContentType::Plain));
extract_id(&response.body_string().unwrap()).unwrap()
}
fn download_paste(rocket: &Rocket, id: &str) -> String {
let mut req = MockRequest::new(Get, format!("/{}", id));
let mut response = req.dispatch_with(rocket);
fn download_paste(client: &Client, id: &str) -> String {
let mut response = client.get(format!("/{}", id)).dispatch();
assert_eq!(response.status(), Status::Ok);
response.body_string().unwrap()
}
#[test]
fn pasting() {
let rocket = rocket();
let client = Client::new(rocket()).unwrap();
// Do a trivial upload, just to make sure it works.
let body_1 = "Hello, world!";
let id_1 = upload_paste(&rocket, body_1);
assert_eq!(download_paste(&rocket, &id_1), body_1);
let id_1 = upload_paste(&client, body_1);
assert_eq!(download_paste(&client, &id_1), body_1);
// Make sure we can keep getting that paste.
assert_eq!(download_paste(&rocket, &id_1), body_1);
assert_eq!(download_paste(&rocket, &id_1), body_1);
assert_eq!(download_paste(&rocket, &id_1), body_1);
assert_eq!(download_paste(&client, &id_1), body_1);
assert_eq!(download_paste(&client, &id_1), body_1);
assert_eq!(download_paste(&client, &id_1), body_1);
// Upload some unicode.
let body_2 = "こんにちは";
let id_2 = upload_paste(&rocket, body_2);
assert_eq!(download_paste(&rocket, &id_2), body_2);
let id_2 = upload_paste(&client, body_2);
assert_eq!(download_paste(&client, &id_2), body_2);
// Make sure we can get both pastes.
assert_eq!(download_paste(&rocket, &id_1), body_1);
assert_eq!(download_paste(&rocket, &id_2), body_2);
assert_eq!(download_paste(&rocket, &id_1), body_1);
assert_eq!(download_paste(&rocket, &id_2), body_2);
assert_eq!(download_paste(&client, &id_1), body_1);
assert_eq!(download_paste(&client, &id_2), body_2);
assert_eq!(download_paste(&client, &id_1), body_1);
assert_eq!(download_paste(&client, &id_2), body_2);
// Now a longer upload.
let body_3 = "Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed
@ -69,8 +63,8 @@ fn pasting() {
in voluptate velit esse cillum dolore eu fugiat nulla pariatur.
Excepteur sint occaecat cupidatat non proident, sunt in culpa qui
officia deserunt mollit anim id est laborum.";
let id_3 = upload_paste(&rocket, body_3);
assert_eq!(download_paste(&rocket, &id_3), body_3);
assert_eq!(download_paste(&rocket, &id_1), body_1);
assert_eq!(download_paste(&rocket, &id_2), body_2);
let id_3 = upload_paste(&client, body_3);
assert_eq!(download_paste(&client, &id_3), body_3);
assert_eq!(download_paste(&client, &id_1), body_1);
assert_eq!(download_paste(&client, &id_2), body_2);
}

View File

@ -1,16 +1,14 @@
use super::rocket;
use rocket::testing::MockRequest;
use rocket::http::Method::*;
use rocket::local::{Client, LocalResponse as Response};
use rocket::http::Status;
use rocket::Response;
macro_rules! run_test {
($query:expr, $test_fn:expr) => ({
let rocket = rocket::ignite()
.mount("/", routes![super::hello]);
let mut request = MockRequest::new(Get, format!("/hello{}", $query));
$test_fn(request.dispatch_with(&rocket));
let client = Client::new(rocket).unwrap();
$test_fn(client.get(format!("/hello{}", $query)).dispatch());
})
}

View File

@ -1,5 +1,5 @@
[package]
name = "forms"
name = "ranking"
version = "0.0.0"
workspace = "../../"

View File

@ -1,11 +1,10 @@
use super::rocket;
use rocket::testing::MockRequest;
use rocket::http::Method::*;
use rocket::local::Client;
fn test(uri: &str, expected: String) {
let rocket = rocket::ignite().mount("/", routes![super::hello, super::hi]);
let mut req = MockRequest::new(Get, uri);
let mut response = req.dispatch_with(&rocket);
let client = Client::new(rocket).unwrap();
let mut response = client.get(uri).dispatch();
assert_eq!(response.body_string(), Some(expected));
}

View File

@ -1,12 +1,9 @@
use super::rocket;
use rocket::testing::MockRequest;
use rocket::http::Method::*;
use rocket::local::Client;
#[test]
fn hello() {
let rocket = rocket();
let mut req = MockRequest::new(Get, "/");
let mut response = req.dispatch_with(&rocket);
let client = Client::new(rocket()).unwrap();
let mut response = client.get("/").dispatch();
assert_eq!(response.body_string(), Some("Rocketeer".into()));
}

View File

@ -1,30 +1,30 @@
use rocket::testing::MockRequest;
use rocket::local::Client;
use rocket::http::{Status, ContentType};
use rocket::http::Method::*;
use std::io::Read;
use std::fs::File;
use std::fs::{self, File};
const UPLOAD_CONTENTS: &str = "Hey! I'm going to be uploaded. :D Yay!";
#[test]
fn test_index() {
let rocket = super::rocket();
let mut req = MockRequest::new(Get, "/");
let mut res = req.dispatch_with(&rocket);
let client = Client::new(super::rocket()).unwrap();
let mut res = client.get("/").dispatch();
assert_eq!(res.body_string(), Some(super::index().to_string()));
}
#[test]
fn test_raw_upload() {
const UPLOAD_CONTENTS: &str = "Hey! I'm going to be uploaded. :D Yay!";
let rocket = super::rocket();
let mut req = MockRequest::new(Post, "/upload")
.header(ContentType::Plain)
.body(UPLOAD_CONTENTS);
// Delete the upload file before we begin.
let _ = fs::remove_file("/tmp/upload.txt");
// Do the upload. Make sure we get the expected results.
let mut res = req.dispatch_with(&rocket);
let client = Client::new(super::rocket()).unwrap();
let mut res = client.post("/upload")
.header(ContentType::Plain)
.body(UPLOAD_CONTENTS)
.dispatch();
assert_eq!(res.status(), Status::Ok);
assert_eq!(res.body_string(), Some(UPLOAD_CONTENTS.len().to_string()));

View File

@ -1,38 +1,31 @@
use super::rocket;
use rocket::testing::MockRequest;
use rocket::Response;
use rocket::http::Method::*;
use rocket::local::Client;
use rocket::http::Status;
macro_rules! run_test {
($path:expr, $test_fn:expr) => ({
let rocket = rocket::ignite().mount("/", routes![super::root, super::login]);
let mut request = MockRequest::new(Get, format!($path));
$test_fn(request.dispatch_with(&rocket));
})
fn client() -> Client {
let rocket = rocket::ignite().mount("/", routes![super::root, super::login]);
Client::new(rocket).unwrap()
}
#[test]
fn test_root() {
run_test!("/", |mut response: Response| {
assert!(response.body().is_none());
assert_eq!(response.status(), Status::SeeOther);
for h in response.headers().iter() {
match h.name.as_str() {
"Location" => assert_eq!(h.value, "/login"),
"Content-Length" => assert_eq!(h.value.parse::<i32>().unwrap(), 0),
_ => { /* let these through */ }
}
let client = client();
let mut response = client.get("/").dispatch();
assert!(response.body().is_none());
assert_eq!(response.status(), Status::SeeOther);
for h in response.headers().iter() {
match h.name.as_str() {
"Location" => assert_eq!(h.value, "/login"),
"Content-Length" => assert_eq!(h.value.parse::<i32>().unwrap(), 0),
_ => { /* let these through */ }
}
});
}
}
#[test]
fn test_login() {
run_test!("/login", |mut response: Response| {
assert_eq!(response.body_string(),
Some("Hi! Please log in before continuing.".to_string()));
assert_eq!(response.status(), Status::Ok);
});
let client = client();
let mut r = client.get("/login").dispatch();
assert_eq!(r.body_string(), Some("Hi! Please log in before continuing.".into()));
}

View File

@ -1,5 +1,5 @@
[package]
name = "hello_ranks"
name = "request_guard"
version = "0.0.0"
workspace = "../../"

View File

@ -28,27 +28,27 @@ fn header_count(header_count: HeaderCount) -> String {
format!("Your request contained {} headers!", header_count)
}
fn rocket() -> rocket::Rocket {
rocket::ignite().mount("/", routes![header_count])
}
fn main() {
rocket::ignite().mount("/", routes![header_count]).launch();
rocket().launch();
}
#[cfg(test)]
mod test {
use super::rocket;
use rocket::testing::MockRequest;
use rocket::http::Method::*;
use rocket::local::Client;
use rocket::http::Header;
fn test_header_count<'h>(headers: Vec<Header<'static>>) {
let rocket = rocket::ignite()
.mount("/", routes![super::header_count]);
let mut req = MockRequest::new(Get, "/");
let client = Client::new(super::rocket()).unwrap();
let mut req = client.get("/");
for header in headers.iter().cloned() {
req = req.header(header);
req.add_header(header);
}
let mut response = req.dispatch_with(&rocket);
let mut response = req.dispatch();
let expect = format!("Your request contained {} headers!", headers.len());
assert_eq!(response.body_string(), Some(expect));
}
@ -56,7 +56,8 @@ mod test {
#[test]
fn test_n_headers() {
for i in 0..50 {
let headers = (0..i).map(|n| Header::new(n.to_string(), n.to_string()))
let headers = (0..i)
.map(|n| Header::new(n.to_string(), n.to_string()))
.collect();
test_header_count(headers);

View File

@ -9,7 +9,7 @@ use std::collections::HashMap;
use rocket::Outcome;
use rocket::request::{self, Form, FlashMessage, FromRequest, Request};
use rocket::response::{Redirect, Flash};
use rocket::http::{Cookie, Session};
use rocket::http::{Cookie, Cookies};
use rocket_contrib::Template;
#[derive(FromForm)]
@ -25,8 +25,8 @@ impl<'a, 'r> FromRequest<'a, 'r> for User {
type Error = ();
fn from_request(request: &'a Request<'r>) -> request::Outcome<User, ()> {
let user = request.session()
.get("user_id")
let user = request.cookies()
.get_private("user_id")
.and_then(|cookie| cookie.value().parse().ok())
.map(|id| User(id));
@ -38,9 +38,9 @@ impl<'a, 'r> FromRequest<'a, 'r> for User {
}
#[post("/login", data = "<login>")]
fn login(mut session: Session, login: Form<Login>) -> Flash<Redirect> {
fn login(mut cookies: Cookies, login: Form<Login>) -> Flash<Redirect> {
if login.get().username == "Sergio" && login.get().password == "password" {
session.set(Cookie::new("user_id", 1.to_string()));
cookies.add_private(Cookie::new("user_id", 1.to_string()));
Flash::success(Redirect::to("/"), "Successfully logged in.")
} else {
Flash::error(Redirect::to("/login"), "Invalid username/password.")
@ -48,8 +48,8 @@ fn login(mut session: Session, login: Form<Login>) -> Flash<Redirect> {
}
#[post("/logout")]
fn logout(mut session: Session) -> Flash<Redirect> {
session.remove(Cookie::named("user_id"));
fn logout(mut cookies: Cookies) -> Flash<Redirect> {
cookies.remove_private(Cookie::named("user_id"));
Flash::success(Redirect::to("/login"), "Successfully logged out.")
}
@ -82,6 +82,7 @@ fn index() -> Redirect {
fn main() {
rocket::ignite()
.attach(Template::fairing())
.mount("/", routes![index, user_index, login, logout, login_user, login_page])
.launch();
}

View File

@ -1,32 +1,28 @@
use rocket::Rocket;
use rocket::testing::MockRequest;
use rocket::http::Method::*;
use rocket::local::Client;
use rocket::http::Status;
fn register_hit(rocket: &Rocket) {
let mut req = MockRequest::new(Get, "/");
let response = req.dispatch_with(&rocket);
fn register_hit(client: &Client) {
let response = client.get("/").dispatch();;
assert_eq!(response.status(), Status::Ok);
}
fn get_count(rocket: &Rocket) -> usize {
let mut req = MockRequest::new(Get, "/count");
let mut response = req.dispatch_with(&rocket);
fn get_count(client: &Client) -> usize {
let mut response = client.get("/count").dispatch();
response.body_string().and_then(|s| s.parse().ok()).unwrap()
}
#[test]
fn test_count() {
let rocket = super::rocket();
let client = Client::new(super::rocket()).unwrap();
// Count should start at 0.
assert_eq!(get_count(&rocket), 0);
assert_eq!(get_count(&client), 0);
for _ in 0..99 { register_hit(&rocket); }
assert_eq!(get_count(&rocket), 99);
for _ in 0..99 { register_hit(&client); }
assert_eq!(get_count(&client), 99);
register_hit(&rocket);
assert_eq!(get_count(&rocket), 100);
register_hit(&client);
assert_eq!(get_count(&client), 100);
}
// Cargo runs each test in parallel on different threads. We use all of these

View File

@ -1,8 +1,7 @@
use std::fs::File;
use std::io::Read;
use rocket::testing::MockRequest;
use rocket::http::Method::*;
use rocket::local::Client;
use rocket::http::Status;
use super::rocket;
@ -10,10 +9,8 @@ use super::rocket;
fn test_query_file<T> (path: &str, file: T, status: Status)
where T: Into<Option<&'static str>>
{
let rocket = rocket();
let mut req = MockRequest::new(Get, &path);
let mut response = req.dispatch_with(&rocket);
let client = Client::new(rocket()).unwrap();
let mut response = client.get(path).dispatch();
assert_eq!(response.status(), status);
let body_data = response.body().and_then(|body| body.into_bytes());

View File

@ -1,14 +1,12 @@
use std::fs::{self, File};
use std::io::prelude::*;
use rocket::testing::MockRequest;
use rocket::http::Method::*;
use rocket::local::Client;
#[test]
fn test_root() {
let rocket = super::rocket();
let mut req = MockRequest::new(Get, "/");
let mut res = req.dispatch_with(&rocket);
let client = Client::new(super::rocket()).unwrap();
let mut res = client.get("/").dispatch();
// Check that we have exactly 25,000 'a'.
let res_str = res.body_string().unwrap();
@ -26,9 +24,8 @@ fn test_file() {
file.write_all(CONTENTS.as_bytes()).expect("write to big_file");
// Get the big file contents, hopefully.
let rocket = super::rocket();
let mut req = MockRequest::new(Get, "/big_file");
let mut res = req.dispatch_with(&rocket);
let client = Client::new(super::rocket()).unwrap();
let mut res = client.get("/big_file").dispatch();
assert_eq!(res.body_string(), Some(CONTENTS.into()));
// Delete the 'big_file'.

View File

@ -8,22 +8,24 @@ fn hello() -> &'static str {
"Hello, world!"
}
fn rocket() -> rocket::Rocket {
rocket::ignite().mount("/", routes![hello])
}
fn main() {
rocket::ignite().mount("/", routes![hello]).launch();
rocket().launch();
}
#[cfg(test)]
mod test {
use super::rocket;
use rocket::testing::MockRequest;
use rocket::local::Client;
use rocket::http::Status;
use rocket::http::Method::*;
#[test]
fn test_hello() {
let rocket = rocket::ignite().mount("/", routes![super::hello]);
let mut req = MockRequest::new(Get, "/");
let mut response = req.dispatch_with(&rocket);
let client = Client::new(rocket()).unwrap();
let mut response = client.get("/").dispatch();
assert_eq!(response.status(), Status::Ok);
assert_eq!(response.body_string(), Some("Hello, world!".into()));
}

View File

@ -1,5 +1,5 @@
[package]
name = "hello_tls"
name = "tls"
version = "0.0.0"
workspace = "../../"

View File

@ -1,12 +1,10 @@
use super::rocket;
use rocket::testing::MockRequest;
use rocket::http::Method::*;
use rocket::local::Client;
#[test]
fn hello_world() {
let rocket = rocket::ignite().mount("/", routes![super::hello]);
let mut req = MockRequest::new(Get, "/");
let mut response = req.dispatch_with(&rocket);
let client = Client::new(rocket).unwrap();
let mut response = client.get("/").dispatch();
assert_eq!(response.body_string(), Some("Hello, world!".into()));
}

View File

@ -5,8 +5,7 @@ use super::task::Task;
use self::parking_lot::Mutex;
use self::rand::{Rng, thread_rng};
use rocket::testing::MockRequest;
use rocket::http::Method::*;
use rocket::local::Client;
use rocket::http::{Status, ContentType};
// We use a lock to synchronize between tests so DB operations don't collide.
@ -15,9 +14,10 @@ use rocket::http::{Status, ContentType};
static DB_LOCK: Mutex<()> = Mutex::new(());
macro_rules! run_test {
(|$rocket:ident, $conn:ident| $block:expr) => ({
(|$client:ident, $conn:ident| $block:expr) => ({
let _lock = DB_LOCK.lock();
let ($rocket, db) = super::rocket();
let (rocket, db) = super::rocket();
let $client = Client::new(rocket).expect("Rocket client");
let $conn = db.expect("failed to get database connection for testing");
$block
})
@ -25,15 +25,15 @@ macro_rules! run_test {
#[test]
fn test_insertion_deletion() {
run_test!(|rocket, conn| {
run_test!(|client, conn| {
// Get the tasks before making changes.
let init_tasks = Task::all(&conn);
// Issue a request to insert a new task.
let mut req = MockRequest::new(Post, "/todo")
client.post("/todo")
.header(ContentType::Form)
.body("description=My+first+task");
req.dispatch_with(&rocket);
.body("description=My+first+task")
.dispatch();
// Ensure we have one more task in the database.
let new_tasks = Task::all(&conn);
@ -45,8 +45,7 @@ fn test_insertion_deletion() {
// Issue a request to delete the task.
let id = new_tasks[0].id.unwrap();
let mut req = MockRequest::new(Delete, format!("/todo/{}", id));
req.dispatch_with(&rocket);
client.delete(format!("/todo/{}", id)).dispatch();
// Ensure it's gone.
let final_tasks = Task::all(&conn);
@ -59,24 +58,22 @@ fn test_insertion_deletion() {
#[test]
fn test_toggle() {
run_test!(|rocket, conn| {
run_test!(|client, conn| {
// Issue a request to insert a new task; ensure it's not yet completed.
let mut req = MockRequest::new(Post, "/todo")
client.post("/todo")
.header(ContentType::Form)
.body("description=test_for_completion");
req.dispatch_with(&rocket);
.body("description=test_for_completion")
.dispatch();
let task = Task::all(&conn)[0].clone();
assert_eq!(task.completed, false);
// Issue a request to toggle the task; ensure it is completed.
let mut req = MockRequest::new(Put, format!("/todo/{}", task.id.unwrap()));
req.dispatch_with(&rocket);
client.put(format!("/todo/{}", task.id.unwrap())).dispatch();
assert_eq!(Task::all(&conn)[0].completed, true);
// Issue a request to toggle the task; ensure it's not completed again.
let mut req = MockRequest::new(Put, format!("/todo/{}", task.id.unwrap()));
req.dispatch_with(&rocket);
client.put(format!("/todo/{}", task.id.unwrap())).dispatch();
assert_eq!(Task::all(&conn)[0].completed, false);
})
}
@ -86,7 +83,7 @@ fn test_many_insertions() {
const ITER: usize = 100;
let mut rng = thread_rng();
run_test!(|rocket, conn| {
run_test!(|client, conn| {
// Get the number of tasks initially.
let init_num = Task::all(&conn).len();
let mut descs = Vec::new();
@ -94,10 +91,10 @@ fn test_many_insertions() {
for i in 0..ITER {
// Issue a request to insert a new task with a random description.
let desc: String = rng.gen_ascii_chars().take(12).collect();
let mut req = MockRequest::new(Post, "/todo")
client.post("/todo")
.header(ContentType::Form)
.body(format!("description={}", desc));
req.dispatch_with(&rocket);
.body(format!("description={}", desc))
.dispatch();
// Record the description we choose for this iteration.
descs.insert(0, desc);
@ -115,32 +112,34 @@ fn test_many_insertions() {
#[test]
fn test_bad_form_submissions() {
run_test!(|rocket, _conn| {
run_test!(|client, _conn| {
// Submit an empty form. We should get a 422 but no flash error.
let mut req = MockRequest::new(Post, "/todo").header(ContentType::Form);
let res = req.dispatch_with(&rocket);
assert_eq!(res.status(), Status::UnprocessableEntity);
let res = client.post("/todo")
.header(ContentType::Form)
.dispatch();
let mut cookies = res.headers().get("Set-Cookie");
assert_eq!(res.status(), Status::UnprocessableEntity);
assert!(!cookies.any(|value| value.contains("error")));
// Submit a form with an empty description.
let mut req = MockRequest::new(Post, "/todo")
// Submit a form with an empty description. We look for 'error' in the
// cookies which corresponds to flash message being set as an error.
let res = client.post("/todo")
.header(ContentType::Form)
.body("description=");
.body("description=")
.dispatch();
// We look for 'error' in the cookies which corresponds to flash message
// being set as an error.
let res = req.dispatch_with(&rocket);
let mut cookies = res.headers().get("Set-Cookie");
assert!(cookies.any(|value| value.contains("error")));
// Submit a form without a description. Expect a 422 but no flash error.
let mut req = MockRequest::new(Post, "/todo")
let res = client.post("/todo")
.header(ContentType::Form)
.body("evil=smile");
let res = req.dispatch_with(&rocket);
assert_eq!(res.status(), Status::UnprocessableEntity);
.body("evil=smile")
.dispatch();
let mut cookies = res.headers().get("Set-Cookie");
assert_eq!(res.status(), Status::UnprocessableEntity);
assert!(!cookies.any(|value| value.contains("error")));
})
}

View File

@ -39,8 +39,11 @@ fn people(id: UUID) -> Result<String, String> {
.ok_or(format!("Person not found for UUID: {}", id))?)
}
fn main() {
fn rocket() -> rocket::Rocket {
rocket::ignite()
.mount("/", routes![people])
.launch();
}
fn main() {
rocket().launch();
}

View File

@ -1,23 +1,16 @@
use super::rocket;
use rocket::testing::MockRequest;
use rocket::http::Method::*;
use rocket::local::Client;
use rocket::http::Status;
fn test(uri: &str, expected: &str) {
let rocket = rocket::ignite().mount("/", routes![super::people]);
let mut req = MockRequest::new(Get, uri);
let mut res = req.dispatch_with(&rocket);
let client = Client::new(rocket()).unwrap();
let mut res = client.get(uri).dispatch();
assert_eq!(res.body_string(), Some(expected.into()));
}
fn test_404(uri: &str) {
let rocket = rocket::ignite().mount("/", routes![super::people]);
let mut req = MockRequest::new(Get, uri);
let res = req.dispatch_with(&rocket);
let client = Client::new(rocket()).unwrap();
let res = client.get(uri).dispatch();
assert_eq!(res.status(), Status::NotFound);
}
@ -26,8 +19,7 @@ fn test_people() {
test("/people/7f205202-7ba1-4c39-b2fc-3e630722bf9f", "We found: Lacy");
test("/people/4da34121-bc7d-4fc1-aee6-bf8de0795333", "We found: Bob");
test("/people/ad962969-4e3d-4de7-ac4a-2d86d6d10839", "We found: George");
test("/people/e18b3a5c-488f-4159-a240-2101e0da19fd", "Person not found for UUID: e18b3a5c-488f-4159-a240-2101e0da19fd");
test("/people/e18b3a5c-488f-4159-a240-2101e0da19fd",
"Person not found for UUID: e18b3a5c-488f-4159-a240-2101e0da19fd");
test_404("/people/invalid_uuid");
}

View File

@ -23,7 +23,7 @@ log = "0.3"
url = "1"
toml = { version = "0.2", default-features = false }
num_cpus = "1"
state = "0.2.1"
state = "0.2.2"
time = "0.1"
memchr = "1"
base64 = "0.5.2"
@ -32,7 +32,7 @@ pear = "0.0.8"
pear_codegen = "0.0.8"
rustls = { version = "0.8.0", optional = true }
cookie = { version = "0.8.1", features = ["percent-encode", "secure"] }
hyper = { version = "0.10.9", default-features = false }
hyper = { version = "0.10.11", default-features = false }
ordermap = "0.2"
[dependencies.hyper-rustls]

View File

@ -13,8 +13,7 @@ fn post() -> &'static str { "post" }
fn rocket() -> rocket::Rocket {
let config = Config::new(Environment::Production).unwrap();
rocket::custom(config, false)
.mount("/", routes![get, post])
rocket::custom(config, false).mount("/", routes![get, post])
}
mod benches {
@ -22,35 +21,34 @@ mod benches {
use super::rocket;
use self::test::Bencher;
use rocket::testing::MockRequest;
use rocket::http::Method::*;
use rocket::local::Client;
use rocket::http::{Accept, ContentType};
#[bench]
fn accept_format(b: &mut Bencher) {
let rocket = rocket();
let mut request = MockRequest::new(Get, "/").header(Accept::JSON);
b.iter(|| { request.dispatch_with(&rocket); });
let client = Client::new(rocket()).unwrap();
let mut request = client.get("/").header(Accept::JSON);
b.iter(|| { request.mut_dispatch(); });
}
#[bench]
fn wrong_accept_format(b: &mut Bencher) {
let rocket = rocket();
let mut request = MockRequest::new(Get, "/").header(Accept::HTML);
b.iter(|| { request.dispatch_with(&rocket); });
let client = Client::new(rocket()).unwrap();
let mut request = client.get("/").header(Accept::HTML);
b.iter(|| { request.mut_dispatch(); });
}
#[bench]
fn content_type_format(b: &mut Bencher) {
let rocket = rocket();
let mut request = MockRequest::new(Post, "/").header(ContentType::JSON);
b.iter(|| { request.dispatch_with(&rocket); });
let client = Client::new(rocket()).unwrap();
let mut request = client.post("/").header(ContentType::JSON);
b.iter(|| { request.mut_dispatch(); });
}
#[bench]
fn wrong_content_type_format(b: &mut Bencher) {
let rocket = rocket();
let mut request = MockRequest::new(Post, "/").header(ContentType::Plain);
b.iter(|| { request.dispatch_with(&rocket); });
let client = Client::new(rocket()).unwrap();
let mut request = client.post("/").header(ContentType::Plain);
b.iter(|| { request.mut_dispatch(); });
}
}

View File

@ -35,36 +35,35 @@ mod benches {
use super::rocket;
use self::test::Bencher;
use rocket::testing::MockRequest;
use rocket::http::Method::*;
use rocket::local::Client;
use rocket::http::{Accept, ContentType};
#[bench]
fn accept_format(b: &mut Bencher) {
let rocket = rocket();
let client = Client::new(rocket()).unwrap();
let mut requests = vec![];
requests.push(MockRequest::new(Get, "/").header(Accept::JSON));
requests.push(MockRequest::new(Get, "/").header(Accept::HTML));
requests.push(MockRequest::new(Get, "/").header(Accept::Plain));
requests.push(client.get("/").header(Accept::JSON));
requests.push(client.get("/").header(Accept::HTML));
requests.push(client.get("/").header(Accept::Plain));
b.iter(|| {
for request in requests.iter_mut() {
request.dispatch_with(&rocket);
request.mut_dispatch();
}
});
}
#[bench]
fn content_type_format(b: &mut Bencher) {
let rocket = rocket();
let client = Client::new(rocket()).unwrap();
let mut requests = vec![];
requests.push(MockRequest::new(Post, "/").header(ContentType::JSON));
requests.push(MockRequest::new(Post, "/").header(ContentType::HTML));
requests.push(MockRequest::new(Post, "/").header(ContentType::Plain));
requests.push(client.post("/").header(ContentType::JSON));
requests.push(client.post("/").header(ContentType::HTML));
requests.push(client.post("/").header(ContentType::Plain));
b.iter(|| {
for request in requests.iter_mut() {
request.dispatch_with(&rocket);
request.mut_dispatch();
}
});
}

View File

@ -49,82 +49,82 @@ mod benches {
use super::{hello_world_rocket, rocket};
use self::test::Bencher;
use rocket::testing::MockRequest;
use rocket::local::Client;
use rocket::http::Method::*;
#[bench]
fn bench_hello_world(b: &mut Bencher) {
let rocket = hello_world_rocket();
let mut request = MockRequest::new(Get, "/");
let client = Client::new(hello_world_rocket()).unwrap();
let mut request = client.get("/");
b.iter(|| {
request.dispatch_with(&rocket);
request.mut_dispatch();
});
}
#[bench]
fn bench_single_get_index(b: &mut Bencher) {
let rocket = rocket();
let mut request = MockRequest::new(Get, "/");
let client = Client::new(rocket()).unwrap();
let mut request = client.get("/");
b.iter(|| {
request.dispatch_with(&rocket);
request.mut_dispatch();
});
}
#[bench]
fn bench_get_put_post_index(b: &mut Bencher) {
let rocket = rocket();
let client = Client::new(rocket()).unwrap();
// Hold all of the requests we're going to make during the benchmark.
let mut requests = vec![];
requests.push(MockRequest::new(Get, "/"));
requests.push(MockRequest::new(Put, "/"));
requests.push(MockRequest::new(Post, "/"));
requests.push(client.get("/"));
requests.push(client.put("/"));
requests.push(client.post("/"));
b.iter(|| {
for request in requests.iter_mut() {
request.dispatch_with(&rocket);
request.mut_dispatch();
}
});
}
#[bench]
fn bench_dynamic(b: &mut Bencher) {
let rocket = rocket();
let client = Client::new(rocket()).unwrap();
// Hold all of the requests we're going to make during the benchmark.
let mut requests = vec![];
requests.push(MockRequest::new(Get, "/abc"));
requests.push(MockRequest::new(Get, "/abcdefg"));
requests.push(MockRequest::new(Get, "/123"));
requests.push(client.get("/abc"));
requests.push(client.get("/abcdefg"));
requests.push(client.get("/123"));
b.iter(|| {
for request in requests.iter_mut() {
request.dispatch_with(&rocket);
request.mut_dispatch();
}
});
}
#[bench]
fn bench_simple_routing(b: &mut Bencher) {
let rocket = rocket();
let client = Client::new(rocket()).unwrap();
// Hold all of the requests we're going to make during the benchmark.
let mut requests = vec![];
for route in rocket.routes() {
let request = MockRequest::new(route.method, route.path.path());
for route in client.rocket().routes() {
let request = client.req(route.method, route.path.path());
requests.push(request);
}
// A few more for the dynamic route.
requests.push(MockRequest::new(Get, "/abc"));
requests.push(MockRequest::new(Get, "/abcdefg"));
requests.push(MockRequest::new(Get, "/123"));
requests.push(client.get("/abc"));
requests.push(client.get("/abcdefg"));
requests.push(client.get("/123"));
b.iter(|| {
for request in requests.iter_mut() {
request.dispatch_with(&rocket);
request.mut_dispatch();
}
});
}

View File

@ -112,6 +112,69 @@ impl Config {
Config::default(env, cwd.as_path().join("Rocket.custom.toml"))
}
/// Returns a builder for `Config` structure where the default parameters
/// are set to those of the development environment. The root configuration
/// directory is set to the current working directory.
///
/// # Errors
///
/// If the current directory cannot be retrieved, a `BadCWD` error is
/// returned.
///
/// # Example
///
/// ```rust
/// use rocket::config::{Config, Environment};
///
/// let mut my_config = Config::development().unwrap();
/// my_config.set_port(1001);
/// ```
pub fn development() -> Result<Config> {
Config::new(Environment::Development)
}
/// Creates a new configuration using the default parameters from the
/// staging environment. The root configuration directory is set to the
/// current working directory.
///
/// # Errors
///
/// If the current directory cannot be retrieved, a `BadCWD` error is
/// returned.
///
/// # Example
///
/// ```rust
/// use rocket::config::{Config, Environment};
///
/// let mut my_config = Config::staging().expect("cwd");
/// my_config.set_port(1001);
/// ```
pub fn staging() -> Result<Config> {
Config::new(Environment::Staging)
}
/// Creates a new configuration using the default parameters from the
/// production environment. The root configuration directory is set to the
/// current working directory.
///
/// # Errors
///
/// If the current directory cannot be retrieved, a `BadCWD` error is
/// returned.
///
/// # Example
///
/// ```rust
/// use rocket::config::{Config, Environment};
///
/// let mut my_config = Config::production().expect("cwd");
/// my_config.set_port(1001);
/// ```
pub fn production() -> Result<Config> {
Config::new(Environment::Production)
}
/// Returns the default configuration for the environment `env` given that
/// the configuration was stored at `config_path`. If `config_path` is not
/// an absolute path, an `Err` of `ConfigError::BadFilePath` is returned.

View File

@ -66,8 +66,7 @@ impl Data {
/// the data in a request.
pub fn open(mut self) -> DataStream {
let buffer = ::std::mem::replace(&mut self.buffer, vec![]);
let empty_stream = Cursor::new(vec![])
.chain(NetStream::Local(Cursor::new(vec![])));
let empty_stream = Cursor::new(vec![]).chain(NetStream::Empty);
// FIXME: Insert a `BufReader` in front of the `NetStream` with capacity
// 4096. We need the new `Chain` methods to get the inner reader to
@ -205,9 +204,9 @@ impl Data {
}
/// This creates a `data` object from a local data source `data`.
#[inline]
pub(crate) fn local(data: Vec<u8>) -> Data {
let empty_stream = Cursor::new(vec![])
.chain(NetStream::Local(Cursor::new(vec![])));
let empty_stream = Cursor::new(vec![]).chain(NetStream::Empty);
Data {
buffer: data,

View File

@ -1,4 +1,4 @@
use std::io::{self, Cursor};
use std::io;
use std::net::{SocketAddr, Shutdown};
use std::time::Duration;
@ -16,7 +16,7 @@ pub enum NetStream {
Http(HttpStream),
#[cfg(feature = "tls")]
Https(HttpsStream),
Local(Cursor<Vec<u8>>)
Empty,
}
impl io::Read for NetStream {
@ -25,9 +25,10 @@ impl io::Read for NetStream {
trace_!("NetStream::read()");
let res = match *self {
Http(ref mut stream) => stream.read(buf),
Local(ref mut stream) => stream.read(buf),
#[cfg(feature = "tls")] Https(ref mut stream) => stream.read(buf)
#[cfg(feature = "tls")] Https(ref mut stream) => stream.read(buf),
Empty => Ok(0),
};
trace_!("NetStream::read() -- complete");
res
}
@ -39,8 +40,8 @@ impl io::Write for NetStream {
trace_!("NetStream::write()");
match *self {
Http(ref mut stream) => stream.write(buf),
Local(ref mut stream) => stream.write(buf),
#[cfg(feature = "tls")] Https(ref mut stream) => stream.write(buf)
#[cfg(feature = "tls")] Https(ref mut stream) => stream.write(buf),
Empty => Ok(0),
}
}
@ -48,8 +49,8 @@ impl io::Write for NetStream {
fn flush(&mut self) -> io::Result<()> {
match *self {
Http(ref mut stream) => stream.flush(),
Local(ref mut stream) => stream.flush(),
#[cfg(feature = "tls")] Https(ref mut stream) => stream.flush()
#[cfg(feature = "tls")] Https(ref mut stream) => stream.flush(),
Empty => Ok(()),
}
}
}
@ -60,7 +61,7 @@ impl NetworkStream for NetStream {
match *self {
Http(ref mut stream) => stream.peer_addr(),
#[cfg(feature = "tls")] Https(ref mut stream) => stream.peer_addr(),
Local(_) => Err(io::Error::from(io::ErrorKind::AddrNotAvailable)),
Empty => Err(io::Error::from(io::ErrorKind::AddrNotAvailable)),
}
}
@ -69,7 +70,7 @@ impl NetworkStream for NetStream {
match *self {
Http(ref stream) => stream.set_read_timeout(dur),
#[cfg(feature = "tls")] Https(ref stream) => stream.set_read_timeout(dur),
Local(_) => Ok(()),
Empty => Ok(()),
}
}
@ -78,7 +79,7 @@ impl NetworkStream for NetStream {
match *self {
Http(ref stream) => stream.set_write_timeout(dur),
#[cfg(feature = "tls")] Https(ref stream) => stream.set_write_timeout(dur),
Local(_) => Ok(()),
Empty => Ok(()),
}
}
@ -87,7 +88,7 @@ impl NetworkStream for NetStream {
match *self {
Http(ref mut stream) => stream.close(how),
#[cfg(feature = "tls")] Https(ref mut stream) => stream.close(how),
Local(_) => Ok(()),
Empty => Ok(()),
}
}
}

View File

@ -1,28 +1,23 @@
use http::Header;
use std::fmt;
use std::cell::RefMut;
pub use cookie::{Cookie, CookieJar, Iter, CookieBuilder, Delta};
pub use cookie::{Cookie, Key, CookieJar};
use cookie::{SameSite, Delta};
use cookie::{PrivateJar, Key};
use http::Header;
impl<'a, 'c> From<&'a Cookie<'c>> for Header<'static> {
fn from(cookie: &Cookie) -> Header<'static> {
Header::new("Set-Cookie", cookie.encoded().to_string())
}
}
#[derive(Debug)]
pub enum Cookies<'a> {
Jarred(RefMut<'a, CookieJar>),
Jarred(RefMut<'a, CookieJar>, &'a Key),
Empty(CookieJar)
}
impl<'a> Cookies<'a> {
pub(crate) fn new(jar: RefMut<'a, CookieJar>) -> Cookies<'a> {
Cookies::Jarred(jar)
#[inline]
pub(crate) fn new(jar: RefMut<'a, CookieJar>, key: &'a Key) -> Cookies<'a> {
Cookies::Jarred(jar, key)
}
#[inline]
pub(crate) fn empty() -> Cookies<'static> {
Cookies::Empty(CookieJar::new())
}
@ -34,41 +29,82 @@ impl<'a> Cookies<'a> {
pub fn get(&self, name: &str) -> Option<&Cookie<'static>> {
match *self {
Cookies::Jarred(ref jar) => jar.get(name),
Cookies::Jarred(ref jar, _) => jar.get(name),
Cookies::Empty(_) => None
}
}
pub fn get_private(&mut self, name: &str) -> Option<Cookie<'static>> {
match *self {
Cookies::Jarred(ref mut jar, key) => jar.private(key).get(name),
Cookies::Empty(_) => None
}
}
pub fn add(&mut self, cookie: Cookie<'static>) {
if let Cookies::Jarred(ref mut jar) = *self {
if let Cookies::Jarred(ref mut jar, _) = *self {
jar.add(cookie)
}
}
pub fn add_private(&mut self, mut cookie: Cookie<'static>) {
cookie.set_http_only(true);
if cookie.path().is_none() {
cookie.set_path("/");
}
if cookie.same_site().is_none() {
cookie.set_same_site(SameSite::Strict);
}
if let Cookies::Jarred(ref mut jar, key) = *self {
jar.private(key).add(cookie)
}
}
pub fn remove(&mut self, cookie: Cookie<'static>) {
if let Cookies::Jarred(ref mut jar) = *self {
if let Cookies::Jarred(ref mut jar, _) = *self {
jar.remove(cookie)
}
}
pub(crate) fn private(&mut self, key: &Key) -> PrivateJar {
match *self {
Cookies::Jarred(ref mut jar) => jar.private(key),
Cookies::Empty(ref mut jar) => jar.private(key)
pub fn remove_private(&mut self, mut cookie: Cookie<'static>) {
if cookie.path().is_none() {
cookie.set_path("/");
}
if let Cookies::Jarred(ref mut jar, key) = *self {
jar.private(key).remove(cookie)
}
}
pub fn iter(&self) -> Iter {
pub fn iter<'s>(&'s self) -> impl Iterator<Item=&'s Cookie<'static>> {
match *self {
Cookies::Jarred(ref jar) => jar.iter(),
Cookies::Jarred(ref jar, _) => jar.iter(),
Cookies::Empty(ref jar) => jar.iter()
}
}
pub(crate) fn delta(&self) -> Delta {
match *self {
Cookies::Jarred(ref jar) => jar.delta(),
Cookies::Jarred(ref jar, _) => jar.delta(),
Cookies::Empty(ref jar) => jar.delta()
}
}
}
impl<'a> fmt::Debug for Cookies<'a> {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match *self {
Cookies::Jarred(ref jar, _) => write!(f, "{:?}", jar),
Cookies::Empty(ref jar) => write!(f, "{:?}", jar)
}
}
}
impl<'a, 'c> From<&'a Cookie<'c>> for Header<'static> {
fn from(cookie: &Cookie) -> Header<'static> {
Header::new("Set-Cookie", cookie.encoded().to_string())
}
}

View File

@ -11,7 +11,6 @@ pub mod uri;
#[macro_use]
mod known_media_types;
mod cookies;
mod session;
mod method;
mod media_type;
mod content_type;
@ -36,5 +35,5 @@ pub use self::header::{Header, HeaderMap};
pub use self::raw_str::RawStr;
pub use self::media_type::MediaType;
pub use self::cookies::*;
pub use self::session::*;
pub use self::cookies::{Cookie, Cookies};
pub(crate) use self::cookies::{Key, CookieJar};

View File

@ -1,75 +0,0 @@
use std::cell::{RefCell, RefMut};
use time::{self, Duration};
use cookie::{Cookie, CookieJar, Delta};
pub use cookie::Key;
use http::{Header, Cookies};
const SESSION_PREFIX: &'static str = "__sess_";
pub struct Session<'a> {
cookies: RefCell<Cookies<'a>>,
key: &'a Key
}
impl<'a> Session<'a> {
#[inline(always)]
pub(crate) fn new(jar: RefMut<'a, CookieJar>, key: &'a Key) -> Session<'a> {
Session { cookies: RefCell::new(Cookies::new(jar)), key: key }
}
#[inline(always)]
pub(crate) fn empty(key: &'a Key) -> Session<'a> {
Session { cookies: RefCell::new(Cookies::empty()), key: key }
}
#[inline(always)]
pub(crate) fn header_for(cookie: &Cookie) -> Header<'static> {
Header::new("Set-Cookie", format!("{}{}", SESSION_PREFIX, cookie))
}
#[inline(always)]
pub(crate) fn parse_cookie(cookie_str: &str) -> Option<Cookie<'static>> {
if !cookie_str.starts_with(SESSION_PREFIX) {
return None;
}
Cookie::parse(&cookie_str[SESSION_PREFIX.len()..]).ok()
.map(|c| c.into_owned())
}
pub fn get(&self, name: &str) -> Option<Cookie<'static>> {
self.cookies.borrow_mut().private(&self.key).get(name)
}
pub fn set(&mut self, mut cookie: Cookie<'static>) {
cookie.set_http_only(true);
if cookie.path().is_none() {
cookie.set_path("/");
}
// TODO: Should this be configurable?
if cookie.max_age().is_none() && cookie.expires().is_none() {
let session_lifetime = Duration::hours(3);
cookie.set_max_age(session_lifetime);
cookie.set_expires(time::now() + session_lifetime);
}
self.cookies.get_mut().private(&self.key).add(cookie)
}
pub fn remove(&mut self, mut cookie: Cookie<'static>) {
if cookie.path().is_none() {
cookie.set_path("/");
}
self.cookies.get_mut().private(&self.key).remove(cookie)
}
#[inline(always)]
pub(crate) fn delta(&mut self) -> Delta {
self.cookies.get_mut().delta()
}
}

View File

@ -117,7 +117,7 @@ extern crate ordermap;
#[cfg(test)] #[macro_use] extern crate lazy_static;
#[doc(hidden)] #[macro_use] pub mod logger;
pub mod testing;
pub mod local;
pub mod http;
pub mod request;
pub mod response;
@ -140,6 +140,7 @@ mod ext;
#[doc(hidden)] pub use codegen::{StaticRouteInfo, StaticCatchInfo};
#[doc(inline)] pub use outcome::Outcome;
#[doc(inline)] pub use data::Data;
#[doc(inline)] pub use config::Config;
pub use router::Route;
pub use request::{Request, State};
pub use error::{Error, LaunchError};

78
lib/src/local/client.rs Normal file
View File

@ -0,0 +1,78 @@
use {Rocket, Request};
use local::LocalRequest;
use http::Method;
use http::uri::URI;
use error::LaunchError;
pub struct Client {
rocket: Rocket,
}
impl Client {
#[inline]
pub fn new(rocket: Rocket) -> Result<Client, LaunchError> {
if let Some(err) = rocket.prelaunch_check() {
return Err(err);
}
Ok(Client {
rocket: rocket,
})
}
#[inline(always)]
pub fn rocket(&self) -> &Rocket {
&self.rocket
}
#[inline(always)]
pub fn req<'c, 'u: 'c, U>(&'c self, method: Method, uri: U) -> LocalRequest<'c>
where U: Into<URI<'u>>
{
let request = Request::new(&self.rocket, method, uri);
LocalRequest::new(&self.rocket, request)
}
#[inline(always)]
pub fn get<'c, 'u: 'c, U: Into<URI<'u>>>(&'c self, uri: U) -> LocalRequest<'c> {
self.req(Method::Get, uri)
}
#[inline(always)]
pub fn put<'c, 'u: 'c, U: Into<URI<'u>>>(&'c self, uri: U) -> LocalRequest<'c> {
self.req(Method::Put, uri)
}
#[inline(always)]
pub fn post<'c, 'u: 'c, U: Into<URI<'u>>>(&'c self, uri: U) -> LocalRequest<'c> {
self.req(Method::Post, uri)
}
#[inline(always)]
pub fn delete<'c, 'u: 'c, U>(&'c self, uri: U) -> LocalRequest<'c>
where U: Into<URI<'u>>
{
self.req(Method::Delete, uri)
}
#[inline(always)]
pub fn options<'c, 'u: 'c, U>(&'c self, uri: U) -> LocalRequest<'c>
where U: Into<URI<'u>>
{
self.req(Method::Options, uri)
}
#[inline(always)]
pub fn head<'c, 'u: 'c, U>(&'c self, uri: U) -> LocalRequest<'c>
where U: Into<URI<'u>>
{
self.req(Method::Head, uri)
}
#[inline(always)]
pub fn patch<'c, 'u: 'c, U>(&'c self, uri: U) -> LocalRequest<'c>
where U: Into<URI<'u>>
{
self.req(Method::Patch, uri)
}
}

107
lib/src/local/mod.rs Normal file
View File

@ -0,0 +1,107 @@
//! 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`] 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::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::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::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::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(plugin)]
//! #![plugin(rocket_codegen)]
//!
//! extern crate rocket;
//!
//! #[get("/")]
//! fn hello() -> &'static str {
//! "Hello, world!"
//! }
//!
//! # fn main() { }
//! #[cfg(test)]
//! mod test {
//! use super::{rocket, hello};
//! use rocket::local::Client;
//!
//! #[test]
//! fn test_hello_world() {
//! // Construct a client to use for dispatching requests.
//! let client = Client::new(rocket::ignite().mount("/", routes![hello]));
//!
//! // Dispatch a request to 'GET /' and validate the response.
//! let mut response = client.get("/").dispatch();
//! assert_eq!(response.body_string(), Some("Hello, world!".into()));
//! }
//! }
//! ```
//!
//! [`Client`]: /rocket/local/struct.Client.html
//! [`LocalRequest`]: /rocket/local/struct.LocalRequest.html
//! [`Rocket`]: /rocket/struct.Rocket.html
//!
mod request;
mod client;
pub use self::request::{LocalResponse, LocalRequest};
pub use self::client::Client;

265
lib/src/local/request.rs Normal file
View File

@ -0,0 +1,265 @@
use std::fmt;
use std::rc::Rc;
use std::mem::transmute;
use std::net::SocketAddr;
use std::ops::{Deref, DerefMut};
use {Rocket, Request, Response, Data};
use http::{Header, Cookie};
pub struct LocalRequest<'c> {
rocket: &'c Rocket,
ptr: *mut Request<'c>,
request: Rc<Request<'c>>,
data: Vec<u8>
}
pub struct LocalResponse<'c> {
_request: Rc<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<'c> LocalRequest<'c> {
#[inline(always)]
pub fn new(rocket: &'c Rocket, request: Request<'c>) -> LocalRequest<'c> {
let mut req = Rc::new(request);
let ptr = Rc::get_mut(&mut req).unwrap() as *mut Request;
LocalRequest { rocket: rocket, ptr: ptr, request: req, data: vec![] }
}
#[inline]
pub fn inner(&self) -> &Request<'c> {
&*self.request
}
#[inline(always)]
fn request(&mut self) -> &mut Request<'c> {
unsafe { &mut *self.ptr }
}
#[inline(always)]
pub fn dispatch(mut self) -> LocalResponse<'c> {
let req = unsafe { transmute(self.request()) };
let response = self.rocket.dispatch(req, Data::local(self.data));
LocalResponse {
_request: self.request,
response: response
}
}
#[inline(always)]
pub fn mut_dispatch(&mut self) -> LocalResponse<'c> {
let data = ::std::mem::replace(&mut self.data, vec![]);
let req = unsafe { transmute(self.request()) };
let response = self.rocket.dispatch(req, Data::local(data));
LocalResponse {
_request: self.request.clone(),
response: response
}
}
#[inline(always)]
pub fn cloned_dispatch(&self) -> LocalResponse<'c> {
let cloned = (*self.request).clone();
let mut req = LocalRequest::new(self.rocket, cloned);
req.data = self.data.clone();
req.dispatch()
}
/// Add a header to this request.
///
/// # Examples
///
/// Add the Content-Type header:
///
/// ```rust
/// use rocket::local::Client;
/// use rocket::http::ContentType;
///
/// # #[allow(unused_variables)]
/// let client = Client::new(rocket::ignite()).unwrap();
/// let req = client.get("/").header(ContentType::JSON);
/// ```
#[inline]
pub fn header<H: Into<Header<'static>>>(mut self, header: H) -> Self {
self.request().add_header(header.into());
self
}
/// Adds a header to this request without consuming `self`.
///
/// # Examples
///
/// Add the Content-Type header:
///
/// ```rust
/// use rocket::local::Client;
/// use rocket::http::ContentType;
///
/// let client = Client::new(rocket::ignite()).unwrap();
/// 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().add_header(header.into());
}
/// Set the remote address of this request.
///
/// # Examples
///
/// Set the remote address to "8.8.8.8:80":
///
/// ```rust
/// use rocket::local::Client;
///
/// let client = Client::new(rocket::ignite()).unwrap();
/// let address = "8.8.8.8:80".parse().unwrap();
/// let req = client.get("/").remote(address);
/// ```
#[inline]
pub fn remote(mut self, address: SocketAddr) -> Self {
self.request().set_remote(address);
self
}
/// Add a cookie to this request.
///
/// # Examples
///
/// Add `user_id` cookie:
///
/// ```rust
/// use rocket::local::Client;
/// use rocket::http::Cookie;
///
/// let client = Client::new(rocket::ignite()).unwrap();
/// # #[allow(unused_variables)]
/// let req = client.get("/")
/// .cookie(Cookie::new("username", "sb"))
/// .cookie(Cookie::new("user_id", format!("{}", 12)));
/// ```
#[inline]
pub fn cookie(self, cookie: Cookie<'static>) -> Self {
self.request.cookies().add(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
///
/// Set the body to be a JSON structure; also sets the Content-Type.
///
/// ```rust
/// use rocket::local::Client;
/// use rocket::http::ContentType;
///
/// let client = Client::new(rocket::ignite()).unwrap();
/// # #[allow(unused_variables)]
/// let req = client.post("/")
/// .header(ContentType::JSON)
/// .body(r#"{ "key": "value", "array": [1, 2, 3], }"#);
/// ```
#[inline]
pub fn body<S: AsRef<[u8]>>(mut self, body: S) -> Self {
self.data = body.as_ref().into();
self
}
}
impl<'c> fmt::Debug for LocalRequest<'c> {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
fmt::Debug::fmt(&self.request, f)
}
}
impl<'c> fmt::Debug for LocalResponse<'c> {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
fmt::Debug::fmt(&self.response, f)
}
}
// fn test() {
// use 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();
// }
// fn test() {
// use 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();
// }
// fn test() {
// use local::Client;
// let rocket = Rocket::ignite();
// let client = Client::new(rocket).unwrap();
// let res = {
// let x = client.get("/").dispatch();
// let y = client.get("/").dispatch();
// };
// let x = client;
// }
// fn test() {
// use 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.set_client(&client2);
// res1
// };
// drop(client1);
// }

View File

@ -6,7 +6,7 @@ use request::Request;
use outcome::{self, IntoOutcome};
use outcome::Outcome::*;
use http::{Status, ContentType, Accept, Method, Cookies, Session};
use http::{Status, ContentType, Accept, Method, Cookies};
use http::uri::URI;
/// Type alias for the `Outcome` of a `FromRequest` conversion.
@ -236,14 +236,6 @@ impl<'a, 'r> FromRequest<'a, 'r> for Cookies<'a> {
}
}
impl<'a, 'r> FromRequest<'a, 'r> for Session<'a> {
type Error = ();
fn from_request(request: &'a Request<'r>) -> Outcome<Self, Self::Error> {
Success(request.session())
}
}
impl<'a, 'r> FromRequest<'a, 'r> for &'a Accept {
type Error = ();

View File

@ -4,32 +4,26 @@ use std::fmt;
use std::str;
use yansi::Paint;
use state::{Container, Storage};
use error::Error;
use super::{FromParam, FromSegments, FromRequest, Outcome};
use rocket::Rocket;
use router::Route;
use config::{Config, Limits};
use http::uri::{URI, Segments};
use http::{Method, Header, HeaderMap, Cookies, Session, CookieJar};
use error::Error;
use http::{Method, Header, HeaderMap, Cookies, CookieJar};
use http::{RawStr, ContentType, Accept, MediaType};
use http::hyper;
struct PresetState<'r> {
// The running Rocket instances configuration.
config: &'r Config,
// The managed state of the running Rocket instance.
state: &'r Container,
}
#[derive(Clone)]
struct RequestState<'r> {
preset: Option<PresetState<'r>>,
config: &'r Config,
state: &'r Container,
params: RefCell<Vec<(usize, usize)>>,
route: Cell<Option<&'r Route>>,
cookies: RefCell<CookieJar>,
session: RefCell<CookieJar>,
accept: Storage<Option<Accept>>,
content_type: Storage<Option<ContentType>>,
}
@ -41,6 +35,7 @@ struct RequestState<'r> {
/// [FromRequest](/rocket/request/trait.FromRequest.html) 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: Method,
uri: URI<'r>,
@ -53,45 +48,45 @@ impl<'r> Request<'r> {
/// Create a new `Request` with the given `method` and `uri`. The `uri`
/// parameter can be of any type that implements `Into<URI>` including
/// `&str` and `String`; it must be a valid absolute URI.
///
/// # Example
///
/// ```rust
/// use rocket::Request;
/// use rocket::http::Method;
///
/// # #[allow(unused_variables)]
/// let request = Request::new(Method::Get, "/uri");
/// ```
#[inline(always)]
pub fn new<'s: 'r, U: Into<URI<'s>>>(method: Method, uri: U) -> Request<'r> {
pub(crate) fn new<'s: 'r, U: Into<URI<'s>>>(rocket: &'r Rocket,
method: Method,
uri: U) -> Request<'r> {
Request {
method: method,
uri: uri.into(),
headers: HeaderMap::new(),
remote: None,
state: RequestState {
preset: None,
config: &rocket.config,
state: &rocket.state,
route: Cell::new(None),
params: RefCell::new(Vec::new()),
cookies: RefCell::new(CookieJar::new()),
session: RefCell::new(CookieJar::new()),
accept: Storage::new(),
content_type: Storage::new(),
}
}
}
#[doc(hidden)]
pub fn example<F: Fn(&mut Request)>(method: Method, uri: &str, f: F) {
let rocket = Rocket::custom(Config::development().unwrap(), true);
let mut request = Request::new(&rocket, method, uri);
f(&mut request);
}
/// Retrieve the method from `self`.
///
/// # Example
///
/// ```rust
/// use rocket::Request;
/// # use rocket::Request;
/// use rocket::http::Method;
///
/// let request = Request::new(Method::Get, "/uri");
/// # Request::example(Method::Get, "/uri", |request| {
/// assert_eq!(request.method(), Method::Get);
/// # });
/// ```
#[inline(always)]
pub fn method(&self) -> Method {
@ -103,14 +98,15 @@ impl<'r> Request<'r> {
/// # Example
///
/// ```rust
/// use rocket::Request;
/// # use rocket::Request;
/// use rocket::http::Method;
///
/// let mut request = Request::new(Method::Get, "/uri");
/// # Request::example(Method::Get, "/uri", |request| {
/// assert_eq!(request.method(), Method::Get);
///
/// request.set_method(Method::Post);
/// assert_eq!(request.method(), Method::Post);
/// # });
/// ```
#[inline(always)]
pub fn set_method(&mut self, method: Method) {
@ -122,11 +118,11 @@ impl<'r> Request<'r> {
/// # Example
///
/// ```rust
/// use rocket::Request;
/// use rocket::http::Method;
///
/// let request = Request::new(Method::Get, "/uri");
/// # use rocket::Request;
/// # use rocket::http::Method;
/// # Request::example(Method::Get, "/uri", |request| {
/// assert_eq!(request.uri().as_str(), "/uri");
/// # });
/// ```
#[inline(always)]
pub fn uri(&self) -> &URI {
@ -140,13 +136,12 @@ impl<'r> Request<'r> {
/// # Example
///
/// ```rust
/// use rocket::Request;
/// use rocket::http::Method;
///
/// let mut request = Request::new(Method::Get, "/uri");
///
/// # use rocket::Request;
/// # use rocket::http::Method;
/// # Request::example(Method::Get, "/uri", |mut request| {
/// request.set_uri("/hello/Sergio?type=greeting");
/// assert_eq!(request.uri().as_str(), "/hello/Sergio?type=greeting");
/// # });
/// ```
#[inline(always)]
pub fn set_uri<'u: 'r, U: Into<URI<'u>>>(&mut self, uri: U) {
@ -161,11 +156,11 @@ impl<'r> Request<'r> {
/// # Example
///
/// ```rust
/// use rocket::Request;
/// use rocket::http::Method;
///
/// let request = Request::new(Method::Get, "/uri");
/// # use rocket::Request;
/// # use rocket::http::Method;
/// # Request::example(Method::Get, "/uri", |request| {
/// assert!(request.remote().is_none());
/// # });
/// ```
#[inline(always)]
pub fn remote(&self) -> Option<SocketAddr> {
@ -179,17 +174,17 @@ impl<'r> Request<'r> {
/// Set the remote address to be 127.0.0.1:8000:
///
/// ```rust
/// use rocket::Request;
/// use rocket::http::Method;
/// # use rocket::Request;
/// # use rocket::http::Method;
/// use std::net::{SocketAddr, IpAddr, Ipv4Addr};
///
/// let mut request = Request::new(Method::Get, "/uri");
///
/// # Request::example(Method::Get, "/uri", |mut request| {
/// let (ip, port) = (IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)), 8000);
/// let localhost = SocketAddr::new(ip, port);
/// request.set_remote(localhost);
///
/// assert_eq!(request.remote(), Some(localhost));
/// # });
/// ```
#[inline(always)]
pub fn set_remote(&mut self, address: SocketAddr) {
@ -202,12 +197,12 @@ impl<'r> Request<'r> {
/// # Example
///
/// ```rust
/// use rocket::Request;
/// use rocket::http::Method;
///
/// let request = Request::new(Method::Get, "/uri");
/// # use rocket::Request;
/// # use rocket::http::Method;
/// # Request::example(Method::Get, "/uri", |request| {
/// let header_map = request.headers();
/// assert!(header_map.is_empty());
/// # });
/// ```
#[inline(always)]
pub fn headers(&self) -> &HeaderMap<'r> {
@ -219,18 +214,20 @@ impl<'r> Request<'r> {
/// # Example
///
/// ```rust
/// use rocket::Request;
/// use rocket::http::{Method, ContentType};
/// # use rocket::Request;
/// # use rocket::http::Method;
/// use rocket::http::ContentType;
///
/// let mut request = Request::new(Method::Get, "/uri");
/// # Request::example(Method::Get, "/uri", |mut request| {
/// assert!(request.headers().is_empty());
///
/// request.add_header(ContentType::HTML);
/// assert!(request.headers().contains("Content-Type"));
/// assert_eq!(request.headers().len(), 1);
/// # });
/// ```
#[inline(always)]
pub fn add_header<H: Into<Header<'r>>>(&mut self, header: H) {
pub fn add_header<'h: 'r, H: Into<Header<'h>>>(&mut self, header: H) {
self.headers.add(header.into());
}
@ -240,10 +237,11 @@ impl<'r> Request<'r> {
/// # Example
///
/// ```rust
/// use rocket::Request;
/// use rocket::http::{Method, ContentType};
/// # use rocket::Request;
/// # use rocket::http::Method;
/// use rocket::http::ContentType;
///
/// let mut request = Request::new(Method::Get, "/uri");
/// # Request::example(Method::Get, "/uri", |mut request| {
/// assert!(request.headers().is_empty());
///
/// request.add_header(ContentType::Any);
@ -251,13 +249,14 @@ impl<'r> Request<'r> {
///
/// request.replace_header(ContentType::PNG);
/// assert_eq!(request.headers().get_one("Content-Type"), Some("image/png"));
/// # });
/// ```
#[inline(always)]
pub fn replace_header<H: Into<Header<'r>>>(&mut self, header: H) {
pub fn replace_header<'h: 'r, H: Into<Header<'h>>>(&mut self, header: H) {
self.headers.replace(header.into());
}
/// Returns a borrow to the cookies in `self`.
/// Returns a wrapped borrow to the cookies in `self`.
///
/// Note that `Cookies` implements internal mutability, so this method
/// allows you to get _and_ set cookies in `self`.
@ -267,17 +266,18 @@ impl<'r> Request<'r> {
/// Add a new cookie to a request's cookies:
///
/// ```rust
/// use rocket::Request;
/// use rocket::http::{Cookie, Method};
/// # use rocket::Request;
/// # use rocket::http::Method;
/// use rocket::http::Cookie;
///
/// let request = Request::new(Method::Get, "/uri");
/// # Request::example(Method::Get, "/uri", |mut request| {
/// request.cookies().add(Cookie::new("key", "val"));
/// request.cookies().add(Cookie::new("ans", format!("life: {}", 38 + 4)));
/// # });
/// ```
#[inline]
pub fn cookies(&self) -> Cookies {
match self.state.cookies.try_borrow_mut() {
Ok(jar) => Cookies::new(jar),
Ok(jar) => Cookies::new(jar, self.state.config.secret_key()),
Err(_) => {
error_!("Multiple `Cookies` instances are active at once.");
info_!("An instance of `Cookies` must be dropped before another \
@ -288,21 +288,6 @@ impl<'r> Request<'r> {
}
}
#[inline]
pub fn session(&self) -> Session {
let key = self.preset().config.secret_key();
match self.state.session.try_borrow_mut() {
Ok(jar) => Session::new(jar, key),
Err(_) => {
error_!("Multiple `Session` instances are active at once.");
info_!("An instance of `Session` must be dropped before another \
can be retrieved.");
warn_!("The retrieved `Session` instance will be empty.");
Session::empty(key)
}
}
}
/// Returns the Content-Type header of `self`. If the header is not present,
/// returns `None`. The Content-Type header is cached after the first call
/// to this function. As a result, subsequent calls will always return the
@ -311,20 +296,22 @@ impl<'r> Request<'r> {
/// # Example
///
/// ```rust
/// use rocket::Request;
/// use rocket::http::{Method, ContentType};
///
/// let mut request = Request::new(Method::Get, "/uri");
/// # use rocket::Request;
/// # use rocket::http::Method;
/// # Request::example(Method::Get, "/uri", |mut request| {
/// assert_eq!(request.content_type(), None);
/// # });
/// ```
///
/// ```rust
/// use rocket::Request;
/// use rocket::http::{Method, ContentType};
/// # use rocket::Request;
/// # use rocket::http::Method;
/// use rocket::http::ContentType;
///
/// let mut request = Request::new(Method::Get, "/uri");
/// # Request::example(Method::Get, "/uri", |mut request| {
/// request.add_header(ContentType::JSON);
/// assert_eq!(request.content_type(), Some(&ContentType::JSON));
/// # });
/// ```
#[inline(always)]
pub fn content_type(&self) -> Option<&ContentType> {
@ -361,7 +348,7 @@ impl<'r> Request<'r> {
/// Get the limits.
pub fn limits(&self) -> &'r Limits {
&self.preset().config.limits
&self.state.config.limits
}
/// Get the current route, if any.
@ -463,17 +450,6 @@ impl<'r> Request<'r> {
Some(Segments(&path[i..j]))
}
#[inline(always)]
fn preset(&self) -> &PresetState<'r> {
match self.state.preset {
Some(ref state) => state,
None => {
error_!("Internal Rocket error: preset state is unset!");
panic!("Please report this error to the GitHub issue tracker.");
}
}
}
/// Set `self`'s parameters given that the route used to reach this request
/// was `route`. This should only be used internally by `Rocket` as improper
/// use may result in out of bounds indexing.
@ -490,12 +466,6 @@ impl<'r> Request<'r> {
self.state.cookies = RefCell::new(jar);
}
/// Replace all of the session cookie in `self` with those in `jar`.
#[inline]
pub(crate) fn set_session(&mut self, jar: CookieJar) {
self.state.session = RefCell::new(jar);
}
/// Try to derive some guarded value from `self`.
#[inline(always)]
pub fn guard<'a, T: FromRequest<'a, 'r>>(&'a self) -> Outcome<T, T::Error> {
@ -505,17 +475,12 @@ impl<'r> Request<'r> {
/// Get the managed state T, if it exists. For internal use only!
#[inline(always)]
pub(crate) fn get_state<T: Send + Sync + 'static>(&self) -> Option<&'r T> {
self.preset().state.try_get()
}
/// Set the precomputed state. For internal use only!
#[inline(always)]
pub(crate) fn set_preset(&mut self, config: &'r Config, state: &'r Container) {
self.state.preset = Some(PresetState { config, state });
self.state.state.try_get()
}
/// Convert from Hyper types into a Rocket Request.
pub(crate) fn from_hyp(h_method: hyper::Method,
pub(crate) fn from_hyp(rocket: &'r Rocket,
h_method: hyper::Method,
h_headers: hyper::header::Headers,
h_uri: hyper::RequestUri,
h_addr: SocketAddr,
@ -533,13 +498,12 @@ impl<'r> Request<'r> {
};
// Construct the request object.
let mut request = Request::new(method, uri);
let mut request = Request::new(rocket, method, uri);
request.set_remote(h_addr);
// Set the request cookies, if they exist.
if let Some(cookie_headers) = h_headers.get_raw("Cookie") {
let mut cookie_jar = CookieJar::new();
let mut session_jar = CookieJar::new();
for header in cookie_headers {
let raw_str = match ::std::str::from_utf8(header) {
Ok(string) => string,
@ -547,16 +511,13 @@ impl<'r> Request<'r> {
};
for cookie_str in raw_str.split(";").map(|s| s.trim()) {
if let Some(cookie) = Session::parse_cookie(cookie_str) {
session_jar.add_original(cookie);
} else if let Some(cookie) = Cookies::parse_cookie(cookie_str) {
if let Some(cookie) = Cookies::parse_cookie(cookie_str) {
cookie_jar.add_original(cookie);
}
}
}
request.set_cookies(cookie_jar);
request.set_session(session_jar);
}
// Set the rest of the headers.
@ -575,6 +536,17 @@ impl<'r> Request<'r> {
}
}
impl<'r> fmt::Debug for Request<'r> {
fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
fmt.debug_struct("Request")
.field("method", &self.method)
.field("uri", &self.uri)
.field("headers", &self.headers())
.field("remote", &self.remote())
.finish()
}
}
impl<'r> fmt::Display for Request<'r> {
/// Pretty prints a Request. This is primarily used by Rocket's logging
/// infrastructure.

View File

@ -1,7 +1,7 @@
use std::net::{IpAddr, Ipv4Addr, SocketAddr};
use std::collections::HashMap;
use Request;
use {Rocket, Request, Config};
use http::hyper;
macro_rules! assert_headers {
@ -20,7 +20,9 @@ macro_rules! assert_headers {
$(expected.entry($key).or_insert(vec![]).append(&mut vec![$($value),+]);)+
// Dispatch the request and check that the headers are what we expect.
let req = Request::from_hyp(h_method, h_headers, h_uri, h_addr).unwrap();
let config = Config::development().unwrap();
let r = Rocket::custom(config, true);
let req = Request::from_hyp(&r, h_method, h_headers, h_uri, h_addr).unwrap();
let actual_headers = req.headers();
for (key, values) in expected.iter() {
let actual: Vec<_> = actual_headers.get(key).collect();

View File

@ -903,7 +903,29 @@ impl<'r> Response<'r> {
/// ```
#[inline(always)]
pub fn body_string(&mut self) -> Option<String> {
self.take_body().and_then(|b| b.into_string())
self.take_body().and_then(Body::into_string)
}
/// Consumes `self's` body and reads it 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
/// use std::io::Cursor;
/// use rocket::Response;
///
/// let mut response = Response::new();
/// assert!(response.body().is_none());
///
/// response.set_sized_body(Cursor::new("hi!"));
/// assert_eq!(response.body_bytes(), Some(vec![0x68, 0x69, 0x21]));
/// assert!(response.body().is_none());
/// ```
#[inline(always)]
pub fn body_bytes(&mut self) -> Option<Vec<u8>> {
self.take_body().and_then(Body::into_bytes)
}
/// Moves the body of `self` out and returns it, if there is one, leaving no

View File

@ -21,19 +21,19 @@ use outcome::Outcome;
use error::{Error, LaunchError, LaunchErrorKind};
use fairing::{Fairing, Fairings};
use http::{Method, Status, Header, Session};
use http::{Method, Status, Header};
use http::hyper::{self, header};
use http::uri::URI;
/// The main `Rocket` type: used to mount routes and catchers and launch the
/// application.
pub struct Rocket {
config: Config,
pub(crate) config: Config,
router: Router,
default_catchers: HashMap<u16, Catcher>,
catchers: HashMap<u16, Catcher>,
state: Container,
fairings: Fairings
pub(crate) state: Container,
fairings: Fairings,
}
#[doc(hidden)]
@ -50,11 +50,12 @@ impl hyper::Handler for Rocket {
let (h_addr, h_method, h_headers, h_uri, _, h_body) = hyp_req.deconstruct();
// Convert the Hyper request into a Rocket request.
let mut req = match Request::from_hyp(h_method, h_headers, h_uri, h_addr) {
let req_res = Request::from_hyp(self, h_method, h_headers, h_uri, h_addr);
let mut req = match req_res {
Ok(req) => req,
Err(e) => {
error!("Bad incoming request: {}", e);
let dummy = Request::new(Method::Get, URI::new("<unknown>"));
let dummy = Request::new(self, Method::Get, URI::new("<unknown>"));
let r = self.handle_error(Status::InternalServerError, &dummy);
return self.issue_response(r, res);
}
@ -209,16 +210,12 @@ impl Rocket {
}
}
// TODO: Explain this `UnsafeCell` business at a macro level.
#[inline]
pub(crate) fn dispatch<'s, 'r>(&'s self,
request: &'r mut Request<'s>,
data: Data) -> Response<'r> {
info!("{}:", request);
// Inform the request about all of the precomputed state.
request.set_preset(&self.config, &self.state);
// Do a bit of preprocessing before routing; run the attached fairings.
self.preprocess_request(request, &data);
self.fairings.handle_request(request, &data);
@ -226,16 +223,11 @@ impl Rocket {
// Route the request to get a response.
let mut response = match self.route(request, data) {
Outcome::Success(mut response) => {
// A user's route responded! Set the regular cookies.
// A user's route responded! Set the cookies.
for cookie in request.cookies().delta() {
response.adjoin_header(cookie);
}
// And now the session cookies.
for cookie in request.session().delta() {
response.adjoin_header(Session::header_for(cookie));
}
response
}
Outcome::Forward(data) => {

View File

@ -131,6 +131,8 @@ mod tests {
use std::str::FromStr;
use super::Collider;
use rocket::Rocket;
use config::Config;
use request::Request;
use data::Data;
use handler::Outcome;
@ -368,7 +370,8 @@ mod tests {
fn req_route_mt_collide<S1, S2>(m: Method, mt1: S1, mt2: S2) -> bool
where S1: Into<Option<&'static str>>, S2: Into<Option<&'static str>>
{
let mut req = Request::new(m, "/");
let rocket = Rocket::custom(Config::development().unwrap(), true);
let mut req = Request::new(&rocket, m, "/");
if let Some(mt_str) = mt1.into() {
if m.supports_payload() {
req.replace_header(mt_str.parse::<ContentType>().unwrap());
@ -425,7 +428,8 @@ mod tests {
}
fn req_route_path_collide(a: &'static str, b: &'static str) -> bool {
let req = Request::new(Get, a.to_string());
let rocket = Rocket::custom(Config::development().unwrap(), true);
let req = Request::new(&rocket, Get, a.to_string());
let route = Route::ranked(0, Get, b.to_string(), dummy_handler);
route.collides_with(&req)
}

View File

@ -70,6 +70,8 @@ impl Router {
mod test {
use super::{Router, Route};
use rocket::Rocket;
use config::Config;
use http::Method;
use http::Method::*;
use http::uri::URI;
@ -159,7 +161,8 @@ mod test {
}
fn route<'a>(router: &'a Router, method: Method, uri: &str) -> Option<&'a Route> {
let request = Request::new(method, URI::new(uri));
let rocket = Rocket::custom(Config::development().unwrap(), true);
let request = Request::new(&rocket, method, URI::new(uri));
let matches = router.route(&request);
if matches.len() > 0 {
Some(matches[0])
@ -169,7 +172,8 @@ mod test {
}
fn matches<'a>(router: &'a Router, method: Method, uri: &str) -> Vec<&'a Route> {
let request = Request::new(method, URI::new(uri));
let rocket = Rocket::custom(Config::development().unwrap(), true);
let request = Request::new(&rocket, method, URI::new(uri));
router.route(&request)
}

View File

@ -1,274 +0,0 @@
//! A tiny module for testing Rocket applications.
//!
//! # Usage
//!
//! The testing methadology is simple:
//!
//! 1. Construct a `Rocket` instance.
//! 2. Construct a request.
//! 3. Dispatch the request using the Rocket instance.
//! 4. Inspect, validate, and verify the response.
//!
//! ## Construct a `Rocket` Instance
//!
//! Constructing a `Rocket` instance for testing is identical to constructing
//! one for launching, except you should not call the `launch` method. That is,
//! use `rocket::ignite`, then mount routes and catchers. That's it!
//!
//! ## Construct a (Mock)Request
//!
//! The [MockRequest](struct.MockRequest.html) object enables the creation of an
//! HTTP request without using any networking. A `MockRequest` object is
//! constructed using the builder pattern. For example, the following code
//! builds a request for submitting a login form with three fields:
//!
//! ```rust
//! use rocket::http::Method::*;
//! use rocket::http::ContentType;
//! use rocket::testing::MockRequest;
//!
//! let (username, password, age) = ("user", "password", 32);
//! MockRequest::new(Post, "/login")
//! .header(ContentType::Form)
//! .body(&format!("username={}&password={}&age={}", username, password, age));
//! ```
//!
//! ## Dispatch and Validate
//!
//! Finally, requests can be dispatched using the
//! [dispatch_with](struct.MockRequest.html#method.dispatch_with) method on the
//! contructed `MockRequest` instance. The method returns the body of the
//! response. At present, the API does not allow for headers in the response to
//! be examined.
//!
//! # Example
//!
//! The following is an example of a complete application with testing.
//!
//! ```rust
//! #![feature(plugin)]
//! #![plugin(rocket_codegen)]
//!
//! extern crate rocket;
//!
//! #[get("/")]
//! fn hello() -> &'static str {
//! "Hello, world!"
//! }
//!
//! # fn main() { }
//! #[cfg(test)]
//! mod test {
//! use super::rocket;
//! use rocket::testing::MockRequest;
//! use rocket::http::Method::*;
//!
//! #[test]
//! fn test_hello_world() {
//! let rocket = rocket::ignite().mount("/", routes![super::hello]);
//! let mut req = MockRequest::new(Get, "/");
//! let mut response = req.dispatch_with(&rocket);
//!
//! // Check that the body contains the string we expect.
//! assert_eq!(response.body_string(), Some("Hello, world!".into()));
//! }
//! }
//! ```
use ::{Rocket, Request, Response, Data};
use error::LaunchError;
use http::{Method, Status, Header, Cookie};
use std::net::SocketAddr;
/// A type for mocking requests for testing Rocket applications.
pub struct MockRequest<'r> {
prechecked: Option<&'r Rocket>,
request: Request<'r>,
data: Data
}
impl<'r> MockRequest<'r> {
/// Constructs a new mocked request with the given `method` and `uri`.
#[inline]
pub fn new<S: AsRef<str>>(method: Method, uri: S) -> Self {
MockRequest {
prechecked: None,
request: Request::new(method, uri.as_ref().to_string()),
data: Data::local(vec![])
}
}
/// Add a header to this request.
///
/// # Examples
///
/// Add the Content-Type header:
///
/// ```rust
/// use rocket::http::Method::*;
/// use rocket::testing::MockRequest;
/// use rocket::http::ContentType;
///
/// # #[allow(unused_variables)]
/// let req = MockRequest::new(Get, "/").header(ContentType::JSON);
/// ```
#[inline]
pub fn header<H: Into<Header<'static>>>(mut self, header: H) -> Self {
self.request.add_header(header.into());
self
}
/// Adds a header to this request without consuming `self`.
///
/// # Examples
///
/// Add the Content-Type header:
///
/// ```rust
/// use rocket::http::Method::*;
/// use rocket::testing::MockRequest;
/// use rocket::http::ContentType;
///
/// let mut req = MockRequest::new(Get, "/");
/// req.add_header(ContentType::JSON);
/// ```
#[inline]
pub fn add_header<H: Into<Header<'static>>>(&mut self, header: H) {
self.request.add_header(header.into());
}
/// Set the remote address of this request.
///
/// # Examples
///
/// Set the remote address to "8.8.8.8:80":
///
/// ```rust
/// use rocket::http::Method::*;
/// use rocket::testing::MockRequest;
///
/// let address = "8.8.8.8:80".parse().unwrap();
/// # #[allow(unused_variables)]
/// let req = MockRequest::new(Get, "/").remote(address);
/// ```
#[inline]
pub fn remote(mut self, address: SocketAddr) -> Self {
self.request.set_remote(address);
self
}
/// Add a cookie to this request.
///
/// # Examples
///
/// Add `user_id` cookie:
///
/// ```rust
/// use rocket::http::Method::*;
/// use rocket::testing::MockRequest;
/// use rocket::http::Cookie;
///
/// # #[allow(unused_variables)]
/// let req = MockRequest::new(Get, "/")
/// .cookie(Cookie::new("username", "sb"))
/// .cookie(Cookie::new("user_id", format!("{}", 12)));
/// ```
#[inline]
pub fn cookie(self, cookie: Cookie<'static>) -> Self {
self.request.cookies().add(cookie);
self
}
/// Set the body (data) of the request.
///
/// # Examples
///
/// Set the body to be a JSON structure; also sets the Content-Type.
///
/// ```rust
/// use rocket::http::Method::*;
/// use rocket::testing::MockRequest;
/// use rocket::http::ContentType;
///
/// # #[allow(unused_variables)]
/// let req = MockRequest::new(Post, "/")
/// .header(ContentType::JSON)
/// .body(r#"{ "key": "value", "array": [1, 2, 3], }"#);
/// ```
#[inline]
pub fn body<S: AsRef<[u8]>>(mut self, body: S) -> Self {
self.data = Data::local(body.as_ref().into());
self
}
/// Returns `Some` if there an error checking `rocket`. Returns `None` if
/// there's no error and dispatching can continue.
fn precheck(&mut self, rocket: &'r Rocket) -> Option<LaunchError> {
// Check if we've already prechecked some `Rocket` instance.
if let Some(r) = self.prechecked {
// Check if the one we've prechecked is indeed `rocket`. This does a
// straight pointer comparison. If they're the same, then we know
// that the instance must not have changed since we kept an
// immutable borrow to it from the precheck.
if (r as *const Rocket) == (rocket as *const Rocket) {
return None
}
}
if let Some(err) = rocket.prelaunch_check() {
return Some(err);
}
self.prechecked = Some(rocket);
None
}
/// Dispatch this request using a given instance of Rocket.
///
/// It is possible that the supplied `rocket` instance contains malformed
/// input such as colliding or invalid routes or failed fairings. When this
/// is the case, the returned `Response` will contain a status of
/// `InternalServerError`, and the body will contain the error that
/// occurred. In all other cases, the returned `Response` will be that of
/// the application.
///
/// # Examples
///
/// Dispatch to a Rocket instance with the `"Hello, world!"` example
/// mounted:
///
/// ```rust
/// # #![feature(plugin)]
/// # #![plugin(rocket_codegen)]
/// # extern crate rocket;
/// #
/// #[get("/")]
/// fn hello() -> &'static str {
/// "Hello, world!"
/// }
///
/// use rocket::testing::MockRequest;
/// use rocket::http::Method::*;
///
/// # fn main() {
/// let rocket = rocket::ignite().mount("/", routes![hello]);
/// let mut req = MockRequest::new(Get, "/");
/// let mut response = req.dispatch_with(&rocket);
///
/// assert_eq!(response.body_string(), Some("Hello, world!".into()));
/// # }
/// ```
#[inline]
pub fn dispatch_with<'s>(&'s mut self, rocket: &'r Rocket) -> Response<'s> {
if let Some(err) = self.precheck(rocket) {
return Response::build()
.status(Status::InternalServerError)
.sized_body(::std::io::Cursor::new(err.to_string()))
.finalize()
}
let data = ::std::mem::replace(&mut self.data, Data::local(vec![]));
rocket.dispatch(&mut self.request, data)
}
}

View File

@ -18,31 +18,28 @@ fn bug(form_data: Form<FormData>) -> &'static str {
mod tests {
use super::*;
use rocket::testing::MockRequest;
use rocket::http::Method::*;
use rocket::local::Client;
use rocket::http::{Status, ContentType};
#[test]
fn method_eval() {
let rocket = rocket::ignite().mount("/", routes![bug]);
let mut req = MockRequest::new(Post, "/")
let client = Client::new(rocket::ignite().mount("/", routes![bug])).unwrap();
let mut response = client.post("/")
.header(ContentType::Form)
.body("_method=patch&form_data=Form+data");
.body("_method=patch&form_data=Form+data")
.dispatch();
let mut response = req.dispatch_with(&rocket);
assert_eq!(response.body_string(), Some("OK".into()));
}
#[test]
fn get_passes_through() {
let rocket = rocket::ignite().mount("/", routes![bug]);
let mut req = MockRequest::new(Get, "/")
let client = Client::new(rocket::ignite().mount("/", routes![bug])).unwrap();
let response = client.get("/")
.header(ContentType::Form)
.body("_method=patch&form_data=Form+data");
.body("_method=patch&form_data=Form+data")
.dispatch();
let response = req.dispatch_with(&rocket);
assert_eq!(response.status(), Status::NotFound);
}
}

View File

@ -17,18 +17,17 @@ fn bug(form_data: Form<FormData>) -> String {
mod tests {
use super::*;
use rocket::testing::MockRequest;
use rocket::http::Method::*;
use rocket::local::Client;
use rocket::http::ContentType;
use rocket::http::Status;
fn check_decoding(raw: &str, decoded: &str) {
let rocket = rocket::ignite().mount("/", routes![bug]);
let mut req = MockRequest::new(Post, "/")
let client = Client::new(rocket::ignite().mount("/", routes![bug])).unwrap();
let mut response = client.post("/")
.header(ContentType::Form)
.body(format!("form_data={}", raw));
.body(format!("form_data={}", raw))
.dispatch();
let mut response = req.dispatch_with(&rocket);
assert_eq!(response.status(), Status::Ok);
assert_eq!(Some(decoded.to_string()), response.body_string());
}

View File

@ -24,8 +24,7 @@ mod tests {
use super::*;
use rocket::Route;
use rocket::testing::MockRequest;
use rocket::http::Method::*;
use rocket::local::Client;
use rocket::http::{Status, ContentType};
use rocket::response::Body;
@ -35,12 +34,10 @@ mod tests {
#[test]
fn auto_head() {
let rocket = rocket::ignite().mount("/", routes());
let mut req = MockRequest::new(Head, "/");
let mut response = req.dispatch_with(&rocket);
let client = Client::new(rocket::ignite().mount("/", routes())).unwrap();
let mut response = client.head("/").dispatch();
assert_eq!(response.status(), Status::Ok);
if let Some(body) = response.body() {
match body {
Body::Sized(_, n) => assert_eq!(n, "Hello, world!".len() as u64),
@ -49,27 +46,23 @@ mod tests {
assert_eq!(body.into_string(), Some("".to_string()));
} else {
panic!("Expected an empty body!")
panic!("Expected a non-empty body!")
}
let content_type: Vec<_> = response.headers().get("Content-Type").collect();
assert_eq!(content_type, vec![ContentType::Plain.to_string()]);
let mut req = MockRequest::new(Head, "/empty");
let response = req.dispatch_with(&rocket);
let response = client.head("empty").dispatch();
assert_eq!(response.status(), Status::NoContent);
}
#[test]
fn user_head() {
let rocket = rocket::ignite().mount("/", routes());
let mut req = MockRequest::new(Head, "/other");
let response = req.dispatch_with(&rocket);
assert_eq!(response.status(), Status::Ok);
let client = Client::new(rocket::ignite().mount("/", routes())).unwrap();
let response = client.head("/other").dispatch();
let content_type: Vec<_> = response.headers().get("Content-Type").collect();
assert_eq!(response.status(), Status::Ok);
assert_eq!(content_type, vec![ContentType::JSON.to_string()]);
}
}

View File

@ -18,8 +18,7 @@ fn index(form: Form<Simple>) -> String {
mod limits_tests {
use rocket;
use rocket::config::{Environment, Config, Limits};
use rocket::testing::MockRequest;
use rocket::http::Method::*;
use rocket::local::Client;
use rocket::http::{Status, ContentType};
fn rocket_with_forms_limit(limit: u64) -> rocket::Rocket {
@ -32,45 +31,45 @@ mod limits_tests {
#[test]
fn large_enough() {
let rocket = rocket_with_forms_limit(128);
let mut req = MockRequest::new(Post, "/")
let client = Client::new(rocket_with_forms_limit(128)).unwrap();
let mut response = client.post("/")
.body("value=Hello+world")
.header(ContentType::Form);
.header(ContentType::Form)
.dispatch();
let mut response = req.dispatch_with(&rocket);
assert_eq!(response.body_string(), Some("Hello world".into()));
}
#[test]
fn just_large_enough() {
let rocket = rocket_with_forms_limit(17);
let mut req = MockRequest::new(Post, "/")
let client = Client::new(rocket_with_forms_limit(17)).unwrap();
let mut response = client.post("/")
.body("value=Hello+world")
.header(ContentType::Form);
.header(ContentType::Form)
.dispatch();
let mut response = req.dispatch_with(&rocket);
assert_eq!(response.body_string(), Some("Hello world".into()));
}
#[test]
fn much_too_small() {
let rocket = rocket_with_forms_limit(4);
let mut req = MockRequest::new(Post, "/")
let client = Client::new(rocket_with_forms_limit(4)).unwrap();
let response = client.post("/")
.body("value=Hello+world")
.header(ContentType::Form);
.header(ContentType::Form)
.dispatch();
let response = req.dispatch_with(&rocket);
assert_eq!(response.status(), Status::BadRequest);
}
#[test]
fn contracted() {
let rocket = rocket_with_forms_limit(10);
let mut req = MockRequest::new(Post, "/")
let client = Client::new(rocket_with_forms_limit(10)).unwrap();
let mut response = client.post("/")
.body("value=Hello+world")
.header(ContentType::Form);
.header(ContentType::Form)
.dispatch();
let mut response = req.dispatch_with(&rocket);
assert_eq!(response.body_string(), Some("Hell".into()));
}
}

View File

@ -27,8 +27,7 @@ mod tests {
use super::*;
use rocket::Rocket;
use rocket::testing::MockRequest;
use rocket::http::Method::*;
use rocket::local::Client;
use rocket::http::{Status, ContentType};
fn rocket() -> Rocket {
@ -39,14 +38,14 @@ mod tests {
macro_rules! check_dispatch {
($mount:expr, $ct:expr, $body:expr) => (
let rocket = rocket();
let mut req = MockRequest::new(Post, $mount);
let client = Client::new(rocket()).unwrap();
let mut req = client.post($mount);
let ct: Option<ContentType> = $ct;
if let Some(ct) = ct {
req.add_header(ct);
}
let mut response = req.dispatch_with(&rocket);
let mut response = req.dispatch();
let body_str = response.body_string();
let body: Option<&'static str> = $body;
match body {

View File

@ -20,27 +20,24 @@ fn second() -> &'static str {
mod tests {
use super::*;
use rocket::Rocket;
use rocket::testing::MockRequest;
use rocket::http::Method::*;
use rocket::local::Client;
fn assert_no_collision(rocket: &Rocket) {
let mut req = MockRequest::new(Get, "/?field=query");
let mut response = req.dispatch_with(&rocket);
fn assert_no_collision(rocket: Rocket) {
let client = Client::new(rocket).unwrap();
let mut response = client.get("/?field=query").dispatch();
assert_eq!(response.body_string(), Some("query".into()));
let mut req = MockRequest::new(Get, "/");
let mut response = req.dispatch_with(&rocket);
let mut response = client.get("/").dispatch();
assert_eq!(response.body_string(), Some("no query".into()));
}
#[test]
fn check_query_collisions() {
let rocket = rocket::ignite().mount("/", routes![first, second]);
assert_no_collision(&rocket);
assert_no_collision(rocket);
let rocket = rocket::ignite().mount("/", routes![second, first]);
assert_no_collision(&rocket);
assert_no_collision(rocket);
}
}

View File

@ -12,15 +12,13 @@ fn not_found() -> Redirect {
mod tests {
use super::*;
use rocket::testing::MockRequest;
use rocket::http::Method::*;
use rocket::local::Client;
use rocket::http::Status;
#[test]
fn error_catcher_redirect() {
let rocket = rocket::ignite().catch(errors![not_found]);
let mut req = MockRequest::new(Get, "/unknown");
let response = req.dispatch_with(&rocket);
let client = Client::new(rocket::ignite().catch(errors![not_found])).unwrap();
let response = client.get("/unknown").dispatch();
println!("Response:\n{:?}", response);
let location: Vec<_> = response.headers().get("location").collect();

View File

@ -12,8 +12,7 @@ fn get_ip(remote: SocketAddr) -> String {
mod remote_rewrite_tests {
use super::*;
use rocket::testing::MockRequest;
use rocket::http::Method::*;
use rocket::local::Client;
use rocket::http::{Header, Status};
use std::net::SocketAddr;
@ -21,21 +20,19 @@ mod remote_rewrite_tests {
const KNOWN_IP: &'static str = "127.0.0.1:8000";
fn check_ip(header: Option<Header<'static>>, ip: Option<String>) {
let address: SocketAddr = KNOWN_IP.parse().unwrap();
let port = address.port();
let addr: SocketAddr = KNOWN_IP.parse().unwrap();
let rocket = rocket::ignite().mount("/", routes![get_ip]);
let mut req = MockRequest::new(Get, "/").remote(address);
if let Some(header) = header {
req.add_header(header);
}
let c = Client::new(rocket::ignite().mount("/", routes![get_ip])).unwrap();
let mut response = match header {
Some(header) => c.get("/").header(header).remote(addr).dispatch(),
None => c.get("/").remote(addr).dispatch()
};
let mut response = req.dispatch_with(&rocket);
assert_eq!(response.status(), Status::Ok);
let body_str = response.body_string();
let body = response.body_string();
match ip {
Some(ip) => assert_eq!(body_str, Some(format!("{}:{}", ip, port))),
None => assert_eq!(body_str, Some(KNOWN_IP.into()))
Some(ip) => assert_eq!(body, Some(format!("{}:{}", ip, addr.port()))),
None => assert_eq!(body, Some(KNOWN_IP.into()))
}
}

View File

@ -13,14 +13,10 @@ fn files(route: &Route, path: PathBuf) -> String {
mod route_guard_tests {
use super::*;
use rocket::local::Client;
use rocket::Rocket;
use rocket::testing::MockRequest;
use rocket::http::Method::*;
fn assert_path(rocket: &Rocket, path: &str) {
let mut req = MockRequest::new(Get, path);
let mut res = req.dispatch_with(&rocket);
fn assert_path(client: &Client, path: &str) {
let mut res = client.get(path).dispatch();
assert_eq!(res.body_string(), Some(path.into()));
}
@ -30,9 +26,10 @@ mod route_guard_tests {
.mount("/first", routes![files])
.mount("/second", routes![files]);
assert_path(&rocket, "/first/some/path");
assert_path(&rocket, "/second/some/path");
assert_path(&rocket, "/first/second/b/c");
assert_path(&rocket, "/second/a/b/c");
let client = Client::new(rocket).unwrap();
assert_path(&client, "/first/some/path");
assert_path(&client, "/second/some/path");
assert_path(&client, "/first/second/b/c");
assert_path(&client, "/second/a/b/c");
}
}

View File

@ -32,14 +32,14 @@ fn dual(user: String, path: Segments) -> String {
mod tests {
use super::*;
use rocket::testing::MockRequest;
use rocket::http::Method::*;
use rocket::local::Client;
#[test]
fn segments_works() {
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();
// We construct a path that matches each of the routes above. We ensure the
// prefix is stripped, confirming that dynamic segments are working.
@ -48,9 +48,7 @@ mod tests {
"/static", "/point/static"]
{
let path = "this/is/the/path/we/want";
let mut req = MockRequest::new(Get, format!("{}/{}", prefix, path));
let mut response = req.dispatch_with(&rocket);
let mut response = client.get(format!("{}/{}", prefix, path)).dispatch();
assert_eq!(response.body_string(), Some(path.into()));
}
}