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
Binary file added .DS_Store
Binary file not shown.
2 changes: 0 additions & 2 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,5 @@ Cargo.lock
*.pdb

# REPO ignores
demo/

*.key
*.crt
17 changes: 7 additions & 10 deletions Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,18 +1,15 @@
[workspace]
resolver = "2"
members = ["config", "reverse_proxy"]
members = ["response", "reverse_proxy"]

[workspace.dependencies]
hyper = { version = "1", features = ["full"] }
tokio = { version = "1", features = ["full"] }
bytes = "1"
tokio-util = "0.7"
tokio-native-tls = { version = "0.3" }
http-body-util = "0.1"
hyper = { version = "1", features = ["full"] }
hyper-util = { version = "0.1", features = ["full"] }
native-tls = "0.2"
http = "1"

futures-util = { version = "0.3", default-features = false }
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"
serde = { version = "1", features = ["derive"] }
serde_json = "1"
tokio = { version = "1", features = ["full"] }
tokio-native-tls = { version = "0.3" }
tokio-util = "0.7"
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, here by there
Copyright (c) 2022, W-lfpup
All rights reserved.

Redistribution and use in source and binary forms, with or without
Expand Down
8 changes: 2 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,13 +1,11 @@
# reverse_proxy
# Reverse Proxy

Route requests to local or upstream servers.

## About

A reverse proxy written in rust using [tokio](https://tokio.rs/) and
[hyper](https://hyper.rs/).

`reverse_proxy` forwards incoming encrypted http1 and http2 requests to upstream servers.
[hyper](https://hyper.rs/) that forwards incoming encrypted http1 and http2 requests to upstream servers.

## How to use

Expand Down Expand Up @@ -68,8 +66,6 @@ This optional property is intended to forward requests to servers using self-sig
reverse_proxy ./path/to/config.json
```

Open a browser and visit `https://localhost:XXXX`.

## Licence

`reverse_proxy` is released under the BSD 3-Clause License
11 changes: 0 additions & 11 deletions config/Cargo.toml

This file was deleted.

1 change: 1 addition & 0 deletions demo/index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
<p>hello!</p>
15 changes: 15 additions & 0 deletions response/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
[package]
name = "response"
version = "0.1.0"
edition = "2021"

# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html

[dependencies]
bytes = { workspace = true }
http-body-util = { workspace = true }
hyper = { workspace = true }
hyper-util = { workspace = true }
native-tls = { workspace = true }
tokio = { workspace = true }
tokio-native-tls = { workspace = true }
61 changes: 53 additions & 8 deletions reverse_proxy/src/requests.rs → response/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,21 +1,23 @@
use bytes::Bytes;
use http_body_util::combinators::BoxBody;
use http_body_util::{BodyExt, Full};
use hyper::body::Incoming;
use hyper::client::conn::{http1, http2};
use hyper::header;
use hyper::Uri;
use hyper::{Request, Response, StatusCode};
use hyper_util::rt::TokioExecutor;
use hyper_util::rt::TokioIo;
use hyper::{header, Request, Response, StatusCode, Uri};
use hyper_util::rt::{TokioExecutor, TokioIo};
use native_tls::TlsConnector;
use std::collections::HashMap;
use std::sync::Arc;
use tokio::net::TcpStream;

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

const UPSTREAM_HANDSHAKE_ERROR: &str = "upstream handshake failed";
const FAILED_TO_PROCESS_REQUEST_ERROR: &str = "failed to process request";
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 fn get_host(req: &Request<Incoming>) -> Option<String> {
fn get_host(req: &Request<Incoming>) -> Option<String> {
// http2
if let Some(host) = req.uri().host() {
return Some(host.to_string());
Expand Down Expand Up @@ -70,7 +72,7 @@ pub fn create_fallback_response(
.status(status_code)
.header(header::CONTENT_TYPE, "text/html; charset=utf-8")
.body(
Full::new(bytes::Bytes::from(body_str))
Full::new(Bytes::from(body_str))
.map_err(|e| match e {})
.boxed(),
)
Expand Down Expand Up @@ -268,3 +270,46 @@ async fn send_http2_tls_request(

create_fallback_response(&StatusCode::BAD_GATEWAY, &FAILED_TO_PROCESS_REQUEST_ERROR)
}

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

// get target uri
let (target_uri, is_dangerous) = match addresses.get(&host) {
Some((trgt_uri, is_dngrs)) => (trgt_uri.clone(), is_dngrs.clone()),
_ => return create_fallback_response(&StatusCode::BAD_GATEWAY, &URI_FROM_REQUEST_ERROR),
};

// replace dest_uri path and query with target path and query
if let Err(_) = update_request_with_dest_uri(&mut req, target_uri) {
return create_fallback_response(&StatusCode::INTERNAL_SERVER_ERROR, &UPSTREAM_URI_ERROR);
};

get_response(req, is_dangerous).await
}

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

// start with no path
dest_parts.path_and_query = None;
if let Some(path_and_query) = req.uri().path_and_query() {
dest_parts.path_and_query = Some(path_and_query.clone());
}

*req.uri_mut() = match Uri::from_parts(dest_parts) {
Ok(u) => u,
Err(e) => return Err(e.to_string()),
};

Ok(())
}
8 changes: 3 additions & 5 deletions reverse_proxy/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -6,14 +6,12 @@ edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html

[dependencies]
bytes = { workspace = true }
config = { path="../config" }
futures-util = { workspace = true }
http = { workspace = true }
http-body-util = { workspace = true }
hyper = { workspace = true }
hyper-util = { workspace = true }
native-tls = { workspace = true }
tokio = { workspace = true }
tokio-native-tls = { workspace = true }
tokio-util = { workspace = true }
serde = { workspace = true }
serde_json = { workspace = true }
response = { path = "../response" }
3 changes: 2 additions & 1 deletion reverse_proxy/src/addresses.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
use config::Config;
use hyper::Uri;
use std::collections::HashMap;

use crate::config::Config;

pub fn create_address_map(config: &Config) -> Result<HashMap<String, (Uri, bool)>, String> {
// get host incoming
let mut hashmap = HashMap::<String, (Uri, bool)>::new();
Expand Down
9 changes: 5 additions & 4 deletions config/src/lib.rs → reverse_proxy/src/config.rs
Original file line number Diff line number Diff line change
@@ -1,8 +1,7 @@
use serde::{Deserialize, Serialize};
use serde_json;
use std::env;
use std::path;
use std::path::PathBuf;
use std::{env, path};
use tokio::fs;

#[derive(Clone, Serialize, Deserialize, Debug)]
Expand Down Expand Up @@ -46,7 +45,8 @@ pub async fn from_filepath(filepath: &PathBuf) -> Result<Config, String> {
Ok(j) => j,
Err(e) => return Err(e.to_string()),
};
if key_filepath.is_dir() {

if !key_filepath.is_file() {
return Err(
"failed to create absolute path from relative path for key_filepath".to_string(),
);
Expand All @@ -56,7 +56,8 @@ pub async fn from_filepath(filepath: &PathBuf) -> Result<Config, String> {
Ok(j) => j,
Err(e) => return Err(e.to_string()),
};
if cert_filepath.is_dir() {

if !cert_filepath.is_file() {
return Err(
"failed to create absolute path from relative path for cert_filepath".to_string(),
);
Expand Down
4 changes: 2 additions & 2 deletions reverse_proxy/src/main.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
use config;
use hyper_util::rt::{TokioExecutor, TokioIo};
use hyper_util::server::conn::auto::Builder;
use native_tls::Identity;
Expand All @@ -8,7 +7,7 @@ use tokio::fs;
use tokio::net::TcpListener;

mod addresses;
mod requests;
mod config;
mod service;

#[tokio::main]
Expand Down Expand Up @@ -81,6 +80,7 @@ async fn main() -> Result<(), String> {
.await
{
// log tls error
return;
}
});
}
Expand Down
77 changes: 6 additions & 71 deletions reverse_proxy/src/service.rs
Original file line number Diff line number Diff line change
@@ -1,90 +1,25 @@
use http::uri::Scheme;
use http::Uri;
use hyper::body::Incoming;
use hyper::service::Service;
use hyper::{Request, StatusCode};
use hyper::{Request, Uri};
use std::collections::HashMap;
use std::future::Future;
use std::pin::Pin;
use std::sync::Arc;

use crate::requests;

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";
use response;

pub struct Svc {
pub addresses: Arc<HashMap<String, (Uri, bool)>>,
}

impl Service<Request<Incoming>> for Svc {
type Response = requests::BoxedResponse;
type Response = response::BoxedResponse;
type Error = hyper::http::Error;
type Future = Pin<Box<dyn Future<Output = Result<Self::Response, Self::Error>> + Send>>;

fn call(&self, mut req: Request<Incoming>) -> Self::Future {
// get origin host from request
let host = match requests::get_host(&req) {
Some(uri) => uri,
_ => {
return Box::pin(async {
// bad request
requests::create_fallback_response(
&StatusCode::BAD_REQUEST,
&URI_FROM_REQUEST_ERROR,
)
});
}
};

// get target uri
let (target_uri, is_dangerous) = match self.addresses.get(&host) {
Some((trgt_uri, is_dngrs)) => (trgt_uri.clone(), is_dngrs.clone()),
_ => {
return Box::pin(async {
// bad request
requests::create_fallback_response(
&StatusCode::BAD_GATEWAY,
&URI_FROM_REQUEST_ERROR,
)
});
}
};

// replace dest_uri path and query with target path and query
if let Err(_) = update_request_with_dest_uri(&mut req, target_uri) {
return Box::pin(async {
requests::create_fallback_response(
&StatusCode::INTERNAL_SERVER_ERROR,
&UPSTREAM_URI_ERROR,
)
});
};
fn call(&self, req: Request<Incoming>) -> Self::Future {
let addresses = self.addresses.clone();

return Box::pin(async move { requests::get_response(req, is_dangerous).await });
Box::pin(async move { response::build_response(req, addresses).await })
}
}

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

if let None = dest_parts.scheme {
dest_parts.scheme = Some(Scheme::HTTP);
}

// start with no path
dest_parts.path_and_query = None;
if let Some(path_and_query) = req.uri().path_and_query() {
dest_parts.path_and_query = Some(path_and_query.clone());
}

*req.uri_mut() = match Uri::from_parts(dest_parts) {
Ok(u) => u,
Err(e) => return Err(e.to_string()),
};

Ok(())
}
6 changes: 3 additions & 3 deletions gateway.example.json → reverse_proxy_config.example.json
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
{
"host_and_port": "127.0.0.1:3000",
"host_and_port": "0.0.0.0:4000",
"key_filepath": "./demo/self-signed-key.key",
"cert_filepath": "./demo/self-signed-cert.crt",
"addresses": [
["helloworld.com", "http://127.0.0.1:4000"]
["127.0.0.1:4000", "http://127.0.0.1:3000"]
],
"dangerous_self_signed_addresses": [
["goodbyeforever.com", "https://127.0.0.1:5000"]
["goodbyeforever.com", "https://127.0.0.1:3001"]
]
}