Rocket: get, set (add), delete cookies - pending cookies

Rocket web get add delete get_pending

In this example of the Rocket web framework we'll see how to set or update (add) a cookie. How to get a cookie sent by the user. How to delete a cookie from the browser, or at least how to ask the browser to delete it.

Finally we'll deal with pending cookies. That is we would like to ensure that in the same route where we make changes to a cookie, the rest of the code already see the new value of the cookie and not the one we received from the user.

Dependencies

examples/rocket/set-cookie/Cargo.toml

[package]
name = "set-cookie"
version = "0.1.0"
edition = "2021"

# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html

[dependencies]
rocket = "0.5"

The code

examples/rocket/set-cookie/src/main.rs

#[macro_use]
extern crate rocket;

use rocket::http::CookieJar;
use rocket::response::content;
use std::time::{SystemTime, UNIX_EPOCH};


fn get_time() -> String {
     let start = SystemTime::now();
     let since_the_epoch = start
     .duration_since(UNIX_EPOCH)
     .expect("Time went backwards");

     since_the_epoch.as_micros().to_string()
}

fn get_html(cookies: &CookieJar<'_>,  current_time: &str) -> content::RawHtml<String> {
    let saved_time: String = match cookies.get("cookie-demo") {
        Some(cookie) => cookie.value().to_owned(),
        None => String::from("No cookie"),
    };

    content::RawHtml(format!(r#"<a href="/">home</a> <a href="/set">set cookie</a> <a href="/delete">delete cookie</a><br>Current time: {current_time}<br>Saved time: {saved_time}<br>"#))
}

#[get("/")]
fn home(cookies: &CookieJar<'_>) -> content::RawHtml<String> {
    let current_time = get_time();
    rocket::info!("home current_time: {}", current_time);
    get_html(cookies, &current_time)
}

#[get("/set")]
fn set_cookie(cookies: &CookieJar<'_>) -> content::RawHtml<String> {
    let current_time = get_time();
    rocket::info!("set_cookie current_time: {}", current_time);
    cookies.add(("cookie-demo", current_time.clone()));
    get_html(cookies, &current_time)
}

#[get("/delete")]
fn delete_cookie(cookies: &CookieJar<'_>) -> content::RawHtml<String> {
    let current_time = get_time();
    rocket::info!("delete_cookie current_time: {}", current_time);
    cookies.remove("cookie-demo");
    get_html(cookies, &current_time)
}


#[launch]
fn rocket() -> _ {
    rocket::build().mount("/", routes![home, delete_cookie, set_cookie])
}

Explanation

We have a function called get_time that returns the elapsed time since the epoch. We just needed something easy to have always changing values. We will set the value returned by this function to be the value of the cookie.

We have 3 routes to set the cookie, delete the cookie and to only look at the cookie (the home page), and they all look the same. So we have a function called get_html. It will get the cookie and it will return the shared html that includes the current time and the value of the cookie which is the timestamp when we set the cookie.

fn get_html(cookies: &CookieJar<'_>,  current_time: &str) -> content::RawHtml<String> {
    let saved_time: String = match cookies.get("cookie-demo") {
        Some(cookie) => cookie.value().to_owned(),
        None => String::from("No cookie"),
    };

    content::RawHtml(format!(r#"<a href="/">home</a> <a href="/set">set cookie</a> <a href="/delete">delete cookie</a><br>Current time: {current_time}<br>Saved time: {saved_time}<br>"#))
}

Interaction

When you first visit the page you will see (with some other timestamp):

Current time: 1706775050281184
Saved time: No cookie

That's because we don't have a cookie yet.

If you click on set cookie you will see:

Current time: 1706775061279344
Saved time: No cookie

So despite the fact that we set the cookie it still thinks we don't have a cookie. That's because the get method returns the cookie that we received from the client. In order to get the cookie that we are about to send to the user we can use the get_pending method.

If we now click on the home we'll see a new current time, and the saved time is the timestamp from when we clicked on set.

Current time: 1706775070699980
Saved time: 1706775061279344

We can keep refreshing the home page and we'll keep seeing the current time changing while the saved time fixed.

If we click on delete cookie it will delete the cookie, but it will still show the values from when we had a cookie.

Current time: 1706775167878389
Saved time: 1706775061279344

If we click on home now we'll see that the cookie is gone:

Current time: 1706775203917017
Saved time: No cookie

Use the pendig cookie

If you want to make sure that the page reflects the inew cookie value on the page where we set it, or the lack of cookie on the page where we delete it then instead of cookies.get("cookie-demo") we can call cookies.get_pending("cookie-demo") using the get_pending method.

One would think that it would be better to always se this method, but the documentation warns us of this being slower and recommends that we only use this method when we really need it.

In my exmaple this is not possible as all 3 operations (get/set/delete) uses the same code, but in a real-world application you'd call the get_pending only after you called add or delete that changes the state of the cookie and you'd called get (or get_private) in every other case.

Related Pages

Rocket - web development with Rust

Author

Gabor Szabo (szabgab)

Gabor Szabo, the author of the Rust Maven web site maintains several Open source projects in Rust and while he still feels he has tons of new things to learn about Rust he already offers training courses in Rust and still teaches Python, Perl, git, GitHub, GitLab, CI, and testing.

Gabor Szabo