Skip to content

Commit

Permalink
Redirect HTTP to HTTPS (ordinals#1198)
Browse files Browse the repository at this point in the history
  • Loading branch information
casey committed Jan 12, 2023
1 parent cecb8d6 commit 47fcb7c
Show file tree
Hide file tree
Showing 2 changed files with 129 additions and 48 deletions.
3 changes: 2 additions & 1 deletion deploy/ord.service
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,8 @@ ExecStart=/usr/local/bin/ord \
server \
--acme-contact mailto:[email protected] \
--http \
--https
--https \
--redirect-http-to-https
Group=ord
MemoryDenyWriteExecute=true
NoNewPrivileges=true
Expand Down
174 changes: 127 additions & 47 deletions src/subcommand/server.rs
Original file line number Diff line number Diff line change
@@ -1,10 +1,9 @@
use super::*;

use {
self::{
deserialize_from_str::DeserializeFromStr,
error::{OptionExt, ServerError, ServerResult},
},
super::*,
crate::templates::{
BlockHtml, ClockSvg, HomeHtml, InputHtml, InscriptionHtml, InscriptionsHtml, OutputHtml,
PageContent, PageHtml, PreviewImageHtml, PreviewTextHtml, PreviewUnknownHtml, RangeHtml,
Expand All @@ -13,7 +12,7 @@ use {
axum::{
body,
extract::{Extension, Path, Query},
http::{header, HeaderMap, HeaderValue, StatusCode},
http::{header, HeaderMap, HeaderValue, StatusCode, Uri},
response::{IntoResponse, Redirect, Response},
routing::get,
Router,
Expand Down Expand Up @@ -52,6 +51,12 @@ impl FromStr for BlockQuery {
}
}

enum SpawnConfig {
Https(AxumAcceptor),
Http,
Redirect(String),
}

#[derive(Deserialize)]
struct Search {
query: String,
Expand Down Expand Up @@ -110,6 +115,8 @@ pub(crate) struct Server {
http: bool,
#[clap(long, help = "Serve HTTPS traffic on <HTTPS_PORT>.")]
https: bool,
#[clap(long, help = "Redirect HTTP traffic to HTTPS.")]
redirect_http_to_https: bool,
}

impl Server {
Expand Down Expand Up @@ -155,16 +162,42 @@ impl Server {
));

match (self.http_port(), self.https_port()) {
(Some(http_port), None) => self.spawn(router, handle, http_port, None)?.await??,
(Some(http_port), None) => {
self
.spawn(router, handle, http_port, SpawnConfig::Http)?
.await??
}
(None, Some(https_port)) => {
self
.spawn(router, handle, https_port, Some(self.acceptor(&options)?))?
.spawn(
router,
handle,
https_port,
SpawnConfig::Https(self.acceptor(&options)?),
)?
.await??
}
(Some(http_port), Some(https_port)) => {
let http_spawn_config = if self.redirect_http_to_https {
let acme_domains = self.acme_domains()?;

SpawnConfig::Redirect(if https_port == 443 {
format!("https://{}", acme_domains[0])
} else {
format!("https://{}:{https_port}", acme_domains[0])
})
} else {
SpawnConfig::Http
};

let (http_result, https_result) = tokio::join!(
self.spawn(router.clone(), handle.clone(), http_port, None)?,
self.spawn(router, handle, https_port, Some(self.acceptor(&options)?))?
self.spawn(router.clone(), handle.clone(), http_port, http_spawn_config)?,
self.spawn(
router,
handle,
https_port,
SpawnConfig::Https(self.acceptor(&options)?),
)?
);
http_result.and(https_result)??;
}
Expand All @@ -180,7 +213,7 @@ impl Server {
router: Router,
handle: Handle,
port: u16,
https_acceptor: Option<AxumAcceptor>,
config: SpawnConfig,
) -> Result<task::JoinHandle<io::Result<()>>> {
let addr = (self.address.as_str(), port)
.to_socket_addrs()?
Expand All @@ -190,26 +223,39 @@ impl Server {
if !integration_test() {
eprintln!(
"Listening on {}:https://{addr}",
if https_acceptor.is_some() {
"https"
} else {
"http"
match config {
SpawnConfig::Https(_) => "https",
_ => "http",
}
);
}

Ok(tokio::spawn(async move {
if let Some(acceptor) = https_acceptor {
axum_server::Server::bind(addr)
.handle(handle)
.acceptor(acceptor)
.serve(router.into_make_service())
.await
} else {
axum_server::Server::bind(addr)
.handle(handle)
.serve(router.into_make_service())
.await
match config {
SpawnConfig::Https(acceptor) => {
axum_server::Server::bind(addr)
.handle(handle)
.acceptor(acceptor)
.serve(router.into_make_service())
.await
}
SpawnConfig::Redirect(destination) => {
axum_server::Server::bind(addr)
.handle(handle)
.serve(
Router::new()
.fallback(Self::redirect_http_to_https)
.layer(Extension(destination))
.into_make_service(),
)
.await
}
SpawnConfig::Http => {
axum_server::Server::bind(addr)
.handle(handle)
.serve(router.into_make_service())
.await
}
}
}))
}
Expand All @@ -224,9 +270,9 @@ impl Server {
Ok(acme_cache)
}

fn acme_domains(acme_domain: &Vec<String>) -> Result<Vec<String>> {
if !acme_domain.is_empty() {
Ok(acme_domain.clone())
fn acme_domains(&self) -> Result<Vec<String>> {
if !self.acme_domain.is_empty() {
Ok(self.acme_domain.clone())
} else {
Ok(vec![sys_info::hostname()?])
}
Expand All @@ -249,7 +295,7 @@ impl Server {
}

fn acceptor(&self, options: &Options) -> Result<AxumAcceptor> {
let config = AcmeConfig::new(Self::acme_domains(&self.acme_domain)?)
let config = AcmeConfig::new(self.acme_domains()?)
.contact(&self.acme_contact)
.cache_option(Some(DirCache::new(Self::acme_cache(
self.acme_cache.as_ref(),
Expand Down Expand Up @@ -678,6 +724,17 @@ impl Server {
.page(chain, index.has_sat_index()?),
)
}

async fn redirect_http_to_https(
Extension(mut destination): Extension<String>,
uri: Uri,
) -> Redirect {
if let Some(path_and_query) = uri.path_and_query() {
destination.push_str(path_and_query.as_str());
}

Redirect::to(&destination)
}
}

#[cfg(test)]
Expand All @@ -695,10 +752,14 @@ mod tests {

impl TestServer {
fn new() -> Self {
Self::new_with_args(&[])
Self::new_with_args(&[], &[])
}

fn new_with_args(args: &[&str]) -> Self {
fn new_with_sat_index() -> Self {
Self::new_with_args(&["--index-sats"], &[])
}

fn new_with_args(ord_args: &[&str], server_args: &[&str]) -> Self {
let bitcoin_rpc_server = test_bitcoincore_rpc::spawn();

let tempdir = TempDir::new().unwrap();
Expand All @@ -716,12 +777,13 @@ mod tests {
let url = Url::parse(&format!("http:https://127.0.0.1:{port}")).unwrap();

let (options, server) = parse_server_args(&format!(
"ord --chain regtest --rpc-url {} --cookie-file {} --data-dir {} {} server --http-port {} --address 127.0.0.1",
"ord --chain regtest --rpc-url {} --cookie-file {} --data-dir {} {} server --http-port {} --address 127.0.0.1 {}",
bitcoin_rpc_server.url(),
cookiefile.to_str().unwrap(),
tempdir.path().to_str().unwrap(),
args.join(" "),
ord_args.join(" "),
port,
server_args.join(" "),
));

let index = Arc::new(Index::open(&options).unwrap());
Expand All @@ -737,8 +799,13 @@ mod tests {
thread::sleep(Duration::from_millis(25));
}

let client = reqwest::blocking::Client::builder()
.redirect(reqwest::redirect::Policy::none())
.build()
.unwrap();

for i in 0.. {
match reqwest::blocking::get(format!("http:https://127.0.0.1:{port}/status")) {
match client.get(format!("http:https://127.0.0.1:{port}/status")).send() {
Ok(_) => break,
Err(err) => {
if i == 400 {
Expand Down Expand Up @@ -992,18 +1059,17 @@ mod tests {

#[test]
fn acme_domain_defaults_to_hostname() {
let (_, server) = parse_server_args("ord server");
assert_eq!(
Server::acme_domains(&Vec::new()).unwrap(),
server.acme_domains().unwrap(),
&[sys_info::hostname().unwrap()]
);
}

#[test]
fn acme_domain_flag_is_respected() {
assert_eq!(
Server::acme_domains(&vec!["example.com".into()]).unwrap(),
&["example.com"]
);
let (_, server) = parse_server_args("ord server --acme-domain example.com");
assert_eq!(server.acme_domains().unwrap(), &["example.com"]);
}

#[test]
Expand Down Expand Up @@ -1068,6 +1134,20 @@ mod tests {
);
}

#[test]
fn http_to_https_redirect_with_path() {
TestServer::new_with_args(&[], &["--redirect-http-to-https", "--https"]).assert_redirect(
"/sat/0",
&format!("https://{}/sat/0", sys_info::hostname().unwrap()),
);
}

#[test]
fn http_to_https_redirect_with_empty() {
TestServer::new_with_args(&[], &["--redirect-http-to-https", "--https"])
.assert_redirect("/", &format!("https://{}/", sys_info::hostname().unwrap()));
}

#[test]
fn status() {
TestServer::new().assert_response("/status", StatusCode::OK, "OK");
Expand Down Expand Up @@ -1187,7 +1267,7 @@ mod tests {

#[test]
fn output_with_sat_index() {
TestServer::new_with_args(&["--index-sats"]).assert_response_regex(
TestServer::new_with_sat_index().assert_response_regex(
"/output/4a5e1e4baab89f3a32518a88c31bc87f618f76673e2cc77ab2127b7afdeda33b:0",
StatusCode::OK,
".*<title>Output 4a5e1e4baab89f3a32518a88c31bc87f618f76673e2cc77ab2127b7afdeda33b:0</title>.*<h1>Output <span class=monospace>4a5e1e4baab89f3a32518a88c31bc87f618f76673e2cc77ab2127b7afdeda33b:0</span></h1>
Expand Down Expand Up @@ -1400,7 +1480,7 @@ mod tests {

#[test]
fn rare_with_index() {
TestServer::new_with_args(&["--index-sats"]).assert_response(
TestServer::new_with_sat_index().assert_response(
"/rare.txt",
StatusCode::OK,
"sat\tsatpoint
Expand All @@ -1411,7 +1491,7 @@ mod tests {

#[test]
fn rare_without_sat_index() {
TestServer::new_with_args(&[]).assert_response(
TestServer::new().assert_response(
"/rare.txt",
StatusCode::NOT_FOUND,
"tracking rare sats requires index created with `--index-sats` flag",
Expand All @@ -1420,7 +1500,7 @@ mod tests {

#[test]
fn show_rare_txt_in_header_with_sat_index() {
TestServer::new_with_args(&["--index-sats"]).assert_response_regex(
TestServer::new_with_sat_index().assert_response_regex(
"/",
StatusCode::OK,
".*
Expand All @@ -1432,7 +1512,7 @@ mod tests {

#[test]
fn rare_sat_location() {
TestServer::new_with_args(&["--index-sats"]).assert_response_regex(
TestServer::new_with_sat_index().assert_response_regex(
"/sat/0",
StatusCode::OK,
".*>4a5e1e4baab89f3a32518a88c31bc87f618f76673e2cc77ab2127b7afdeda33b:0:0<.*",
Expand Down Expand Up @@ -1504,7 +1584,7 @@ mod tests {

#[test]
fn outputs_traversed_are_tracked() {
let server = TestServer::new_with_args(&["--index-sats"]);
let server = TestServer::new_with_sat_index();

assert_eq!(
server
Expand Down Expand Up @@ -1536,7 +1616,7 @@ mod tests {

#[test]
fn coinbase_sat_ranges_are_tracked() {
let server = TestServer::new_with_args(&["--index-sats"]);
let server = TestServer::new_with_sat_index();

assert_eq!(
server.index.statistic(crate::index::Statistic::SatRanges),
Expand All @@ -1560,7 +1640,7 @@ mod tests {

#[test]
fn split_sat_ranges_are_tracked() {
let server = TestServer::new_with_args(&["--index-sats"]);
let server = TestServer::new_with_sat_index();

assert_eq!(
server.index.statistic(crate::index::Statistic::SatRanges),
Expand All @@ -1584,7 +1664,7 @@ mod tests {

#[test]
fn fee_sat_ranges_are_tracked() {
let server = TestServer::new_with_args(&["--index-sats"]);
let server = TestServer::new_with_sat_index();

assert_eq!(
server.index.statistic(crate::index::Statistic::SatRanges),
Expand Down Expand Up @@ -1748,7 +1828,7 @@ mod tests {

#[test]
fn inscription_page_has_sat_when_sats_are_tracked() {
let server = TestServer::new_with_args(&["--index-sats"]);
let server = TestServer::new_with_sat_index();
server.mine_blocks(1);

let inscription_id = server.bitcoin_rpc_server.broadcast_tx(TransactionTemplate {
Expand Down

0 comments on commit 47fcb7c

Please sign in to comment.