init commit
This commit is contained in:
commit
2f1a435dda
12
Cargo.toml
Normal file
12
Cargo.toml
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
[package]
|
||||||
|
name = "http_rrs"
|
||||||
|
version = "0.1.0"
|
||||||
|
edition = "2021"
|
||||||
|
|
||||||
|
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
openssl = { version = "0.10.64", features = ["vendored"] }
|
||||||
|
yaml-rust = "0.4.5"
|
||||||
|
log = "0.4.21"
|
||||||
|
log4rs = "1.3.0"
|
26
README.md
Normal file
26
README.md
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
# HttpRRS
|
||||||
|
HTTP request redirection system
|
||||||
|
|
||||||
|
Default `conf.yml`:
|
||||||
|
```yml
|
||||||
|
http_host: localhost:80
|
||||||
|
https_host: localhost:443
|
||||||
|
|
||||||
|
sites:
|
||||||
|
# - domain: example.com # Domain with SSL
|
||||||
|
# host: localhost:8080
|
||||||
|
# ssl_cert: "/path/to/public/certificate.txt"
|
||||||
|
# ssl_key: "/path/to/private/key.txt"
|
||||||
|
|
||||||
|
# - domain: sub.example.com # Domain with no SSL
|
||||||
|
# host: localhost:8081
|
||||||
|
|
||||||
|
- domain: localhost
|
||||||
|
host: localhost:8080
|
||||||
|
```
|
||||||
|
|
||||||
|
## How it works
|
||||||
|
|
||||||
|
This works as a proxy that redirects based on the Host header
|
||||||
|
|
||||||
|

|
14
conf.yml
Normal file
14
conf.yml
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
http_host: localhost:80
|
||||||
|
https_host: localhost:443
|
||||||
|
|
||||||
|
sites:
|
||||||
|
# - domain: example.com # Domain with SSL
|
||||||
|
# host: localhost:8080
|
||||||
|
# ssl_cert: "/path/to/public/certificate.txt"
|
||||||
|
# ssl_key: "/path/to/private/key.txt"
|
||||||
|
|
||||||
|
# - domain: sub.example.com # Domain with no SSL
|
||||||
|
# host: localhost:8081
|
||||||
|
|
||||||
|
- domain: localhost
|
||||||
|
host: localhost:8080
|
BIN
explaination.png
Normal file
BIN
explaination.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 48 KiB |
590
src/http_server.rs
Normal file
590
src/http_server.rs
Normal file
@ -0,0 +1,590 @@
|
|||||||
|
use http_rrs::ThreadPool;
|
||||||
|
use log::{debug, info, warn};
|
||||||
|
use openssl::ssl::{
|
||||||
|
NameType, SniError, SslAcceptor, SslAlert, SslContext, SslFiletype, SslMethod, SslRef,
|
||||||
|
SslStream,
|
||||||
|
};
|
||||||
|
use std::time::Duration;
|
||||||
|
use std::{
|
||||||
|
io::{Read, Write},
|
||||||
|
net::{IpAddr, Shutdown, TcpListener, TcpStream},
|
||||||
|
sync::Arc,
|
||||||
|
thread,
|
||||||
|
};
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub struct SslCert {
|
||||||
|
pub cert_file: String,
|
||||||
|
pub key_file: String,
|
||||||
|
pub ctx_index: u8,
|
||||||
|
pub ctx: Option<SslContext>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl SslCert {
|
||||||
|
pub fn generate_ctx(cert_file: &str, key_file: &str) -> Option<SslContext> {
|
||||||
|
let mut ctx = match SslContext::builder(SslMethod::tls()) {
|
||||||
|
Ok(i) => i,
|
||||||
|
Err(_) => return None,
|
||||||
|
};
|
||||||
|
match ctx.set_private_key_file(&key_file, SslFiletype::PEM) {
|
||||||
|
Ok(i) => i,
|
||||||
|
Err(_) => return None,
|
||||||
|
};
|
||||||
|
match ctx.set_certificate_file(&cert_file, SslFiletype::PEM) {
|
||||||
|
Ok(i) => i,
|
||||||
|
Err(_) => return None,
|
||||||
|
};
|
||||||
|
match ctx.check_private_key() {
|
||||||
|
Ok(i) => i,
|
||||||
|
Err(_) => return None,
|
||||||
|
};
|
||||||
|
Some(ctx.build())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn new(cert_file: &str, key_file: &str) -> Option<SslCert> {
|
||||||
|
let ctx = match Self::generate_ctx(cert_file, key_file) {
|
||||||
|
Some(i) => Some(i),
|
||||||
|
None => {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
Some(SslCert {
|
||||||
|
ctx: ctx,
|
||||||
|
cert_file: cert_file.to_string(),
|
||||||
|
key_file: key_file.to_string(),
|
||||||
|
ctx_index: 0,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_ctx(&mut self) -> Option<&SslContext> {
|
||||||
|
// self.ctx_index += 1;
|
||||||
|
// if self.ctx_index > 5 {
|
||||||
|
// self.ctx_index = 0;
|
||||||
|
// self.ctx = Self::generate_ctx(&self.cert_file, &self.key_file);
|
||||||
|
// }
|
||||||
|
self.ctx.as_ref()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub struct Site {
|
||||||
|
pub domain: String,
|
||||||
|
pub host: String,
|
||||||
|
pub ssl: Option<SslCert>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Site {
|
||||||
|
fn connect(self) -> Result<TcpStream, String> {
|
||||||
|
match TcpStream::connect(self.host) {
|
||||||
|
Ok(i) => Ok(i),
|
||||||
|
Err(_) => Err("server not canacting".to_string()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub struct SiteServer {
|
||||||
|
host: String,
|
||||||
|
sites: Arc<Vec<Site>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
fn split_once<'a>(in_string: &'a str, separator: &str) -> Result<(&'a str, &'a str), String> {
|
||||||
|
let mut splitter = in_string.splitn(2, &separator);
|
||||||
|
let first = match splitter.next() {
|
||||||
|
Some(i) => i,
|
||||||
|
None => return Err("first split nat foined".to_string()),
|
||||||
|
};
|
||||||
|
let second = match splitter.next() {
|
||||||
|
Some(i) => i,
|
||||||
|
None => return Err("escond split nat foined".to_string()),
|
||||||
|
};
|
||||||
|
Ok((first, second))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_site(sites: &Vec<Site>, domain: &str) -> Option<Site> {
|
||||||
|
for i in sites.iter() {
|
||||||
|
if i.domain == domain {
|
||||||
|
return Some(i.clone());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
|
||||||
|
impl SiteServer {
|
||||||
|
pub fn new<'a>(host: String, sites: Arc<Vec<Site>>) -> Self {
|
||||||
|
SiteServer { host, sites }
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_site(self, domain: &str) -> Option<Site> {
|
||||||
|
return get_site(&self.sites, domain);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl SiteServer {
|
||||||
|
pub fn start_http(self) {
|
||||||
|
thread::spawn(move || {
|
||||||
|
self.run_http();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn run_http(self) {
|
||||||
|
let listener: TcpListener = match TcpListener::bind(&self.host) {
|
||||||
|
Ok(i) => i,
|
||||||
|
Err(_) => {
|
||||||
|
info!("Http server listener bind error");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
let pool = ThreadPool::new(10);
|
||||||
|
|
||||||
|
info!("HTTP server runned on {}", &self.host);
|
||||||
|
|
||||||
|
for stream in listener.incoming() {
|
||||||
|
let local_self = self.clone();
|
||||||
|
|
||||||
|
pool.execute(move || {
|
||||||
|
let mut stream = match stream {
|
||||||
|
Ok(i) => i,
|
||||||
|
Err(e) => {
|
||||||
|
warn!("{}", e);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
match stream.set_write_timeout(Some(Duration::from_secs(10))) {
|
||||||
|
Ok(i) => i,
|
||||||
|
Err(_) => {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
match stream.set_read_timeout(Some(Duration::from_secs(10))) {
|
||||||
|
Ok(i) => i,
|
||||||
|
Err(_) => {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let addr = stream.peer_addr();
|
||||||
|
|
||||||
|
match local_self.accept_http(
|
||||||
|
&mut stream,
|
||||||
|
match addr {
|
||||||
|
Ok(v) => v,
|
||||||
|
Err(_) => {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
) {
|
||||||
|
Ok(v) => {
|
||||||
|
let (addr, method, host, page) = v;
|
||||||
|
info!("{} > {} http://{}{}", addr, method, host, page);
|
||||||
|
}
|
||||||
|
Err(_) => {}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn run_ssl(self) {
|
||||||
|
let listener: TcpListener = match TcpListener::bind(&self.host) {
|
||||||
|
Ok(i) => i,
|
||||||
|
Err(_) => {
|
||||||
|
info!("Ssl server listener bind error");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let mut cert = match SslAcceptor::mozilla_intermediate(SslMethod::tls()) {
|
||||||
|
Ok(v) => v,
|
||||||
|
Err(_) => {
|
||||||
|
info!("Ssl acceptor create error");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let sites = self.clone().sites.clone();
|
||||||
|
|
||||||
|
cert.set_servername_callback(Box::new(
|
||||||
|
move |_ssl: &mut SslRef, _alert: &mut SslAlert| -> Result<(), SniError> {
|
||||||
|
debug!("hangs");
|
||||||
|
let servname = match _ssl.servername(NameType::HOST_NAME) {
|
||||||
|
Some(i) => i,
|
||||||
|
None => return Err(SniError::NOACK),
|
||||||
|
};
|
||||||
|
let cert = match get_site(&sites, servname) {
|
||||||
|
Some(i) => i,
|
||||||
|
None => return Err(SniError::NOACK),
|
||||||
|
};
|
||||||
|
match _ssl.set_ssl_context(
|
||||||
|
match match cert.ssl {
|
||||||
|
Some(i) => i,
|
||||||
|
None => return Err(SniError::NOACK),
|
||||||
|
}
|
||||||
|
.get_ctx()
|
||||||
|
{
|
||||||
|
Some(k) => k,
|
||||||
|
None => return Err(SniError::NOACK),
|
||||||
|
},
|
||||||
|
) {
|
||||||
|
Ok(i) => i,
|
||||||
|
Err(_) => return Err(SniError::NOACK),
|
||||||
|
};
|
||||||
|
return Ok(());
|
||||||
|
},
|
||||||
|
));
|
||||||
|
|
||||||
|
let cert = cert.build();
|
||||||
|
|
||||||
|
let pool = ThreadPool::new(10);
|
||||||
|
|
||||||
|
info!("HTTPS server runned on {}", &self.host);
|
||||||
|
|
||||||
|
for stream in listener.incoming() {
|
||||||
|
let local_self = self.clone();
|
||||||
|
let local_cert = cert.clone();
|
||||||
|
|
||||||
|
pool.execute(move || {
|
||||||
|
let stream = match stream {
|
||||||
|
Ok(i) => {
|
||||||
|
debug!("norm esy");
|
||||||
|
i
|
||||||
|
}
|
||||||
|
Err(_) => {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
match stream.set_write_timeout(Some(Duration::from_secs(10))) {
|
||||||
|
Ok(i) => i,
|
||||||
|
Err(_) => {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
match stream.set_read_timeout(Some(Duration::from_secs(10))) {
|
||||||
|
Ok(i) => i,
|
||||||
|
Err(_) => {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let addr = stream.peer_addr();
|
||||||
|
|
||||||
|
let mut stream = match local_cert.accept(stream) {
|
||||||
|
Ok(st) => {
|
||||||
|
debug!("ssl esy");
|
||||||
|
st
|
||||||
|
}
|
||||||
|
Err(_) => {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
match local_self.accept_ssl(
|
||||||
|
&mut stream,
|
||||||
|
match addr {
|
||||||
|
Ok(v) => v,
|
||||||
|
Err(_) => {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
) {
|
||||||
|
Ok(v) => {
|
||||||
|
let (addr, method, host, page) = v;
|
||||||
|
info!("{} > {} https://{}{}", addr, method, host, page);
|
||||||
|
}
|
||||||
|
Err(_) => {}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn accept_http(
|
||||||
|
self,
|
||||||
|
stream: &mut TcpStream,
|
||||||
|
peer_addr: std::net::SocketAddr,
|
||||||
|
) -> Result<(String, String, String, String), ()> {
|
||||||
|
let octets = match peer_addr.ip() {
|
||||||
|
IpAddr::V4(ip) => ip.octets(),
|
||||||
|
_ => [127, 0, 0, 1],
|
||||||
|
};
|
||||||
|
|
||||||
|
let dot: String = String::from(".");
|
||||||
|
let ip_str = String::from(
|
||||||
|
octets[0].to_string()
|
||||||
|
+ &dot
|
||||||
|
+ &octets[1].to_string()
|
||||||
|
+ &dot
|
||||||
|
+ &octets[2].to_string()
|
||||||
|
+ &dot
|
||||||
|
+ &octets[3].to_string(),
|
||||||
|
);
|
||||||
|
|
||||||
|
println!("{}", &ip_str);
|
||||||
|
|
||||||
|
let addition: String = ip_str.clone() + ":" + peer_addr.port().to_string().as_str() + "\n";
|
||||||
|
|
||||||
|
let mut reqst_data: Vec<u8> = vec![0; 4096];
|
||||||
|
|
||||||
|
match stream.read(&mut reqst_data) {
|
||||||
|
Ok(i) => i,
|
||||||
|
Err(_) => return Err(()),
|
||||||
|
};
|
||||||
|
|
||||||
|
let reqst = match String::from_utf8(reqst_data) {
|
||||||
|
Ok(v) => v,
|
||||||
|
Err(_) => {
|
||||||
|
return Err(());
|
||||||
|
}
|
||||||
|
};
|
||||||
|
let reqst = reqst.trim_matches(char::from(0));
|
||||||
|
|
||||||
|
let (head, body) = match split_once(&reqst, "\r\n\r\n") {
|
||||||
|
Ok(i) => i,
|
||||||
|
Err(_) => return Err(()),
|
||||||
|
};
|
||||||
|
|
||||||
|
let mut head_lines = head.split("\r\n");
|
||||||
|
|
||||||
|
let status = match head_lines.next() {
|
||||||
|
Some(i) => i,
|
||||||
|
None => return Err(()),
|
||||||
|
};
|
||||||
|
let status: Vec<&str> = status.split(" ").collect();
|
||||||
|
|
||||||
|
let mut host: &str = "honk";
|
||||||
|
let mut content_length: usize = 0;
|
||||||
|
|
||||||
|
for l in head_lines {
|
||||||
|
let (key, value) = match split_once(&l, ": ") {
|
||||||
|
Ok(i) => i,
|
||||||
|
Err(_) => return Err(()),
|
||||||
|
};
|
||||||
|
let key = key.to_lowercase().replace("-", "_");
|
||||||
|
|
||||||
|
if key == "host" {
|
||||||
|
host = &value;
|
||||||
|
}
|
||||||
|
if key == "content_length" {
|
||||||
|
content_length = match value.parse() {
|
||||||
|
Ok(i) => i,
|
||||||
|
Err(_) => {
|
||||||
|
return Err(());
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let site = self.get_site(host);
|
||||||
|
|
||||||
|
if site.is_none() {
|
||||||
|
return Err(());
|
||||||
|
}
|
||||||
|
|
||||||
|
let site = match site {
|
||||||
|
Some(i) => i,
|
||||||
|
None => return Err(()),
|
||||||
|
};
|
||||||
|
let mut site_stream = match site.connect() {
|
||||||
|
Ok(i) => i,
|
||||||
|
Err(_) => return Err(()),
|
||||||
|
};
|
||||||
|
|
||||||
|
match site_stream.write((addition + reqst).as_bytes()) {
|
||||||
|
Ok(i) => i,
|
||||||
|
Err(_) => {
|
||||||
|
return Err(());
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let body_len = body.len();
|
||||||
|
if body_len < content_length {
|
||||||
|
let mut body_data: Vec<u8> = vec![0; content_length - body_len];
|
||||||
|
match stream.read_exact(&mut body_data) {
|
||||||
|
Ok(i) => i,
|
||||||
|
Err(_) => return Err(()),
|
||||||
|
};
|
||||||
|
match site_stream.write_all(&body_data) {
|
||||||
|
Ok(i) => i,
|
||||||
|
Err(_) => return Err(()),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
loop {
|
||||||
|
let mut buf: Vec<u8> = Vec::new();
|
||||||
|
match site_stream.read_to_end(&mut buf) {
|
||||||
|
Ok(i) => i,
|
||||||
|
Err(_) => return Err(()),
|
||||||
|
};
|
||||||
|
if buf.is_empty() {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
match stream.write_all(&buf) {
|
||||||
|
Ok(i) => i,
|
||||||
|
Err(_) => return Err(()),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
let method = status[0];
|
||||||
|
let page = status[1];
|
||||||
|
|
||||||
|
match site_stream.shutdown(Shutdown::Both) {
|
||||||
|
Ok(i) => i,
|
||||||
|
Err(_) => {
|
||||||
|
return Err(());
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
Ok((
|
||||||
|
ip_str.clone(),
|
||||||
|
method.to_string().clone(),
|
||||||
|
host.to_string().clone(),
|
||||||
|
page.to_string().clone(),
|
||||||
|
))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn accept_ssl(
|
||||||
|
self,
|
||||||
|
stream: &mut SslStream<TcpStream>,
|
||||||
|
peer_addr: std::net::SocketAddr,
|
||||||
|
) -> Result<(String, String, String, String), ()> {
|
||||||
|
let octets = match peer_addr.ip() {
|
||||||
|
IpAddr::V4(ip) => ip.octets(),
|
||||||
|
_ => [127, 0, 0, 1],
|
||||||
|
};
|
||||||
|
|
||||||
|
let dot: String = String::from(".");
|
||||||
|
let ip_str = String::from(
|
||||||
|
octets[0].to_string()
|
||||||
|
+ &dot
|
||||||
|
+ &octets[1].to_string()
|
||||||
|
+ &dot
|
||||||
|
+ &octets[2].to_string()
|
||||||
|
+ &dot
|
||||||
|
+ &octets[3].to_string(),
|
||||||
|
);
|
||||||
|
|
||||||
|
let addition: String = ip_str.clone() + ":" + peer_addr.port().to_string().as_str() + "\n";
|
||||||
|
|
||||||
|
let mut reqst_data: Vec<u8> = vec![0; 4096];
|
||||||
|
|
||||||
|
match stream.read(&mut reqst_data) {
|
||||||
|
Ok(i) => i,
|
||||||
|
Err(_) => return Err(()),
|
||||||
|
};
|
||||||
|
|
||||||
|
let reqst = match String::from_utf8(reqst_data) {
|
||||||
|
Ok(v) => v,
|
||||||
|
Err(_) => {
|
||||||
|
return Err(());
|
||||||
|
}
|
||||||
|
};
|
||||||
|
let reqst = reqst.trim_matches(char::from(0));
|
||||||
|
|
||||||
|
let (head, body) = match split_once(&reqst, "\r\n\r\n") {
|
||||||
|
Ok(i) => i,
|
||||||
|
Err(_) => return Err(()),
|
||||||
|
};
|
||||||
|
|
||||||
|
let mut head_lines = head.split("\r\n");
|
||||||
|
|
||||||
|
let status = match head_lines.next() {
|
||||||
|
Some(i) => i,
|
||||||
|
None => return Err(()),
|
||||||
|
};
|
||||||
|
let status: Vec<&str> = status.split(" ").collect();
|
||||||
|
|
||||||
|
let mut host: &str = "honk";
|
||||||
|
let mut content_length: usize = 0;
|
||||||
|
|
||||||
|
for l in head_lines {
|
||||||
|
let (key, value) = match split_once(&l, ": ") {
|
||||||
|
Ok(i) => i,
|
||||||
|
Err(_) => return Err(()),
|
||||||
|
};
|
||||||
|
let key = key.to_lowercase().replace("-", "_");
|
||||||
|
|
||||||
|
if key == "host" {
|
||||||
|
host = &value;
|
||||||
|
}
|
||||||
|
if key == "content_length" {
|
||||||
|
content_length = match value.parse() {
|
||||||
|
Ok(i) => i,
|
||||||
|
Err(_) => {
|
||||||
|
return Err(());
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let site = self.get_site(host);
|
||||||
|
|
||||||
|
if site.is_none() {
|
||||||
|
return Err(());
|
||||||
|
}
|
||||||
|
|
||||||
|
let site = match site {
|
||||||
|
Some(i) => i,
|
||||||
|
None => return Err(()),
|
||||||
|
};
|
||||||
|
let mut site_stream = match site.connect() {
|
||||||
|
Ok(i) => i,
|
||||||
|
Err(_) => return Err(()),
|
||||||
|
};
|
||||||
|
|
||||||
|
match site_stream.write((addition + reqst).as_bytes()) {
|
||||||
|
Ok(i) => i,
|
||||||
|
Err(_) => {
|
||||||
|
return Err(());
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let body_len = body.len();
|
||||||
|
if body_len < content_length {
|
||||||
|
let mut body_data: Vec<u8> = vec![0; content_length - body_len];
|
||||||
|
match stream.read_exact(&mut body_data) {
|
||||||
|
Ok(i) => i,
|
||||||
|
Err(_) => return Err(()),
|
||||||
|
};
|
||||||
|
match site_stream.write_all(&body_data) {
|
||||||
|
Ok(i) => i,
|
||||||
|
Err(_) => return Err(()),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
loop {
|
||||||
|
let mut buf: Vec<u8> = Vec::new();
|
||||||
|
match site_stream.read_to_end(&mut buf) {
|
||||||
|
Ok(i) => i,
|
||||||
|
Err(_) => return Err(()),
|
||||||
|
};
|
||||||
|
if buf.is_empty() {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
match stream.write_all(&buf) {
|
||||||
|
Ok(i) => i,
|
||||||
|
Err(e) => {
|
||||||
|
info!("{}", e);
|
||||||
|
return Err(());
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
let method = status[0];
|
||||||
|
let page = status[1];
|
||||||
|
|
||||||
|
match site_stream.shutdown(Shutdown::Both) {
|
||||||
|
Ok(i) => i,
|
||||||
|
Err(_) => {
|
||||||
|
return Err(());
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
Ok((
|
||||||
|
ip_str.clone(),
|
||||||
|
method.to_string().clone(),
|
||||||
|
host.to_string().clone(),
|
||||||
|
page.to_string().clone(),
|
||||||
|
))
|
||||||
|
}
|
||||||
|
}
|
71
src/lib.rs
Normal file
71
src/lib.rs
Normal file
@ -0,0 +1,71 @@
|
|||||||
|
use std::{
|
||||||
|
sync::{mpsc, Arc, Mutex},
|
||||||
|
thread,
|
||||||
|
};
|
||||||
|
|
||||||
|
type Job = Box<dyn FnOnce() + Send + 'static>;
|
||||||
|
|
||||||
|
pub struct ThreadPool {
|
||||||
|
workers: Vec<Worker>,
|
||||||
|
sender: mpsc::Sender<Job>,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub enum PoolCreationError {
|
||||||
|
InvalidSize,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ThreadPool {
|
||||||
|
pub fn new(size: usize) -> ThreadPool {
|
||||||
|
assert!(size > 0);
|
||||||
|
|
||||||
|
let (sender, receiver) = mpsc::channel();
|
||||||
|
let receiver = Arc::new(Mutex::new(receiver));
|
||||||
|
|
||||||
|
let mut workers = Vec::with_capacity(size);
|
||||||
|
|
||||||
|
for _ in 0..size {
|
||||||
|
workers.push(Worker::new(Arc::clone(&receiver)));
|
||||||
|
}
|
||||||
|
|
||||||
|
ThreadPool { workers, sender }
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn build(size: usize) -> Result<ThreadPool, PoolCreationError> {
|
||||||
|
if size <= 0 {
|
||||||
|
Err(PoolCreationError::InvalidSize)
|
||||||
|
} else {
|
||||||
|
Ok(Self::new(size))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn join(self) {
|
||||||
|
for ele in self.workers.into_iter() {
|
||||||
|
ele.thread.join().unwrap();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn execute<F>(&self, f: F)
|
||||||
|
where
|
||||||
|
F: FnOnce() + Send + 'static,
|
||||||
|
{
|
||||||
|
let job = Box::new(f);
|
||||||
|
|
||||||
|
self.sender.send(job).unwrap();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct Worker {
|
||||||
|
thread: thread::JoinHandle<()>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Worker {
|
||||||
|
fn new(receiver: Arc<Mutex<mpsc::Receiver<Job>>>) -> Worker {
|
||||||
|
let thread = thread::spawn(move || {
|
||||||
|
while let Ok(job) = receiver.lock().unwrap().recv() {
|
||||||
|
job();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
Worker { thread }
|
||||||
|
}
|
||||||
|
}
|
116
src/main.rs
Normal file
116
src/main.rs
Normal file
@ -0,0 +1,116 @@
|
|||||||
|
pub mod http_server;
|
||||||
|
extern crate yaml_rust;
|
||||||
|
|
||||||
|
use http_server::*;
|
||||||
|
use std::sync::Arc;
|
||||||
|
use std::{fs, thread};
|
||||||
|
use yaml_rust::YamlLoader;
|
||||||
|
|
||||||
|
use log::LevelFilter;
|
||||||
|
use log4rs::append::console::ConsoleAppender;
|
||||||
|
use log4rs::append::file::FileAppender;
|
||||||
|
use log4rs::config::{Appender, Config, Root};
|
||||||
|
use log4rs::encode::pattern::PatternEncoder;
|
||||||
|
|
||||||
|
struct AppConfig {
|
||||||
|
sites: Vec<Site>,
|
||||||
|
http_host: String,
|
||||||
|
https_host: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl AppConfig {
|
||||||
|
fn parse(filename: &str) -> Option<AppConfig> {
|
||||||
|
let Ok(file_content) = fs::read_to_string(filename) else {
|
||||||
|
return None;
|
||||||
|
};
|
||||||
|
let Ok(docs) = YamlLoader::load_from_str(file_content.as_str()) else {
|
||||||
|
return None;
|
||||||
|
};
|
||||||
|
let doc = docs.get(0)?;
|
||||||
|
|
||||||
|
let http_host = doc["http_host"].as_str()?.to_string();
|
||||||
|
let https_host = doc["https_host"].as_str()?.to_string();
|
||||||
|
|
||||||
|
let mut sites: Vec<Site> = Vec::new();
|
||||||
|
|
||||||
|
let sites_yaml = doc["sites"].as_vec()?;
|
||||||
|
|
||||||
|
for s in sites_yaml {
|
||||||
|
let mut cert: Option<SslCert> = None;
|
||||||
|
|
||||||
|
if !s["ssl_cert"].is_badvalue() && !s["ssl_cert"].is_null() {
|
||||||
|
cert = Some(
|
||||||
|
SslCert::new(
|
||||||
|
s["ssl_cert"].as_str().unwrap(),
|
||||||
|
s["ssl_key"].as_str().unwrap(),
|
||||||
|
)
|
||||||
|
.unwrap(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
let site = Site {
|
||||||
|
domain: s["domain"].as_str().unwrap().to_string(),
|
||||||
|
host: s["host"].as_str().unwrap().to_string(),
|
||||||
|
ssl: cert,
|
||||||
|
};
|
||||||
|
|
||||||
|
sites.push(site);
|
||||||
|
}
|
||||||
|
|
||||||
|
Some(AppConfig {
|
||||||
|
sites,
|
||||||
|
http_host,
|
||||||
|
https_host,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
log4rs::init_config(
|
||||||
|
Config::builder()
|
||||||
|
.appender(
|
||||||
|
Appender::builder().build(
|
||||||
|
"logfile",
|
||||||
|
Box::new(
|
||||||
|
FileAppender::builder()
|
||||||
|
.encoder(Box::new(PatternEncoder::new(
|
||||||
|
"{d(%Y-%m-%d %H:%M:%S)} | {l} - {m}\n",
|
||||||
|
)))
|
||||||
|
.build("latest.log")
|
||||||
|
.unwrap(),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
.appender(
|
||||||
|
Appender::builder().build(
|
||||||
|
"stdout",
|
||||||
|
Box::new(
|
||||||
|
ConsoleAppender::builder()
|
||||||
|
.encoder(Box::new(PatternEncoder::new(
|
||||||
|
"{d(%Y-%m-%d %H:%M:%S)} | {l} - {m}\n",
|
||||||
|
)))
|
||||||
|
.build(),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
.build(
|
||||||
|
Root::builder()
|
||||||
|
.appender("logfile")
|
||||||
|
.appender("stdout")
|
||||||
|
.build(LevelFilter::Debug),
|
||||||
|
)
|
||||||
|
.unwrap(),
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
let config = AppConfig::parse("conf.yml").unwrap();
|
||||||
|
let sites_arc = Arc::new(config.sites);
|
||||||
|
|
||||||
|
let sites = sites_arc.clone();
|
||||||
|
thread::spawn(move || {
|
||||||
|
SiteServer::new(config.http_host.to_string(), sites).run_http();
|
||||||
|
});
|
||||||
|
|
||||||
|
let sites = sites_arc.clone();
|
||||||
|
SiteServer::new(config.https_host.to_string(), sites).run_ssl();
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user