Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 0 additions & 1 deletion .github/workflows/check.yml
Original file line number Diff line number Diff line change
Expand Up @@ -27,5 +27,4 @@ jobs:
cargo fmt --all --check
- name: Check
run: |
cargo clippy --release --features https -- -D warnings
cargo clippy -- -D warnings
25 changes: 0 additions & 25 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 0 additions & 5 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -3,15 +3,10 @@ name = "backend"
version = "0.1.0"
edition = "2024"

[features]
https = ["dep:rustls", "dep:axum-extra"]

[dependencies]
axum = "0.8.4"
axum-extra = { version = "0.10.1", optional = true }
axum-server = { version = "0.7.2", features = ["tls-rustls"] }
dotenvy = "0.15.7"
rustls = { version = "0.23.31", optional = true }
serde = { version = "1.0.221", features = ["derive"] }
serde_json = "1.0.145"
tokio = { version = "1.47.1", features = ["rt-multi-thread"] }
124 changes: 17 additions & 107 deletions src/main.rs
Original file line number Diff line number Diff line change
@@ -1,30 +1,12 @@
use std::net::{Ipv4Addr, SocketAddr, SocketAddrV4};
use std::{
env::VarError,
net::{Ipv4Addr, SocketAddr, SocketAddrV4},
};

use crate::app::new_app;

// enforce https on release to make sure no one forgets to use https
#[cfg(all(not(debug_assertions), not(feature = "https")))]
compile_error!("Feature `https` must be enabled on release.");

mod app;

#[allow(dead_code)]
struct Ports {
http: u16,
https: u16,
}

#[cfg(not(debug_assertions))]
const PORTS: Ports = Ports {
http: 80,
https: 443,
};
#[cfg(debug_assertions)]
const PORTS: Ports = Ports {
http: 8080,
https: 4430,
};

#[cfg(debug_assertions)]
const ADDR: Ipv4Addr = Ipv4Addr::LOCALHOST;
#[cfg(not(debug_assertions))]
Expand All @@ -33,95 +15,23 @@ const ADDR: Ipv4Addr = Ipv4Addr::UNSPECIFIED;
#[tokio::main]
async fn main() {
let _ = dotenvy::dotenv_override(); // it doesn't matter if there isnt a .env
#[cfg(not(feature = "https"))]
http_main().await;
#[cfg(feature = "https")]
https_main().await;
}

#[cfg(not(feature = "https"))]
async fn http_main() {
let app = new_app();

axum_server::bind(SocketAddr::V4(SocketAddrV4::new(ADDR, PORTS.http)))
.serve(app.into_make_service())
.await
.unwrap();
}

#[cfg(feature = "https")]
async fn https_main() {
use axum_server::tls_rustls::RustlsConfig;

// configure certificate and private key used by https
let config = RustlsConfig::from_pem_file(
dotenvy::var("HTTPS_CERT_PATH").expect("Environment variable `HTTPS_CERT_PATH` not found.\nCreate it in a .env within the cwd or in the environment"),
dotenvy::var("HTTPS_KEY_PATH").expect("Environment variable `HTTPS_KEY_PATH` not found.\nCreate it in a .env within the cwd or in the environment"),
)
.await
.unwrap();

tokio::spawn(redirect_http_to_https(PORTS));

let app = new_app();

// run https server
axum_server::bind_rustls(SocketAddr::V4(SocketAddrV4::new(ADDR, PORTS.https)), config)
.serve(app.into_make_service())
.await
.unwrap();
}

#[cfg(feature = "https")]
async fn redirect_http_to_https(ports: Ports) {
use axum::{
BoxError,
handler::HandlerWithoutStateExt,
http::{Uri, uri::Authority},
};
use axum_extra::extract::Host;

fn make_https(host: &str, uri: Uri, https_port: u16) -> Result<Uri, BoxError> {
let mut parts = uri.into_parts();

parts.scheme = Some(axum::http::uri::Scheme::HTTPS);

if parts.path_and_query.is_none() {
parts.path_and_query = Some("/".parse().unwrap());
}

let authority: Authority = host.parse()?;
let bare_host = match authority.port() {
Some(port_struct) => authority
.as_str()
.strip_suffix(port_struct.as_str())
.unwrap()
.strip_suffix(':')
.unwrap(), // if authority.port() is Some(port) then we can be sure authority ends with :{port}
None => authority.as_str(),
};

parts.authority = Some(format!("{bare_host}:{https_port}").parse()?);

Ok(Uri::from_parts(parts)?)
}

let redirect = move |Host(host): Host, uri: Uri| async move {
use axum::response::Redirect;

match make_https(&host, uri, ports.https) {
Ok(uri) => Ok(Redirect::permanent(&uri.to_string())),
Err(_) => {
use axum::http::StatusCode;

Err(StatusCode::BAD_REQUEST)
let port = dotenvy::var("HTTP_PORT")
.map(|x| x.parse().unwrap())
.unwrap_or_else(|err| match &err {
dotenvy::Error::EnvVar(VarError::NotPresent) => {
if cfg!(debug_assertions) {
8080
} else {
80
}
}
}
};
_ => panic!("{err}"),
});

let addr = SocketAddr::V4(SocketAddrV4::new(ADDR, ports.http));
let listener = tokio::net::TcpListener::bind(addr).await.unwrap();
axum::serve(listener, redirect.into_make_service())
axum_server::bind(SocketAddr::V4(SocketAddrV4::new(ADDR, port)))
.serve(app.into_make_service())
.await
.unwrap();
}
Loading