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
2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "reverse_proxy"
version = "0.2.1"
version = "0.3.1"
edition = "2024"

[dependencies]
Expand Down
2 changes: 1 addition & 1 deletion LICENSE
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
BSD 3-Clause License

Copyright (c) 2022, W-lfpup
Copyright (c) 2026, Here By There LLC
All rights reserved.

Redistribution and use in source and binary forms, with or without
Expand Down
14 changes: 8 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -31,12 +31,14 @@ All filepaths can be absolute or relative to the config filepath.
"host_and_port": "<string>",
"key_filepath": "<string>",
"cert_filepath": "<string>",
"addresses": [
["<origin_address>", "<target_address>"]
],
"dangerous_self_signed_addresses": [
["<origin_address>", "<target_address>"]
]
"addresses": [{
"incoming": "origin_address",
"outgoing": "destination_address",
}],
"dangerous_self_signed_addresses": [{
"incoming": "origin_address",
"outgoing": "destination_address",
}]
}
```

Expand Down
1 change: 0 additions & 1 deletion demo/index.html

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,9 @@
"key_filepath": "./demo/self-signed-key.key",
"cert_filepath": "./demo/self-signed-cert.crt",
"addresses": [
[
"https://127.0.0.1",
"http://127.0.0.1:3000"
]
{
"incoming": "https://example.com",
"outgoing": "http://127.0.0.1:3000"
}
]
}
}
18 changes: 11 additions & 7 deletions src/addresses.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
use crate::config::Config;
use crate::config::{Address, Config};
use crate::errors::Error;
use crate::response::{AddressMap, AddressParams};
use hyper::Uri;
Expand All @@ -20,16 +20,17 @@ pub fn create_address_map(config: &Config) -> Result<AddressMap, Error> {

fn add_addresses_to_map(
url_map: &mut AddressMap,
addresses: &Vec<(String, String)>,
addresses: &Vec<Address>,
is_dangerous: bool,
) -> Result<(), Error> {
for (source_str, target_str) in addresses {
let source_uri = match Uri::try_from(source_str) {
for address in addresses {
let Address { incoming, outgoing } = address;
let incoming_uri = match Uri::try_from(incoming) {
Ok(uri) => uri,
Err(e) => return Err(Error::Uri(e)),
};

let source_host = match source_uri.host() {
let incoming_host = match incoming_uri.host() {
Some(h) => h,
_ => {
return Err(Error::Custom(
Expand All @@ -38,12 +39,15 @@ fn add_addresses_to_map(
}
};

let uri = match Uri::try_from(target_str) {
let uri = match Uri::try_from(outgoing) {
Ok(uri) => uri,
Err(e) => return Err(Error::Uri(e)),
};

url_map.insert(source_host.to_string(), AddressParams { uri, is_dangerous });
url_map.insert(
incoming_host.to_string(),
AddressParams { uri, is_dangerous },
);
}

Ok(())
Expand Down
13 changes: 8 additions & 5 deletions src/config.rs
Original file line number Diff line number Diff line change
@@ -1,22 +1,25 @@
use crate::errors::Error;
use serde::{Deserialize, Serialize};
use serde_json;
use std::path;
use std::path::PathBuf;
use tokio::fs;

use crate::errors::Error;
#[derive(Clone, Serialize, Deserialize, Debug)]
pub struct Address {
pub incoming: String,
pub outgoing: String,
}

#[derive(Clone, Serialize, Deserialize, Debug)]
pub struct Config {
pub host_and_port: String,
pub key_filepath: PathBuf,
pub cert_filepath: PathBuf,
pub addresses: Vec<(String, String)>,
pub dangerous_self_signed_addresses: Option<Vec<(String, String)>>,
pub addresses: Vec<Address>,
pub dangerous_self_signed_addresses: Option<Vec<Address>>,
}

// Serde path fs errors in one function

pub async fn from_filepath(filepath: &PathBuf) -> Result<Config, Error> {
let config_path = match path::absolute(filepath) {
Ok(pb) => pb,
Expand Down
5 changes: 2 additions & 3 deletions src/main.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
use crate::errors::Error;
use hyper_util::rt::{TokioExecutor, TokioIo};
use hyper_util::server::conn::auto::Builder;
use native_tls::Identity;
Expand All @@ -13,8 +14,6 @@ mod requests;
mod response;
mod service;

use crate::errors::Error;

#[tokio::main]
async fn main() -> Result<(), Error> {
// create config
Expand Down Expand Up @@ -64,7 +63,7 @@ async fn main() -> Result<(), Error> {
Err(e) => return Err(Error::Io(e)),
};

println!("Reverse Proxy: {}", &config.host_and_port);
println!("Reverse proxy @ {}", &config.host_and_port);

loop {
let (socket, _remote_addr) = match listener.accept().await {
Expand Down
1 change: 0 additions & 1 deletion src/requests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,6 @@ fn get_host_and_authority<'a>(uri: &Uri) -> Result<(String, String), &'a str> {
let host = match uri.host() {
Some(h) => h.to_string(),
_ => return Err("failed to retrieve downstream URI from upstream URI"),
// _ => return Err(Error::Custom("failed to retrieve downstream URI from upstream URI".to_string())),
};

let port = match uri.port() {
Expand Down
99 changes: 22 additions & 77 deletions src/response.rs
Original file line number Diff line number Diff line change
@@ -1,17 +1,15 @@
use crate::requests;
use bytes::Bytes;
use http_body_util::combinators::BoxBody;
use hyper::body::Incoming;
use hyper::http::uri::InvalidUriParts;
use hyper::{Request, Response, StatusCode, Uri, header};
use hyper::{Request, Response, StatusCode, Uri};
use std::collections::HashMap;
use std::sync::Arc;

use crate::requests;

pub type BoxedResponse = Response<BoxBody<Bytes, hyper::Error>>;

const URI_FROM_REQUEST_ERROR: &str = "unable to parse upstream URI from request";
const UPSTREAM_URI_ERROR: &str = "falied to update request with upstream URI";
pub const URI_FROM_REQUEST_ERROR: &str = "unable to parse upstream URI from request";
pub const UPSTREAM_URI_ERROR: &str = "falied to update request with upstream URI";

#[derive(Clone, Debug)]
pub struct AddressParams {
Expand All @@ -21,74 +19,10 @@ pub struct AddressParams {

pub type AddressMap = HashMap<String, AddressParams>;

pub async fn build_response(
mut req: Request<Incoming>,
addresses: Arc<HashMap<String, AddressParams>>,
) -> Result<BoxedResponse, hyper::http::Error> {
let host = match get_host(&req) {
Some(uri) => uri,
_ => {
return requests::create_fallback_response(
&StatusCode::BAD_REQUEST,
&URI_FROM_REQUEST_ERROR,
);
}
};

// get target uri
let address_params = match addresses.get(&host) {
Some(params) => params,
_ => {
return requests::create_fallback_response(
&StatusCode::BAD_GATEWAY,
&URI_FROM_REQUEST_ERROR,
);
}
};

if let Err(_) = update_request_with_dest_uri(&mut req, &address_params.uri) {
return requests::create_fallback_response(
&StatusCode::INTERNAL_SERVER_ERROR,
&UPSTREAM_URI_ERROR,
);
};

get_response(req, address_params.is_dangerous).await
}

fn get_host(req: &Request<Incoming>) -> Option<String> {
// http2
if let Some(host) = req.uri().host() {
return Some(host.to_string());
};

// http 1
let host_header = match req.headers().get(header::HOST) {
Some(h) => h,
_ => return None,
};

let host_str = match host_header.to_str() {
Ok(hs) => hs,
_ => return None,
};

let host_as_uri = match Uri::try_from(host_str) {
Ok(hau) => hau,
_ => return None,
};

if let Some(host) = host_as_uri.host() {
return Some(host.to_string());
}

None
}

fn update_request_with_dest_uri(
req: &mut Request<Incoming>,
mut req: Request<Incoming>,
target_uri: &Uri,
) -> Result<(), InvalidUriParts> {
) -> Result<Request<Incoming>, InvalidUriParts> {
let mut dest_parts = target_uri.clone().into_parts();

dest_parts.path_and_query = None;
Expand All @@ -101,25 +35,36 @@ fn update_request_with_dest_uri(
Err(e) => return Err(e),
};

Ok(())
Ok(req)
}

async fn get_response(
pub async fn build_response(
req: Request<Incoming>,
is_dangerous: bool,
address_params: AddressParams,
) -> Result<BoxedResponse, hyper::http::Error> {
let version = req.version();

let req = match update_request_with_dest_uri(req, &address_params.uri) {
Ok(r) => r,
_ => {
return requests::create_fallback_response(
&StatusCode::INTERNAL_SERVER_ERROR,
&UPSTREAM_URI_ERROR,
);
}
};

let scheme = match req.uri().scheme() {
Some(a) => a.as_str(),
_ => "http",
};

match (version, scheme) {
(hyper::Version::HTTP_2, "https") => {
requests::send_http2_tls_request(req, is_dangerous).await
requests::send_http2_tls_request(req, address_params.is_dangerous).await
}
(hyper::Version::HTTP_2, _) => requests::send_http2_request(req).await,
(_, "https") => requests::send_http1_tls_request(req, is_dangerous).await,
(_, "https") => requests::send_http1_tls_request(req, address_params.is_dangerous).await,
_ => requests::send_http1_request(req).await,
}
}
61 changes: 59 additions & 2 deletions src/service.rs
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
use crate::requests;
use crate::response;
use crate::response::{AddressMap, BoxedResponse, build_response};
use hyper::Request;
use hyper::body::Incoming;
use hyper::http;
use hyper::service::Service;
use hyper::{Request, StatusCode, Uri, header};
use std::future::Future;
use std::pin::Pin;
use std::sync::Arc;
Expand All @@ -18,6 +20,61 @@ impl Service<Request<Incoming>> for Svc {

fn call(&self, req: Request<Incoming>) -> Self::Future {
let addresses = self.addresses.clone();
Box::pin(async move { build_response(req, addresses).await })

let host = match get_host(&req) {
Some(uri) => uri,
_ => {
return Box::pin(async move {
requests::create_fallback_response(
&StatusCode::BAD_REQUEST,
&response::URI_FROM_REQUEST_ERROR,
)
});
}
};

// get target uri
let address_params = match addresses.get(&host) {
Some(params) => params.clone(),
_ => {
return Box::pin(async move {
requests::create_fallback_response(
&StatusCode::BAD_GATEWAY,
&response::URI_FROM_REQUEST_ERROR,
)
});
}
};

Box::pin(async move { build_response(req, address_params).await })
}
}

pub fn get_host(req: &Request<Incoming>) -> Option<String> {
// http2
if let Some(host) = req.uri().host() {
return Some(host.to_string());
};

// http 1
let host_header = match req.headers().get(header::HOST) {
Some(h) => h,
_ => return None,
};

let host_str = match host_header.to_str() {
Ok(hs) => hs,
_ => return None,
};

let host_as_uri = match Uri::try_from(host_str) {
Ok(hau) => hau,
_ => return None,
};

if let Some(host) = host_as_uri.host() {
return Some(host.to_string());
}

None
}
Loading