1

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.

1 Answers1

0

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());
}
S. Brown
  • 401
  • 3
  • 6
  • Can you include the full code of it because I can't get it to work with this syntax `#[get(...)]` – michaelgrigoryan25 Apr 16 '21 at 20:24
  • Isn't there any other way of doing this? I don't want to declare a separate variable for all my routes – michaelgrigoryan25 Apr 17 '21 at 05:49
  • @michael.grigoryan What do you mean, a separate variable for all your routes? – S. Brown Apr 17 '21 at 23:55
  • I mean this `let rocket = rocket::ignite().mount("/", routes![shorten_link]); let client = Client::new(rocket).expect("valid rocket instance");` – michaelgrigoryan25 Apr 18 '21 at 07:34
  • `Client` isn't something you'd use in a production environment. It's just for testing, or for making requests to yourself. To start the server in production, you'd just chain a call to `launch()` onto the end of the first line. The second line just creates a mock client that makes requests to the server (`rocket`). Technically, you could make it one line with the following: `let client = Client::new(rocket::ignite().mount("/", routes![shorten_link])).expect("valid rocket instance");`, but that would consume the server, and you wouldn't be able to launch it for other clients or to use. – S. Brown Apr 18 '21 at 22:18
  • @S.Brown so in production, where/how do you perform this encoding? – Corel Aug 23 '21 at 15:29