Search code examples
rustrust-rocket

Cannot handle a HTTP URL as a query parameter in Rocket


I am using Rocket to create a basic web api to practice and improve my Rust programming skills.

Here I got stuck.

#[post("/make/<link>")]
fn shorten_link(link: String) -> String {
    // Some other code
}

The problem is when I post to let's say http://localhost:8000/make/https://youtube.com/ I get redirected to a Rocket 404 page but when I do something like http://localhost:8000/make/hello everything works fine. I don't understand what I am doing wrong here so any help will be appreciated, thank you.


Solution

  • The problem is that the /s in the url https://youtube.com/ are being misinterpreted. Specifically, they are being interpreted as a new segment of the path. For example, if I requested /seg1/seg2/seg3, I wouldn't expect a handler annotated with #[get("/seg1/<arg>")] to match that path, as there is an extra segment at the end of it. Similarly, the /s in the https://youtube.com/ are being interpreted as new path segments, and thus aren't hitting your handler.

    The first solution I thought of for this was to rewrite the handler like so:

    #[post("/make/<link..>")]
    fn shorten_link(link: PathBuf) -> String {
        let link = link.to_str().unwrap(); // Don't actually unwrap
    
        // Other code
    }
    

    I thought this would work, because it takes a whole bunch of path segments, and turns it into one string, but that didn't work, because Rocket complained that you can't end a path segment with a :.

    So I just tried URL-formatting https://youtube.com/, which worked without any modification to the handler. URL-formatting is where you take symbols that either aren't safe for HTTP or have a different meaning (in our case, / and :), and you replace them with a special code that is okay. For example, the URL-encoded version of https://youtube.com/ is https%3A%2F%2Fyoutube.com%2F. The final request was a POST request to /make/https%3A%2F%2Fyoutube.com%2F:

    client.post(format!(
        "/make/{}",
        urlencoding::encode("https://youtube.com/"),
    ));
    

    You can use the urlencoding crate to encode URLS for you.

    Edit: My full code looked like this (it was just a simple test):

    #![feature(decl_macro)]
    
    use rocket::{local::Client, post, routes};
    
    #[post("/make/<link>")]
    fn shorten_link(link: String) -> String {
        link
    }
    
    fn main() {
        let rocket = rocket::ignite().mount("/", routes![shorten_link]);
        let client = Client::new(rocket).expect("valid rocket instance");
        let req = client.post(format!(
            "/make/{}",
            urlencoding::encode("https://youtube.com/"),
        ));
        let mut resp = req.dispatch();
        println!("{}", resp.body_string().unwrap());
    }