Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add examples using sessions #168

Merged
merged 6 commits into from
Mar 5, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,10 @@ members = [

# cookies

# sessions
"examples/sessions/introduction",
"examples/sessions/custom_data_type",

# headers
"examples/headers/setting",

Expand Down
1 change: 1 addition & 0 deletions examples/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ information on functionality and ordering.
| [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 |
| [Sessions](sessions) | Working with Sessions. | 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 |
Expand Down
33 changes: 33 additions & 0 deletions examples/sessions/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
# Sessions Examples

A collection of crates showing how to store and retrieve session data with the Gotham web framework.

## Ordering

We recommend reviewing our sessions 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)
16 changes: 16 additions & 0 deletions examples/sessions/custom_data_type/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
[package]
name = "gotham_examples_session_custom_data_type"
description = "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 <dawagner@gmail.com>"]
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"
89 changes: 89 additions & 0 deletions examples/sessions/custom_data_type/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
# Custom session data types

Storing and retrieving session data with a custom data type, in a type safe way, with the Gotham web framework.

## Running

From the `examples/session/custom_data_type` directory:

```
Terminal 1:
$ cargo run 130 ↵
Compiling gotham_examples_session_custom_data_type v0.0.0 (file://.../gotham/examples/session/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)
148 changes: 148 additions & 0 deletions examples/sessions/custom_data_type/src/main.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,148 @@
//! 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<VisitData> = SessionData::<Option<VisitData>>::borrow_from(&state);
visit_data.clone()
Copy link
Contributor

@alsuren alsuren Mar 1, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I was trying to work out whether this could be done without the clone() and without the type-coercing noise. I think that deref() and deref_mut() help a lot here. This is what I came up with:

fn get_handler(mut state: State) -> (State, Response) {
    let (visit_count, body) = match SessionData::<Option<VisitData>>::borrow_from(&state).deref() {
        &Some(ref visit_data) => (
            visit_data.count,
            format!(
                "You have visited this page {} time(s) before. Your last visit was {}.\n",
                visit_data.count, visit_data.last_visit,
            ),
        ),
        &None => (0, "You have never visited this page before.\n".to_owned()),
    };
    ...
    {
        *SessionData::<Option<VisitData>>::borrow_mut_from(&mut state).deref_mut() =
            Some(VisitData {
                count: visit_count + 1,
                last_visit: format!("{}", time::now().rfc3339()),
            });
    }
    (state, res)
}

I'm not sure if I like my match that returns a tuple of (visit_count, body) or not: I think my mind is still in python mode. I suspect that it would get increasingly awkward as the view gets more complicated. Might be better to stick with .clone().

(Thanks for writing this by the way: I was wondering how to use the session middleware)

Copy link
Contributor

@bradleybeddoes bradleybeddoes Mar 4, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm happy to stick with the clone for now to try and keep examples simple. Longer term I am hopeful that NLL will help here.

When I get a moment I might even grab a nightly compiler and give that a go on a few of our examples now that there is a feature flag for it.

};

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<VisitData> =
SessionData::<Option<VisitData>>::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::<Option<VisitData>>()
// 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<String> = {
let cookie_header = response.headers().get::<SetCookie>();
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
);
}
}
12 changes: 12 additions & 0 deletions examples/sessions/introduction/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
[package]
name = "gotham_examples_session_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 <dawagner@gmail.com>"]
publish = false

[dependencies]
gotham = { path = "../../../gotham" }

hyper = "0.11"
mime = "0.3"
Loading