From 24dd5bc81d6e99887603c571ca1ab6da25ab8e0f Mon Sep 17 00:00:00 2001 From: Daniel Wagner-Hall Date: Thu, 1 Mar 2018 00:41:06 +0000 Subject: [PATCH] Add examples using cookies One is for a basic data type (usize), and the other shows using your own custom data type. --- Cargo.toml | 2 + examples/README.md | 2 +- examples/cookies/.gitkeep | 0 examples/cookies/README.md | 33 ++++ examples/cookies/custom_data_type/Cargo.toml | 16 ++ examples/cookies/custom_data_type/README.md | 89 ++++++++++ examples/cookies/custom_data_type/src/main.rs | 154 ++++++++++++++++++ examples/cookies/introduction/Cargo.toml | 12 ++ examples/cookies/introduction/README.md | 89 ++++++++++ examples/cookies/introduction/src/main.rs | 140 ++++++++++++++++ 10 files changed, 536 insertions(+), 1 deletion(-) delete mode 100644 examples/cookies/.gitkeep create mode 100644 examples/cookies/README.md create mode 100644 examples/cookies/custom_data_type/Cargo.toml create mode 100644 examples/cookies/custom_data_type/README.md create mode 100644 examples/cookies/custom_data_type/src/main.rs create mode 100644 examples/cookies/introduction/Cargo.toml create mode 100644 examples/cookies/introduction/README.md create mode 100644 examples/cookies/introduction/src/main.rs diff --git a/Cargo.toml b/Cargo.toml index 445d96bf5..ad567b705 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -25,6 +25,8 @@ members = [ "examples/query_string/introduction", # cookies + "examples/cookies/introduction", + "examples/cookies/custom_data_type", # headers "examples/headers/setting", diff --git a/examples/README.md b/examples/README.md index 1a73a3f5f..23bcaec05 100644 --- a/examples/README.md +++ b/examples/README.md @@ -34,7 +34,7 @@ information on functionality and ordering. | [Routing](routing) | Dispatching `Requests` to functionality provided by your application. | 4 | | [Path](path) | Extracting data from the `Request` path ensuring type safety. | 1 | | [Query String](query_string) | Extracting data from the `Request` query string whilst ensuring type safety. | 1 | -| [Cookies](cookies) | Working with Cookies. | 0 | +| [Cookies](cookies) | Working with Cookies. | 2 | | [Headers](headers) | Working with HTTP Headers. | 1 | | [Handlers](handlers) | Developing application logic that responds to web requests. | 0 | | [Middleware](middleware) | Developing custom middleware for your application. | 1 | diff --git a/examples/cookies/.gitkeep b/examples/cookies/.gitkeep deleted file mode 100644 index e69de29bb..000000000 diff --git a/examples/cookies/README.md b/examples/cookies/README.md new file mode 100644 index 000000000..2295031fd --- /dev/null +++ b/examples/cookies/README.md @@ -0,0 +1,33 @@ +# Cookies Examples + +A crate showing how to store and retrieve session data with the Gotham web framework. + +## Ordering + +We recommend reviewing our cookies examples in the order shown below: + +1. [Introduction](introduction) - Shows how to store and retrieve session data. +2. [Custom data type](custom_data_type) - Shows how to store and retrieve session data with a custom data type. + +## Help + +You can get help for the Gotham web framework at: + +* [The Gotham web framework website](https://gotham.rs) +* [Gotham web framework API documentation](https://docs.rs/gotham/) +* [Gitter chatroom](https://gitter.im/gotham-rs/gotham) +* [Twitter](https://twitter.com/gotham_rs) + +## License + +Licensed under your option of: + +* [MIT License](../LICENSE-MIT) +* [Apache License, Version 2.0](../LICENSE-APACHE) + +## Community + +The following policies guide participation in our project and our community: + +* [Code of conduct](../../CODE_OF_CONDUCT.md) +* [Contributing](../../CONTRIBUTING.md) diff --git a/examples/cookies/custom_data_type/Cargo.toml b/examples/cookies/custom_data_type/Cargo.toml new file mode 100644 index 000000000..6610b736b --- /dev/null +++ b/examples/cookies/custom_data_type/Cargo.toml @@ -0,0 +1,16 @@ +[package] +name = "gotham_examples_cookies_custom_data_type" +description = "An introduction to storing and retrieving session data with a custom data type, in a type safe way, with the Gotham web framework." +version = "0.0.0" +authors = ["Daniel Wagner-Hall "] +publish = false + +[dependencies] +gotham = { path = "../../../gotham" } +gotham_derive = { path = "../../../gotham_derive" } + +hyper = "0.11" +mime = "0.3" +serde = "1" +serde_derive = "1" +time = "0.1.39" diff --git a/examples/cookies/custom_data_type/README.md b/examples/cookies/custom_data_type/README.md new file mode 100644 index 000000000..849731ecd --- /dev/null +++ b/examples/cookies/custom_data_type/README.md @@ -0,0 +1,89 @@ +# Cookies introduction + +An introduction to storing and retrieving session data with a custom data type, in a type safe way, with the Gotham web framework. + +## Running + +From the `examples/cookies/custom_data_type` directory: + +``` +Terminal 1: + $ cargo run 130 ↵ + Compiling gotham_examples_cookies_custom_data_type v0.0.0 (file://.../gotham/examples/cookies/custom_data_type) + Finished dev [unoptimized + debuginfo] target(s) in 2.49 secs + Running `.../gotham/target/debug/gotham_examples_cookies_custom_data_type` + Listening for requests at http://127.0.0.1:7878 + +Terminal 2: + $ curl -v -c /tmp/cookiejar http://localhost:7878 + * Rebuilt URL to: http://localhost:7878/ + * Trying ::1... + * TCP_NODELAY set + * Connection failed + * connect to ::1 port 7878 failed: Connection refused + * Trying 127.0.0.1... + * TCP_NODELAY set + * Connected to localhost (127.0.0.1) port 7878 (#0) + > GET / HTTP/1.1 + > Host: localhost:7878 + > User-Agent: curl/7.54.0 + > Accept: */* + > + < HTTP/1.1 200 OK + < Content-Length: 41 + < Content-Type: text/plain + < X-Request-ID: 5fdd0a88-4b23-4c68-8f6b-91d3c6a69fd4 + < X-Frame-Options: DENY + < X-XSS-Protection: 1; mode=block + < X-Content-Type-Options: nosniff + * Added cookie _gotham_session="op-CELe5R-mEJ3zxhcak4eElI5EUtslBLbZ6chyUvAWzEwkvAkPUBzsHj014xHW1tWq0RG4vyXSnXDZneqfxyA" for domain localhost, path /, expire 0 + < Set-Cookie: _gotham_session=op-CELe5R-mEJ3zxhcak4eElI5EUtslBLbZ6chyUvAWzEwkvAkPUBzsHj014xHW1tWq0RG4vyXSnXDZneqfxyA; HttpOnly; SameSite=Lax; Path=/ + < X-Runtime-Microseconds: 1143 + < Date: Thu, 01 Mar 2018 00:29:10 GMT + < + You have never visited this page before. + * Connection #0 to host localhost left intact + + $ curl -v -b /tmp/cookiejar http://localhost:7878 + * Rebuilt URL to: http://localhost:7878/ + * Trying ::1... + * TCP_NODELAY set + * Connection failed + * connect to ::1 port 7878 failed: Connection refused + * Trying 127.0.0.1... + * TCP_NODELAY set + * Connected to localhost (127.0.0.1) port 7878 (#0) + > GET / HTTP/1.1 + > Host: localhost:7878 + > User-Agent: curl/7.54.0 + > Accept: */* + > Cookie: _gotham_session=op-CELe5R-mEJ3zxhcak4eElI5EUtslBLbZ6chyUvAWzEwkvAkPUBzsHj014xHW1tWq0RG4vyXSnXDZneqfxyA + > + < HTTP/1.1 200 OK + < Content-Length: 87 + < Content-Type: text/plain + < X-Request-ID: 0a202d40-2c89-418e-82b0-5854c0041665 + < X-Frame-Options: DENY + < X-XSS-Protection: 1; mode=block + < X-Content-Type-Options: nosniff + < X-Runtime-Microseconds: 400 + < Date: Thu, 01 Mar 2018 00:29:13 GMT + < + You have visited this page 1 time(s) before. Your last visit was 2018-03-01T00:29:10Z. + * Connection #0 to host localhost left intact + +``` + +## License + +Licensed under your option of: + +* [MIT License](../../LICENSE-MIT) +* [Apache License, Version 2.0](../../LICENSE-APACHE) + +## Community + +The following policies guide participation in our project and our community: + +* [Code of conduct](../../CODE_OF_CONDUCT.md) +* [Contributing](../../CONTRIBUTING.md) diff --git a/examples/cookies/custom_data_type/src/main.rs b/examples/cookies/custom_data_type/src/main.rs new file mode 100644 index 000000000..8bf3e5ef6 --- /dev/null +++ b/examples/cookies/custom_data_type/src/main.rs @@ -0,0 +1,154 @@ +//! An introduction to storing and retrieving session data with a custom data type, in a type safe +//! way, with the Gotham web framework. + +extern crate gotham; +#[macro_use] +extern crate gotham_derive; +extern crate hyper; +extern crate mime; +extern crate serde; +#[macro_use] +extern crate serde_derive; +extern crate time; + +use hyper::{Response, StatusCode}; + +use gotham::http::response::create_response; +use gotham::pipeline::new_pipeline; +use gotham::pipeline::single::single_pipeline; +use gotham::router::Router; +use gotham::router::builder::*; +use gotham::state::{FromState, State}; +use gotham::middleware::session::{NewSessionMiddleware, SessionData}; + +// A custom type for storing data associated with the user's session. +#[derive(Clone, Deserialize, Serialize, StateData)] +struct VisitData { + count: usize, + last_visit: String, +} + +/// Handler function for `GET` requests directed to `/` +/// +/// Each request made will update state about your recent visits, and report it back. +fn get_handler(mut state: State) -> (State, Response) { + let maybe_visit_data = { + let visit_data: &Option = SessionData::>::borrow_from(&state); + visit_data.clone() + }; + + let body = match &maybe_visit_data { + &Some(ref visit_data) => { + format!( + "You have visited this page {} time(s) before. Your last visit was {}.\n", + visit_data.count, + visit_data.last_visit, + ) + } + &None => "You have never visited this page before.\n".to_owned(), + }; + let res = { + create_response( + &state, + StatusCode::Ok, + Some((body.as_bytes().to_vec(), mime::TEXT_PLAIN)), + ) + }; + { + let visit_data: &mut Option = + SessionData::>::borrow_mut_from(&mut state); + let old_count = maybe_visit_data.map(|v| v.count).unwrap_or(0); + *visit_data = Some(VisitData { + count: old_count + 1, + last_visit: format!("{}", time::now().rfc3339()), + }); + } + (state, res) +} + +/// Create a `Router` +fn router() -> Router { + let middleware = NewSessionMiddleware::default() + .with_session_type::>() + // By default, the cookies used are only sent over secure connections. For our test server, + // we don't set up an HTTPS certificate, so we allow the cookies to be sent over insecure + // connections. This should not be done in real applications. + .insecure(); + let (chain, pipelines) = single_pipeline(new_pipeline().add(middleware).build()); + build_router( + chain, + pipelines, + |route| { route.get("/").to(get_handler); }, + ) +} + +/// Start a server and use a `Router` to dispatch requests +pub fn main() { + let addr = "127.0.0.1:7878"; + println!("Listening for requests at http://{}", addr); + gotham::start(addr, router()) +} + +#[cfg(test)] +mod tests { + use super::*; + use gotham::test::TestServer; + use hyper::header::{Cookie, SetCookie}; + use std::borrow::Cow; + + #[test] + fn cookie_is_set_and_updates_response() { + let test_server = TestServer::new(router()).unwrap(); + let response = test_server + .client() + .get("http://localhost/") + .perform() + .unwrap(); + + assert_eq!(response.status(), StatusCode::Ok); + + let set_cookie: Vec = { + let cookie_header = response.headers().get::(); + assert!(cookie_header.is_some()); + cookie_header.unwrap().0.clone() + }; + assert!(set_cookie.len() == 1); + + let body = response.read_body().unwrap(); + assert_eq!( + &body[..], + "You have never visited this page before.\n".as_bytes() + ); + + let cookie = { + let mut cookie = Cookie::new(); + + let only_cookie: String = set_cookie.get(0).unwrap().clone(); + let cookie_components: Vec<_> = only_cookie.split(";").collect(); + let cookie_str_parts: Vec<_> = cookie_components.get(0).unwrap().split("=").collect(); + cookie.append( + Cow::Owned(cookie_str_parts.get(0).unwrap().to_string()), + Cow::Owned(cookie_str_parts.get(1).unwrap().to_string()), + ); + cookie + }; + + let response = test_server + .client() + .get("http://localhost/") + .with_header(cookie) + .perform() + .unwrap(); + + assert_eq!(response.status(), StatusCode::Ok); + let body = response.read_body().unwrap(); + let body_string = String::from_utf8(body).unwrap(); + assert!( + body_string.starts_with( + "You have visited this page 1 time(s) before. Your last visit was ", + ), + "Wrong body: {}", + body_string + ); + } +} diff --git a/examples/cookies/introduction/Cargo.toml b/examples/cookies/introduction/Cargo.toml new file mode 100644 index 000000000..e5f929568 --- /dev/null +++ b/examples/cookies/introduction/Cargo.toml @@ -0,0 +1,12 @@ +[package] +name = "gotham_examples_cookies_introduction" +description = "An introduction to storing and retrieving session data, in a type safe way, with the Gotham web framework." +version = "0.0.0" +authors = ["Daniel Wagner-Hall "] +publish = false + +[dependencies] +gotham = { path = "../../../gotham" } + +hyper = "0.11" +mime = "0.3" diff --git a/examples/cookies/introduction/README.md b/examples/cookies/introduction/README.md new file mode 100644 index 000000000..74f7e64e7 --- /dev/null +++ b/examples/cookies/introduction/README.md @@ -0,0 +1,89 @@ +# Cookies introduction + +An introduction to storing and retrieving session data, in a type safe way, with the Gotham web framework. + +## Running + +From the `examples/cookies/introduction` directory: + +``` +Terminal 1: + $ cargo run 130 ↵ + Compiling gotham_examples_cookies_introduction v0.0.0 (file://.../gotham/examples/cookies/introduction) + Finished dev [unoptimized + debuginfo] target(s) in 2.49 secs + Running `.../gotham/target/debug/gotham_examples_cookies_introduction` + Listening for requests at http://127.0.0.1:7878 + +Terminal 2: + $ curl -v -c /tmp/cookiejar http://localhost:7878 + * Rebuilt URL to: http://localhost:7878/ + * Trying ::1... + * TCP_NODELAY set + * Connection failed + * connect to ::1 port 7878 failed: Connection refused + * Trying 127.0.0.1... + * TCP_NODELAY set + * Connected to localhost (127.0.0.1) port 7878 (#0) + > GET / HTTP/1.1 + > Host: localhost:7878 + > User-Agent: curl/7.54.0 + > Accept: */* + > + < HTTP/1.1 200 OK + < Content-Length: 44 + < Content-Type: text/plain + < X-Request-ID: e9992f1d-9120-4473-be3e-60085098fb27 + < X-Frame-Options: DENY + < X-XSS-Protection: 1; mode=block + < X-Content-Type-Options: nosniff + * Added cookie _gotham_session="rU4d0wvS1FO16jJ_pEzqrAYot6jcqrtpy8wDKEqwjuYbbgzunagwGA0h0kd6qH-cLwYlaGr3gOOxJEKmFa2pSg" for domain localhost, path /, expire 0 + < Set-Cookie: _gotham_session=rU4d0wvS1FO16jJ_pEzqrAYot6jcqrtpy8wDKEqwjuYbbgzunagwGA0h0kd6qH-cLwYlaGr3gOOxJEKmFa2pSg; HttpOnly; SameSite=Lax; Path=/ + < X-Runtime-Microseconds: 479 + < Date: Wed, 28 Feb 2018 23:16:05 GMT + < + You have visited this page 0 time(s) before + * Connection #0 to host localhost left intact + + $ curl -v -b /tmp/cookiejar http://localhost:7878 + * Rebuilt URL to: http://localhost:7878/ + * Trying ::1... + * TCP_NODELAY set + * Connection failed + * connect to ::1 port 7878 failed: Connection refused + * Trying 127.0.0.1... + * TCP_NODELAY set + * Connected to localhost (127.0.0.1) port 7878 (#0) + > GET / HTTP/1.1 + > Host: localhost:7878 + > User-Agent: curl/7.54.0 + > Accept: */* + > Cookie: _gotham_session=rU4d0wvS1FO16jJ_pEzqrAYot6jcqrtpy8wDKEqwjuYbbgzunagwGA0h0kd6qH-cLwYlaGr3gOOxJEKmFa2pSg + > + < HTTP/1.1 200 OK + < Content-Length: 44 + < Content-Type: text/plain + < X-Request-ID: 788c055a-293b-49da-986f-d0afed9015fb + < X-Frame-Options: DENY + < X-XSS-Protection: 1; mode=block + < X-Content-Type-Options: nosniff + < X-Runtime-Microseconds: 320 + < Date: Wed, 28 Feb 2018 23:16:38 GMT + < + You have visited this page 1 time(s) before + * Connection #0 to host localhost left intact + +``` + +## License + +Licensed under your option of: + +* [MIT License](../../LICENSE-MIT) +* [Apache License, Version 2.0](../../LICENSE-APACHE) + +## Community + +The following policies guide participation in our project and our community: + +* [Code of conduct](../../CODE_OF_CONDUCT.md) +* [Contributing](../../CONTRIBUTING.md) diff --git a/examples/cookies/introduction/src/main.rs b/examples/cookies/introduction/src/main.rs new file mode 100644 index 000000000..14199521c --- /dev/null +++ b/examples/cookies/introduction/src/main.rs @@ -0,0 +1,140 @@ +//! An introduction to storing and retrieving session data, in a type safe way, with the Gotham +//! web framework. + +extern crate gotham; +extern crate hyper; +extern crate mime; + +use hyper::{Response, StatusCode}; + +use gotham::http::response::create_response; +use gotham::pipeline::new_pipeline; +use gotham::pipeline::single::single_pipeline; +use gotham::router::Router; +use gotham::router::builder::*; +use gotham::state::{FromState, State}; +use gotham::middleware::session::{NewSessionMiddleware, SessionData}; + +/// Handler function for `GET` requests directed to `/` +/// +/// Each request made will increment a counter of requests which have been made, +/// and tell you how many times you've visited the page. +fn get_handler(mut state: State) -> (State, Response) { + // Define a narrow scope so that state can be borrowed/moved later in the function. + let visits = { + // Borrow a reference to the usize stored for the session (keyed by a cookie) from state. + // We don't need to worry about the underlying cookie mechanics, we just ask for our usize. + let visits: &usize = SessionData::::borrow_from(&state); + *visits + }; + + let res = { + create_response( + &state, + StatusCode::Ok, + Some(( + format!( + "You have visited this page {} time(s) before\n", + visits + ).as_bytes() + .to_vec(), + mime::TEXT_PLAIN, + )), + ) + }; + { + // Mutably borrow the usize, so we can increment it. + let visits: &mut usize = SessionData::::borrow_mut_from(&mut state); + *visits += 1; + } + (state, res) +} + +/// Create a `Router` +fn router() -> Router { + // Install middleware which handles session creation before, and updating after, our handler is + // called. + // The default NewSessionMiddleware stores session data in an in-memory map, which means that + // server restarts will throw the data away, but it can be customized as needed. + let middleware = NewSessionMiddleware::default() + // Configure the type of data which we want to store in the session. + // See the custom_data_type example for storing more complex data. + .with_session_type::() + // By default, the cookies used are only sent over secure connections. For our test server, + // we don't set up an HTTPS certificate, so we allow the cookies to be sent over insecure + // connections. This should not be done in real applications. + .insecure(); + let (chain, pipelines) = single_pipeline(new_pipeline().add(middleware).build()); + build_router( + chain, + pipelines, + |route| { route.get("/").to(get_handler); }, + ) +} + +/// Start a server and use a `Router` to dispatch requests +pub fn main() { + let addr = "127.0.0.1:7878"; + println!("Listening for requests at http://{}", addr); + gotham::start(addr, router()) +} + +#[cfg(test)] +mod tests { + use super::*; + use gotham::test::TestServer; + use hyper::header::{Cookie, SetCookie}; + use std::borrow::Cow; + + #[test] + fn cookie_is_set_and_counter_increments() { + let test_server = TestServer::new(router()).unwrap(); + let response = test_server + .client() + .get("http://localhost/") + .perform() + .unwrap(); + + assert_eq!(response.status(), StatusCode::Ok); + + let set_cookie: Vec = { + let cookie_header = response.headers().get::(); + assert!(cookie_header.is_some()); + cookie_header.unwrap().0.clone() + }; + assert!(set_cookie.len() == 1); + + let body = response.read_body().unwrap(); + assert_eq!( + &body[..], + "You have visited this page 0 time(s) before\n".as_bytes() + ); + + let cookie = { + let mut cookie = Cookie::new(); + + let only_cookie: String = set_cookie.get(0).unwrap().clone(); + let cookie_components: Vec<_> = only_cookie.split(";").collect(); + let cookie_str_parts: Vec<_> = cookie_components.get(0).unwrap().split("=").collect(); + cookie.append( + Cow::Owned(cookie_str_parts.get(0).unwrap().to_string()), + Cow::Owned(cookie_str_parts.get(1).unwrap().to_string()), + ); + cookie + }; + + let response = test_server + .client() + .get("http://localhost/") + .with_header(cookie) + .perform() + .unwrap(); + + assert_eq!(response.status(), StatusCode::Ok); + let body = response.read_body().unwrap(); + assert_eq!( + &body[..], + "You have visited this page 1 time(s) before\n".as_bytes() + ); + } +}