Skip to content

Instantly share code, notes, and snippets.

@vcapra1
Created May 12, 2019 19:37
Show Gist options
  • Save vcapra1/2dc30575f798d9702433a6c57882862b to your computer and use it in GitHub Desktop.
Save vcapra1/2dc30575f798d9702433a6c57882862b to your computer and use it in GitHub Desktop.
Rocket.rs route by subdomain
use rocket::{request::FromRequest, Outcome, Request};
pub struct WWWHost;
pub struct AAAHost;
impl<'a, 'r> FromRequest<'a, 'r> for WWWHost {
type Error = ();
fn from_request(request: &'a Request<'r>) -> rocket::request::Outcome<Self, Self::Error> {
if let Some(hostname) = request.headers().get_one("host") {
if hostname == "example.com" {
Outcome::Success(WWWHost)
} else if hostname == "www.example.com" {
Outcome::Success(WWWHost)
} else {
Outcome::Forward(())
}
} else {
// There was no host header (this shouldn't happen)
Outcome::Forward(())
}
}
}
impl<'a, 'r> FromRequest<'a, 'r> for AAAHost {
type Error = ();
fn from_request(request: &'a Request<'r>) -> rocket::request::Outcome<Self, Self::Error> {
if let Some(hostname) = request.headers().get_one("host") {
if hostname == "aaa.example.com" {
Outcome::Success(WWWHost)
} else {
Outcome::Forward(())
}
} else {
// There was no host header (this shouldn't happen)
Outcome::Forward(())
}
}
}
#[macro_use]
extern crate rocket;
mod hosts;
#[get("/")]
fn root(host: WWWHost) -> String {
String::from("Welcome to www.example.com!");
}
#[get("/")]
fn sub(host: AAAHost) -> String {
String::from("Welcome to aaa.example.com!");
}
fn main() {
rocket::ignite().mount("/", routes![root, sub]).launch();
}
@olehermanse
Copy link

@vcapra1 Thanks for sharing! I copied the impl from: rwf2/Rocket#823

I think it's a little nicer:

#[get("/<seed>")]
fn seed_host(seed: String, state: State<QuizData>, host: HostHeader) -> Template {
    if host.0.contains("dailyquiz.app") {
        return english_template(seed, state, "/");
    }
    return norwegian_template(seed, state, "/");
}

https://github.com/olehermanse/dagensquiz/blob/master/src/main.rs#L66

https://github.com/olehermanse/dagensquiz/blob/master/src/main.rs#L315

@vcapra1
Copy link
Author

vcapra1 commented May 27, 2019

Yeah that works great too. My code had rather long functions, however, and I wanted to split the code that handled each subdomain into its own file.

@TakingFire
Copy link

Stumbled across this great gist and found that the trait structure has changed somewhat in 0.5-rc.
Modified version of hosts.rs, based on the docs example:

use rocket::http::Status;
use rocket::request::{FromRequest, Outcome, Request};

pub struct WWWHost;
pub struct AAAHost;

#[rocket::async_trait]
impl<'r> FromRequest<'r> for WWWHost {
    type Error = ();

    async fn from_request(req: &'r Request<'_>) -> Outcome<Self, Self::Error> {
        match req.headers().get_one("host") {
            None => Outcome::Failure((Status::BadRequest, ())),
            Some(host) if matches!(host, "example.com" | "www.example.com") => Outcome::Success(WWWHost),
            Some(_) => Outcome::Forward(()),
        }
    }
}

#[rocket::async_trait]
impl<'r> FromRequest<'r> for AAAHost {
    type Error = ();

    async fn from_request(req: &'r Request<'_>) -> Outcome<Self, Self::Error> {
        match req.headers().get_one("host") {
            None => Outcome::Failure((Status::BadRequest, ())),
            Some(host) if (host == "aaa.example.com") => Outcome::Success(AAAHost),
            Some(_) => Outcome::Forward(()),
        }
    }
}

@ShayBox
Copy link

ShayBox commented Jan 22, 2024

Here's my version which uses the duplicate crate, the one for creating the structs is needless and adds extra lines, but it allows for only one allow macro. This includes a default struct which can be removed.

#[macro_use]
extern crate rocket;

use duplicate::duplicate_item;
use rocket::{
    http::Status,
    request::{FromRequest, Outcome},
    Request,
};

#[allow(clippy::upper_case_acronyms)]
#[duplicate_item(
    Name;
    [ API ];
    [ WEB ];
    [ DEF ];
)]
struct Name;

#[duplicate_item(
    Host    Name;
    [ API ] [ "api.shaybox.com" | "127.0.0.1:8000" ];
    [ WEB ] [ "www.shaybox.com" | "shaybox.com" ];
    [ DEF ] [ _ ];
)]
#[rocket::async_trait]
impl<'r> FromRequest<'r> for Host {
    type Error = ();

    async fn from_request(req: &'r Request<'_>) -> Outcome<Self, Self::Error> {
        match req.headers().get_one("host") {
            None => Outcome::Error((Status::BadRequest, ())),
            Some(Name) => Outcome::Success(Host),
            #[allow(unreachable_patterns)] // DEF
            Some(_) => Outcome::Forward(Status::Accepted),
            // Some(host) => { // Debug
            //     println!("{host}");
            //     Outcome::Forward(Status::Accepted)
            // }
        }
    }
}

#[get("/", rank = 2)]
fn def(_host: DEF) -> &'static str {
    "Hello, DEF!"
}

#[get("/", rank = 1)]
fn api(_host: API) -> &'static str {
    "Hello, API!"
}

#[get("/", rank = 0)]
fn web(_host: WEB) -> &'static str {
    "Hello, WEB!"
}

#[launch]
fn rocket() -> _ {
    rocket::build().mount("/", routes![api, web, def])
}

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment