Skip to content

Instantly share code, notes, and snippets.

@reddraggone9
Created July 20, 2017 20:58
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save reddraggone9/cc33248f7a3a68aef6b2a45e4dd117a6 to your computer and use it in GitHub Desktop.
Save reddraggone9/cc33248f7a3a68aef6b2a45e4dd117a6 to your computer and use it in GitHub Desktop.
Post/Redirect/Get vs window.history.replaceState
[package]
name = "rocket-post-redirect"
version = "0.1.0"
authors = ["reddraggone9 <cljenkins9@gmail.com>"]
[dependencies]
rocket = "0.3.0"
rocket_codegen = "0.3.0"
//! A common pattern in web development has the server issue a [redirect after receiving a form
//! submission][0]. This is primarily intended to prevent refreshing the page from triggering a
//! scary browser dialog. However, it would appear that there is another option. Instead of
//! performing a redirect, the server could respond directly with the contents of the page it would
//! have redirected to and a bit of JavaScript which uses the history API to change the URL. This
//! gist compares the two approaches.
//!
//! [0]: https://en.wikipedia.org/wiki/Post/Redirect/Get
//!
//! Based on testing in Firefox, both methods:
//!
//! - don't resubmit the form if you refresh the page.
//! - send a request to `/get-after-redirect` when you refresh the page.
//! - show `/` followed by `/get-after-redirect` in global history.
//! - result in the back button returning to `/`. Going forward from that page
//! returns you to `/get-after-redirect`, though a get request is only issued in
//! the window.history case.
//! - show `/get-after-redirect` in the URL bar after form submission. Bookmarking
//! the page also works correctly.
//!
//! ## Downsides ##
//!
//! ### Post/Redirect/Get ###
//! - Requires an extra round trip
//! - Requires the server to store data (e.g. flashes) between requests. Rocket can store flash
//! strings in a cookie, but this has its own problems (e.g. has to be serialized to a string,
//! cookie size limitations, avoidable data transfer both to and from the client, needs crypto
//! to trust it).
//!
//! ### window.history ###
//! - Requires JavaScript
//! - Post handler must be able to "forward" to the other handler
//! - Going around HTTP like this feels blasphemous
#![feature(plugin)]
#![plugin(rocket_codegen)]
extern crate rocket;
use rocket::response::Redirect;
use rocket::response::content::Html;
fn main() {
rocket::ignite()
.mount("/", routes![
index,
post_and_redirect,
get_after_redirect,
replace_state,
])
.launch();
}
#[get("/")]
fn index() -> Html<&'static str> {
Html(r#"
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
</head>
<body>
<form method="post">
<button formaction="/post-and-redirect">
POST with a redirect to update URL
</button>
<button formaction="/replace-state">
POST with JavaScript to update URL
</button>
</form>
</body>
</html>
"#)
}
#[post("/post-and-redirect")]
fn post_and_redirect() -> Redirect {
Redirect::to("/get-after-redirect")
}
#[get("/get-after-redirect")]
fn get_after_redirect() -> Html<String> {
redirect_success(None)
}
#[post("/replace-state")]
fn replace_state() -> Html<String> {
// Upon reflection, I suppose there wouldn't be any harm in including this in the desired page
// unconditionally.
redirect_success(Some(r#"
<script>window.history.replaceState(null, "", "/get-after-redirect")</script>
"#))
}
fn redirect_success(script: Option<&str>) -> Html<String> {
Html(format!(r#"
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
</head>
<body>
{}<p>Redirect successful.</p>
</body>
</html>
"#, script.unwrap_or("")))
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment