feat: boxygit add .
This commit is contained in:
parent
ba8d1b9453
commit
a8bc61f40c
15 changed files with 4700 additions and 0 deletions
2
.gitignore
vendored
Normal file
2
.gitignore
vendored
Normal file
|
@ -0,0 +1,2 @@
|
|||
/target
|
||||
examples/*/target
|
1413
Cargo.lock
generated
Normal file
1413
Cargo.lock
generated
Normal file
File diff suppressed because it is too large
Load diff
24
Cargo.toml
Normal file
24
Cargo.toml
Normal file
|
@ -0,0 +1,24 @@
|
|||
[package]
|
||||
name = "boxy"
|
||||
version = "0.1.0"
|
||||
edition = "2024"
|
||||
|
||||
[dependencies]
|
||||
hyper = { version = "1.6.0", features = ["full"]}
|
||||
tokio = { version = "1.45.1", features = ["full"]}
|
||||
http-body-util = "0.1.3"
|
||||
hyper-util = { version = "0.1.14", features = ["full"]}
|
||||
log = "0.4.27"
|
||||
pretty_env_logger = "0.5.0"
|
||||
anyhow = "1.0.98"
|
||||
thiserror = "2.0.12"
|
||||
tokio-postgres = "0.7.13"
|
||||
bcrypt = "0.17.0"
|
||||
ring = "0.17.14"
|
||||
nanoid = "0.4.0"
|
||||
serde_yaml_bw = "2.1.1"
|
||||
serde = "1.0.219"
|
||||
base64 = "0.22.1"
|
||||
string-builder = "0.2.0"
|
||||
json = "0.12.4"
|
||||
|
20
config.yaml
Normal file
20
config.yaml
Normal file
|
@ -0,0 +1,20 @@
|
|||
db:
|
||||
host: '127.0.0.1'
|
||||
user: 'postgres'
|
||||
password: 'trust'
|
||||
|
||||
proxy:
|
||||
listen: 127.0.0.1
|
||||
port: 8005
|
||||
|
||||
api:
|
||||
listen: 127.0.0.1
|
||||
port: 8006
|
||||
|
||||
hosts: # ignore this it doesn't function
|
||||
- hostname: localhost:8005
|
||||
address: localhost:8000
|
||||
|
||||
clients:
|
||||
- name: 'eu-central-1' # Example Client right here (the client in this case would be for example the stereo.cat backend)
|
||||
secret: '$2b$12$5wH/0p702PPqVp7fCpVS4.1GA2/wAbk89w2nMjwuS8439OhjCUGbK' # password123
|
2615
examples/example-server/Cargo.lock
generated
Normal file
2615
examples/example-server/Cargo.lock
generated
Normal file
File diff suppressed because it is too large
Load diff
9
examples/example-server/Cargo.toml
Normal file
9
examples/example-server/Cargo.toml
Normal file
|
@ -0,0 +1,9 @@
|
|||
[package]
|
||||
name = "example-server"
|
||||
version = "0.1.0"
|
||||
edition = "2024"
|
||||
|
||||
[dependencies]
|
||||
reqwest = { version = "0.12.22", features = ["json", "blocking"] }
|
||||
rocket = "0.5.1"
|
||||
serde_json = "1.0.141"
|
41
examples/example-server/src/main.rs
Normal file
41
examples/example-server/src/main.rs
Normal file
|
@ -0,0 +1,41 @@
|
|||
use std::{collections::HashMap, fmt::format};
|
||||
|
||||
use rocket::{build, get, launch, routes};
|
||||
use serde_json::json;
|
||||
|
||||
extern crate rocket;
|
||||
|
||||
const BOXY_ADDRESS: &str = "localhost";
|
||||
const BOXY_PORT: u16 = 8006;
|
||||
|
||||
const CLIENT_NAME: &str = "eu-central-1";
|
||||
const CLIENT_SECRET: &str = "password123";
|
||||
|
||||
#[get("/")]
|
||||
fn index() -> &'static str {
|
||||
"Hello world!"
|
||||
}
|
||||
|
||||
#[launch]
|
||||
fn rocket() -> _ {
|
||||
// This is the example backend client. (The stereo.cat backend for example).
|
||||
let client = reqwest::blocking::Client::new();
|
||||
|
||||
// We define the port of the server running locally and the hostname we want to route to it.
|
||||
let body = json!({
|
||||
"port": 8000,
|
||||
"hostname": "localhost:8005",
|
||||
});
|
||||
|
||||
// Send it to Boxy's API
|
||||
let res = client
|
||||
.post(format!("http://{}:{}/register", BOXY_ADDRESS, BOXY_PORT))
|
||||
.basic_auth(CLIENT_NAME, Some(CLIENT_SECRET))
|
||||
.json(&body)
|
||||
.send()
|
||||
.unwrap();
|
||||
|
||||
println!("{}", res.text().unwrap());
|
||||
|
||||
build().mount("/", routes![index])
|
||||
}
|
81
src/config.rs
Normal file
81
src/config.rs
Normal file
|
@ -0,0 +1,81 @@
|
|||
use std::error::Error;
|
||||
|
||||
use serde::{Deserialize, Serialize};
|
||||
use tokio::{fs::File, io::AsyncReadExt};
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize, PartialEq, Clone)]
|
||||
pub struct Proxy {
|
||||
pub listen: String,
|
||||
pub port: u16,
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize, PartialEq, Clone)]
|
||||
pub struct Db {
|
||||
pub host: String,
|
||||
pub user: String,
|
||||
pub port: Option<u16>,
|
||||
pub password: Option<String>,
|
||||
pub database: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize, PartialEq, Clone)]
|
||||
pub struct Api {
|
||||
pub listen: String,
|
||||
pub port: u16,
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize, PartialEq, Clone)]
|
||||
pub struct Client {
|
||||
pub name: String,
|
||||
pub secret: String,
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize, PartialEq, Clone)]
|
||||
pub struct Host {
|
||||
pub hostname: String,
|
||||
pub address: String,
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize, PartialEq, Clone)]
|
||||
pub struct Config {
|
||||
pub db: Db,
|
||||
pub proxy: Proxy,
|
||||
pub api: Api,
|
||||
pub clients: Vec<Client>,
|
||||
pub hosts: Vec<Host>,
|
||||
}
|
||||
|
||||
impl Db {
|
||||
pub async fn to_string(&self) -> String {
|
||||
let mut builder = String::new();
|
||||
|
||||
builder += format!(
|
||||
"host={} port={} user={} dbname={}",
|
||||
self.host,
|
||||
self.port.unwrap_or(5432),
|
||||
self.user,
|
||||
self.database.clone().unwrap_or(self.user.clone()),
|
||||
)
|
||||
.as_str();
|
||||
|
||||
match &self.password {
|
||||
Some(x) => builder += format!(" password={}", x).as_str(),
|
||||
None => {}
|
||||
}
|
||||
|
||||
builder
|
||||
}
|
||||
}
|
||||
|
||||
impl Config {
|
||||
pub async fn get() -> Result<Self, Box<dyn Error>> {
|
||||
let mut file = File::open("./config.yaml").await?;
|
||||
let mut contents = String::new();
|
||||
|
||||
file.read_to_string(&mut contents).await?;
|
||||
|
||||
let config: Self = serde_yaml_bw::from_str::<Self>(&contents)?;
|
||||
|
||||
Ok(config)
|
||||
}
|
||||
}
|
131
src/db.rs
Normal file
131
src/db.rs
Normal file
|
@ -0,0 +1,131 @@
|
|||
use std::{error::Error, net::{IpAddr, SocketAddr}};
|
||||
|
||||
use tokio_postgres::{Client, Socket, tls::MakeTlsConnect};
|
||||
|
||||
const ENDPOINT_TABLE: &str = "endpoints";
|
||||
const HOSTS_RELATION_TABLE: &str = "hosts";
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct BoxyDatabase {
|
||||
pub client: Client,
|
||||
}
|
||||
|
||||
pub struct Endpoint {
|
||||
pub id: Option<i32>,
|
||||
pub address: IpAddr,
|
||||
pub port: u16,
|
||||
pub callback: String,
|
||||
}
|
||||
|
||||
impl Endpoint {
|
||||
pub async fn new(id: Option<i32>, address: IpAddr, port: u16, callback: String) -> Self {
|
||||
Self {
|
||||
id,
|
||||
address,
|
||||
port,
|
||||
callback,
|
||||
}
|
||||
}
|
||||
pub async fn get_by_hostname(
|
||||
db: &BoxyDatabase,
|
||||
hostname: String,
|
||||
) -> Result<Vec<Self>, Box<dyn Error>> {
|
||||
let mut result: Vec<Self> = Vec::new();
|
||||
|
||||
let rows = db
|
||||
.client
|
||||
.query(
|
||||
format!(
|
||||
"SELECT {ENDPOINT_TABLE}.* FROM {HOSTS_RELATION_TABLE}
|
||||
JOIN {ENDPOINT_TABLE} ON {HOSTS_RELATION_TABLE}.endpoint_id = {ENDPOINT_TABLE}.id
|
||||
WHERE {HOSTS_RELATION_TABLE}.hostname = $1"
|
||||
).as_str(),
|
||||
&[&hostname],
|
||||
)
|
||||
.await?;
|
||||
|
||||
for row in rows {
|
||||
result.push(Self {
|
||||
id: row.get("id"),
|
||||
address: row.get("address"),
|
||||
port: row.get::<&str, i32>("port") as u16,
|
||||
callback: row.get("callback"),
|
||||
});
|
||||
}
|
||||
|
||||
Ok(result)
|
||||
}
|
||||
}
|
||||
|
||||
impl Endpoint {
|
||||
pub async fn register(
|
||||
&mut self,
|
||||
db: &mut BoxyDatabase,
|
||||
hostname: String,
|
||||
) -> Result<(), Box<dyn Error>> {
|
||||
let tx = db.client.transaction().await?;
|
||||
|
||||
let endpoint_id: i32 = tx
|
||||
.query_one(
|
||||
format!(
|
||||
"INSERT INTO {ENDPOINT_TABLE} (address,port,callback) VALUES ($1, $2, $3) RETURNING id").as_str(),
|
||||
&[
|
||||
&self.address,
|
||||
&(self.port as i32),
|
||||
&self.callback,
|
||||
],
|
||||
)
|
||||
.await?
|
||||
.get("id");
|
||||
|
||||
tx.execute(
|
||||
format!("INSERT INTO {HOSTS_RELATION_TABLE} (endpoint_id,hostname) VALUES ($1,$2)")
|
||||
.as_str(),
|
||||
&[&endpoint_id, &hostname],
|
||||
)
|
||||
.await?;
|
||||
|
||||
tx.commit().await?;
|
||||
|
||||
self.id = Some(endpoint_id);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl BoxyDatabase {
|
||||
pub async fn new(c: Client) -> Result<Self, Box<dyn Error>> {
|
||||
c.execute(
|
||||
format!(
|
||||
"CREATE TABLE IF NOT EXISTS {ENDPOINT_TABLE}
|
||||
(
|
||||
id serial PRIMARY KEY,
|
||||
address inet NOT NULL,
|
||||
port integer CHECK (port >= 0 AND port <= 65535) NOT NULL,
|
||||
callback text
|
||||
)
|
||||
"
|
||||
)
|
||||
.as_str(),
|
||||
&[],
|
||||
)
|
||||
.await?;
|
||||
|
||||
c.execute(
|
||||
format!(
|
||||
"CREATE TABLE IF NOT EXISTS {HOSTS_RELATION_TABLE}
|
||||
(
|
||||
id SERIAL PRIMARY KEY,
|
||||
endpoint_id int REFERENCES {HOSTS_RELATION_TABLE}(id),
|
||||
hostname text
|
||||
)
|
||||
"
|
||||
)
|
||||
.as_str(),
|
||||
&[],
|
||||
)
|
||||
.await?;
|
||||
|
||||
Ok(BoxyDatabase { client: c })
|
||||
}
|
||||
}
|
90
src/main.rs
Normal file
90
src/main.rs
Normal file
|
@ -0,0 +1,90 @@
|
|||
mod config;
|
||||
mod db;
|
||||
mod services;
|
||||
mod types;
|
||||
|
||||
use std::{env, sync::Arc};
|
||||
|
||||
use bcrypt::{DEFAULT_COST, bcrypt};
|
||||
use config::Config;
|
||||
use db::BoxyDatabase;
|
||||
use log::{error, info};
|
||||
use nanoid::nanoid;
|
||||
use ring::rand::SystemRandom;
|
||||
use services::{api::ApiService, controller::ControllerService};
|
||||
use tokio::{fs::File, io::AsyncReadExt, sync::Mutex};
|
||||
use tokio_postgres::{NoTls, tls::NoTlsError};
|
||||
use types::Server;
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
|
||||
pretty_env_logger::formatted_builder()
|
||||
.filter(None, log::LevelFilter::Info)
|
||||
.init();
|
||||
|
||||
let args: Vec<String> = env::args().collect();
|
||||
|
||||
if args.len() > 1 {
|
||||
match args[1].as_str() {
|
||||
"hash" => {
|
||||
if args.len() < 3 {
|
||||
error!("You need to specify a string to hash.");
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
let hash = bcrypt::hash(&args[2], DEFAULT_COST).unwrap();
|
||||
info!("Generated Hash: {}", hash);
|
||||
|
||||
return Ok(());
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
||||
let config = Config::get().await.unwrap();
|
||||
|
||||
let db_string = config.db.to_string().await;
|
||||
|
||||
info!("Database string: {}", db_string);
|
||||
|
||||
let (client, conn) = tokio_postgres::connect(db_string.as_str(), NoTls)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
tokio::spawn(async move {
|
||||
if let Err(e) = conn.await {
|
||||
error!("Error while connecting to database: {}", e);
|
||||
}
|
||||
});
|
||||
|
||||
let database = Box::new(BoxyDatabase::new(client).await.unwrap());
|
||||
|
||||
let database_shared = Arc::new(Mutex::new(Box::leak(database)));
|
||||
|
||||
let api_svc = ApiService {
|
||||
database: database_shared.clone(),
|
||||
config: config.clone(),
|
||||
_address: None
|
||||
};
|
||||
|
||||
let svc = ControllerService {
|
||||
database: database_shared,
|
||||
};
|
||||
|
||||
let api_server = Server::new(api_svc, (config.api.listen, config.api.port))
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
let proxy_server = Server::new(svc, (config.proxy.listen, config.proxy.port))
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
tokio::task::spawn(async move {
|
||||
api_server.handle().await;
|
||||
});
|
||||
|
||||
// We don't put this on a separate thread because we'd be wasting the main thread.
|
||||
proxy_server.handle().await;
|
||||
|
||||
Ok(())
|
||||
}
|
3
src/services.rs
Normal file
3
src/services.rs
Normal file
|
@ -0,0 +1,3 @@
|
|||
pub mod proxy;
|
||||
pub mod controller;
|
||||
pub mod api;
|
92
src/services/api.rs
Normal file
92
src/services/api.rs
Normal file
|
@ -0,0 +1,92 @@
|
|||
use std::{hash, net::{IpAddr, SocketAddr}, pin::Pin, sync::Arc};
|
||||
|
||||
use base64::{Engine, prelude::BASE64_STANDARD};
|
||||
use bcrypt::DEFAULT_COST;
|
||||
use http_body_util::{BodyExt, Full};
|
||||
use hyper::{
|
||||
body::{Bytes, Incoming}, service::Service, Method, Request, Response, StatusCode, Uri
|
||||
};
|
||||
use log::{info, trace};
|
||||
use tokio::{net::TcpStream, sync::Mutex};
|
||||
|
||||
use crate::{
|
||||
config::{Client, Config}, db::{BoxyDatabase, Endpoint}, types::{GeneralBody, GeneralResponse, TcpIntercept}
|
||||
};
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct ApiService {
|
||||
pub database: Arc<Mutex<&'static mut BoxyDatabase>>,
|
||||
pub config: Config,
|
||||
pub _address: Option<IpAddr>,
|
||||
}
|
||||
|
||||
async fn default_response() -> Response<http_body_util::Either<Incoming, Full<Bytes>>> {
|
||||
Response::builder()
|
||||
.status(404)
|
||||
.body(GeneralBody::Right(Full::from(Bytes::from(
|
||||
"That route doesn't exist.",
|
||||
))))
|
||||
.unwrap()
|
||||
}
|
||||
|
||||
async fn custom_resp(e: StatusCode, m: String) -> Response<http_body_util::Either<Incoming, Full<Bytes>>> {
|
||||
Response::builder()
|
||||
.status(e)
|
||||
.body(GeneralBody::Right(Full::from(Bytes::from(m))))
|
||||
.unwrap()
|
||||
}
|
||||
|
||||
impl TcpIntercept for ApiService {
|
||||
fn handle(&mut self, stream: &TcpStream) {
|
||||
self._address = Some(stream.peer_addr().unwrap().ip());
|
||||
}
|
||||
}
|
||||
|
||||
impl Service<Request<Incoming>> for ApiService {
|
||||
type Response = GeneralResponse;
|
||||
type Error = hyper::Error;
|
||||
type Future = Pin<Box<dyn Future<Output = Result<Self::Response, Self::Error>> + Send>>;
|
||||
|
||||
fn call(&self, req: Request<Incoming>) -> Self::Future {
|
||||
let database = self.database.clone();
|
||||
let config = self.config.clone();
|
||||
let address = self._address.clone().unwrap();
|
||||
Box::pin(async move {
|
||||
match *req.method() {
|
||||
Method::POST => match req.uri().path() {
|
||||
"/register" => {
|
||||
let encoded_header = req.headers().get(hyper::header::AUTHORIZATION).unwrap().to_str().unwrap();
|
||||
|
||||
let auth_string = String::from_utf8(BASE64_STANDARD.decode(&encoded_header[6..]).unwrap()).unwrap();
|
||||
|
||||
let auth_string_split: Vec<&str> = auth_string.split(':').collect();
|
||||
|
||||
let name = auth_string_split.first().unwrap();
|
||||
let secret = auth_string_split.get(1).unwrap();
|
||||
|
||||
let matched_clients: Vec<&Client> = config.clients.iter().filter(|x| x.name.eq(name)).collect();
|
||||
|
||||
let client = matched_clients.first().unwrap();
|
||||
|
||||
if !bcrypt::verify(secret, client.secret.as_str()).unwrap() {
|
||||
return Ok(custom_resp(StatusCode::UNAUTHORIZED, "Invalid credentials.".to_string()).await);
|
||||
}
|
||||
|
||||
let body = String::from_utf8(req.collect().await.unwrap().to_bytes().iter().cloned().collect::<Vec<u8>>()).unwrap();
|
||||
let json = json::parse(body.as_str()).unwrap();
|
||||
|
||||
info!("body: {}", body);
|
||||
|
||||
let mut endpoint = Endpoint::new(None, address, json["port"].as_u16().unwrap(), json["callback"].as_str().unwrap_or("/").to_string()).await;
|
||||
|
||||
endpoint.register(*database.lock().await, json["hostname"].as_str().unwrap().to_string()).await.unwrap();
|
||||
|
||||
Ok(custom_resp(StatusCode::OK, "yay".to_string()).await)
|
||||
}
|
||||
_ => Ok(default_response().await),
|
||||
},
|
||||
_ => Ok(default_response().await),
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
60
src/services/controller.rs
Normal file
60
src/services/controller.rs
Normal file
|
@ -0,0 +1,60 @@
|
|||
use std::{pin::Pin, sync::Arc};
|
||||
|
||||
use http_body_util::Full;
|
||||
use hyper::{
|
||||
Request, Response,
|
||||
body::{Bytes, Incoming},
|
||||
service::Service,
|
||||
};
|
||||
use log::error;
|
||||
use tokio::sync::Mutex;
|
||||
|
||||
use crate::{
|
||||
config::{self, Client, Config, Host},
|
||||
db::{BoxyDatabase, Endpoint},
|
||||
types::{GeneralBody, GeneralResponse, TcpIntercept},
|
||||
};
|
||||
|
||||
use super::proxy::ProxyService;
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct ControllerService {
|
||||
pub database: Arc<Mutex<&'static mut BoxyDatabase>>,
|
||||
}
|
||||
|
||||
impl TcpIntercept for ControllerService {
|
||||
fn handle(&mut self, stream: &tokio::net::TcpStream) {
|
||||
}
|
||||
}
|
||||
|
||||
impl Service<Request<Incoming>> for ControllerService {
|
||||
type Response = GeneralResponse;
|
||||
type Error = hyper::Error;
|
||||
type Future = Pin<Box<dyn Future<Output = Result<Self::Response, Self::Error>> + Send>>;
|
||||
|
||||
fn call(&self, req: Request<Incoming>) -> Self::Future {
|
||||
let database = self.database.clone();
|
||||
Box::pin(async move {
|
||||
let hostname = req
|
||||
.headers()
|
||||
.get(hyper::header::HOST)
|
||||
.unwrap()
|
||||
.to_str()
|
||||
.unwrap()
|
||||
.to_string();
|
||||
|
||||
let endpoints = Endpoint::get_by_hostname(*database.lock().await, hostname.clone())
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
let endpoint = endpoints.first().unwrap();
|
||||
|
||||
let proxy = ProxyService {
|
||||
address: format!("{}:{}", endpoint.address.clone(), endpoint.port),
|
||||
hostname,
|
||||
};
|
||||
|
||||
proxy.call(req).await
|
||||
})
|
||||
}
|
||||
}
|
50
src/services/proxy.rs
Normal file
50
src/services/proxy.rs
Normal file
|
@ -0,0 +1,50 @@
|
|||
use std::pin::Pin;
|
||||
|
||||
use hyper::{Request, body::Incoming, service::Service};
|
||||
use hyper_util::rt::TokioIo;
|
||||
use log::error;
|
||||
use tokio::net::TcpStream;
|
||||
|
||||
use crate::types::{GeneralResponse, to_general_response};
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct ProxyService {
|
||||
pub address: String,
|
||||
pub hostname: String,
|
||||
}
|
||||
|
||||
impl Service<Request<Incoming>> for ProxyService {
|
||||
type Response = GeneralResponse;
|
||||
type Error = hyper::Error;
|
||||
type Future = Pin<Box<dyn Future<Output = Result<Self::Response, Self::Error>> + Send>>;
|
||||
|
||||
fn call(&self, incoming: Request<Incoming>) -> Self::Future {
|
||||
let address = self.address.clone();
|
||||
let hostname = self.hostname.clone();
|
||||
Box::pin(async move {
|
||||
let stream = TcpStream::connect(address).await.unwrap();
|
||||
let io = TokioIo::new(stream);
|
||||
|
||||
let (mut sender, conn) = hyper::client::conn::http1::Builder::new()
|
||||
.handshake(io)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
tokio::spawn(async move {
|
||||
if let Err(err) = conn.await {
|
||||
error!("Could not open connection to backend: {err}");
|
||||
}
|
||||
});
|
||||
|
||||
let (mut parts, body_stream) = incoming.into_parts();
|
||||
|
||||
parts
|
||||
.headers
|
||||
.insert(hyper::header::HOST, hostname.parse().unwrap());
|
||||
|
||||
let req = Request::from_parts(parts, body_stream);
|
||||
|
||||
Ok(to_general_response(sender.send_request(req).await.unwrap()))
|
||||
})
|
||||
}
|
||||
}
|
69
src/types.rs
Normal file
69
src/types.rs
Normal file
|
@ -0,0 +1,69 @@
|
|||
use std::error::Error;
|
||||
|
||||
use http_body_util::{Either, Full};
|
||||
use hyper::{
|
||||
Request, Response,
|
||||
body::{Body, Bytes, Incoming},
|
||||
server::conn::http1,
|
||||
service::{HttpService, Service},
|
||||
};
|
||||
use hyper_util::rt::TokioIo;
|
||||
use log::error;
|
||||
use tokio::net::{TcpListener, TcpStream};
|
||||
|
||||
pub type GeneralResponse = Response<GeneralBody>;
|
||||
pub type GeneralBody = Either<Incoming, Full<Bytes>>;
|
||||
|
||||
pub fn to_general_response(res: Response<Incoming>) -> GeneralResponse {
|
||||
let (parts, body) = res.into_parts();
|
||||
Response::from_parts(parts, GeneralBody::Left(body))
|
||||
}
|
||||
|
||||
pub struct Server<S> {
|
||||
listener: TcpListener,
|
||||
service: S,
|
||||
}
|
||||
|
||||
pub trait TcpIntercept {
|
||||
fn handle(&mut self, stream: &TcpStream);
|
||||
}
|
||||
|
||||
impl<S> Server<S>
|
||||
where
|
||||
S: TcpIntercept,
|
||||
S: Service<Request<Incoming>> + Clone + Send + 'static,
|
||||
S: HttpService<Incoming> + Clone + Send,
|
||||
<S::ResBody as Body>::Error: Into<Box<dyn Error + Send + Sync>>,
|
||||
<S::ResBody as Body>::Data: Send,
|
||||
S::ResBody: Send,
|
||||
<S as HttpService<Incoming>>::Future: Send,
|
||||
{
|
||||
pub async fn handle(&self) {
|
||||
loop {
|
||||
let (stream, _) = self.listener.accept().await.unwrap();
|
||||
|
||||
let mut svc_clone = self.service.clone();
|
||||
svc_clone.handle(&stream);
|
||||
|
||||
let io = TokioIo::new(stream);
|
||||
tokio::task::spawn(async move {
|
||||
if let Err(err) = http1::Builder::new()
|
||||
.writev(false)
|
||||
.serve_connection(io, svc_clone)
|
||||
.await
|
||||
{
|
||||
error!("Error while trying to serve connection: {err}")
|
||||
};
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn new(service: S, a: (String, u16)) -> Result<Self, Box<dyn Error>> {
|
||||
Ok(Self {
|
||||
listener: TcpListener::bind(&a).await?,
|
||||
service,
|
||||
})
|
||||
}
|
||||
}
|
||||
/*
|
||||
*/
|
Loading…
Add table
Add a link
Reference in a new issue