configuration options, error handling
This commit is contained in:
parent
87d61457bd
commit
4a9c6c11ac
4 changed files with 181 additions and 75 deletions
70
src/main.rs
70
src/main.rs
|
@ -1,18 +1,68 @@
|
|||
mod server;
|
||||
use std::env;
|
||||
use std::path::Path;
|
||||
|
||||
fn read_config() -> server::ServerConfig {
|
||||
let mut config = server::ServerConfig::new(
|
||||
"klockenschooster.de".to_string(),
|
||||
"/home/jw/code/projects/sheldond/doc".to_string(),
|
||||
);
|
||||
config.add_addr("127.0.0.1:1965".to_string());
|
||||
config.add_addr("[::1]:1965".to_string());
|
||||
return config;
|
||||
fn help() {
|
||||
let version = match option_env!("CARGO_PKG_VERSION") {
|
||||
Some(v) => v,
|
||||
None => "",
|
||||
};
|
||||
|
||||
println!("usage: sheldond {}", version);
|
||||
println!(" -h, --help\t\tdisplay this message");
|
||||
println!(" -l, --listen\t\tadd a listening address (you can define multiple)");
|
||||
println!(" -d, --default-host\tdefault hostname to listen for");
|
||||
println!(" -g, --gem-root\tpath to the gemini root, aka the folder to serve files from");
|
||||
}
|
||||
|
||||
fn parse_args() -> Option<server::ServerConfig> {
|
||||
let mut has_addr = false;
|
||||
let mut has_host = false;
|
||||
let mut has_root = false;
|
||||
|
||||
let mut config = server::ServerConfig::new();
|
||||
let mut args = env::args();
|
||||
loop {
|
||||
match args.next() {
|
||||
Some(arg) => {
|
||||
if arg == "-h" || arg == "--help" {
|
||||
return None;
|
||||
}
|
||||
if arg == "-l" || arg == "--listen" {
|
||||
let addr = args.next().unwrap();
|
||||
config.add_addr(addr);
|
||||
has_addr = true;
|
||||
}
|
||||
if arg == "-d" || arg == "--default-host" {
|
||||
let host = args.next().unwrap();
|
||||
config.set_default_host(host);
|
||||
has_host = true;
|
||||
}
|
||||
if arg == "-g" || arg == "--gem-root" {
|
||||
let gem_root = args.next().unwrap();
|
||||
config.set_gem_root(gem_root);
|
||||
has_root = true;
|
||||
}
|
||||
}
|
||||
None => break,
|
||||
}
|
||||
}
|
||||
|
||||
if !has_addr || !has_host || !has_root {
|
||||
return None;
|
||||
}
|
||||
|
||||
Some(config)
|
||||
}
|
||||
|
||||
fn main() {
|
||||
let server = server::Server::new(&read_config());
|
||||
let config = match parse_args() {
|
||||
Some(config) => config,
|
||||
None => {
|
||||
help();
|
||||
return;
|
||||
}
|
||||
};
|
||||
|
||||
let server = server::Server::new(&config);
|
||||
server.serve();
|
||||
}
|
||||
|
|
|
@ -1,32 +1,89 @@
|
|||
use crate::server::response;
|
||||
use crate::server::ServerConfig;
|
||||
use openssl::ssl::{SslAcceptor, SslFiletype, SslMethod, SslStream};
|
||||
use openssl::ssl::SslStream;
|
||||
use std::fs::File;
|
||||
use std::io::{copy, BufReader, BufWriter};
|
||||
use std::net::TcpStream;
|
||||
use std::path::Path;
|
||||
use url::Url;
|
||||
|
||||
pub fn handle(config: &ServerConfig, url: Url, stream: &mut SslStream<TcpStream>) {
|
||||
if url.scheme() != "gemini" {
|
||||
stream.ssl_write(&response::invalid_protocol().to_vec());
|
||||
return;
|
||||
}
|
||||
fn send_header(stream: &mut SslStream<TcpStream>, header: &response::Header) {
|
||||
match stream.ssl_write(&header.to_vec()) {
|
||||
Ok(_s) => {
|
||||
return;
|
||||
}
|
||||
Err(_e) => {
|
||||
return;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
let rel_path = Path::new(url.path()).strip_prefix("/").unwrap();
|
||||
let path = config.www_root.join(rel_path);
|
||||
|
||||
let file = match File::open(&path) {
|
||||
Ok(file) => file,
|
||||
pub fn handle_request(config: &ServerConfig, mut stream: SslStream<TcpStream>) {
|
||||
let mut buffer = [0; 1026];
|
||||
match stream.ssl_read(&mut buffer) {
|
||||
Ok(s) => {
|
||||
if s == 0 {
|
||||
send_header(&mut stream, &response::bad_request());
|
||||
return;
|
||||
}
|
||||
}
|
||||
Err(_) => {
|
||||
stream.ssl_write(&response::not_found().to_vec());
|
||||
send_header(&mut stream, &response::bad_request());
|
||||
return;
|
||||
}
|
||||
};
|
||||
|
||||
stream.ssl_write(&response::Header::new(response::Status::Success, "text/gemini").to_vec());
|
||||
let request = match String::from_utf8(buffer.to_vec()) {
|
||||
Ok(request) => request,
|
||||
Err(_) => {
|
||||
send_header(&mut stream, &response::bad_request());
|
||||
return;
|
||||
}
|
||||
};
|
||||
|
||||
let location = match Url::parse(&request) {
|
||||
Ok(url) => url,
|
||||
Err(_) => config.default_host.join(&request).unwrap(),
|
||||
};
|
||||
|
||||
handle_response(config, location, &mut stream);
|
||||
}
|
||||
|
||||
fn handle_response(config: &ServerConfig, url: Url, mut stream: &mut SslStream<TcpStream>) {
|
||||
if url.scheme() != "gemini" {
|
||||
send_header(&mut stream, &response::permanent_failure());
|
||||
return;
|
||||
}
|
||||
|
||||
if url.host() != config.default_host.host() {
|
||||
send_header(&mut stream, &response::proxy_request_refused());
|
||||
return;
|
||||
}
|
||||
|
||||
let rel_path = match Path::new(url.path()).strip_prefix("/") {
|
||||
Ok(path) => path,
|
||||
Err(_) => {
|
||||
send_header(&mut stream, &response::bad_request());
|
||||
return;
|
||||
}
|
||||
};
|
||||
let path = config.gem_root.join(rel_path);
|
||||
|
||||
let file = match File::open(&path) {
|
||||
Ok(file) => file,
|
||||
Err(_) => {
|
||||
send_header(&mut stream, &response::not_found());
|
||||
return;
|
||||
}
|
||||
};
|
||||
|
||||
let header = response::Header::new(response::Status::Success, "text/gemini");
|
||||
send_header(&mut stream, &header);
|
||||
|
||||
let mut buf_file = BufReader::new(file);
|
||||
let mut buf_stream = BufWriter::new(stream);
|
||||
copy(&mut buf_file, &mut buf_stream);
|
||||
match copy(&mut buf_file, &mut buf_stream) {
|
||||
Ok(_s) => {}
|
||||
Err(_e) => {}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
use openssl::ssl::{SslAcceptor, SslFiletype, SslMethod, SslStream};
|
||||
use std::collections::HashMap;
|
||||
use std::net::{SocketAddr, TcpListener, TcpStream};
|
||||
use std::path::{Path, PathBuf};
|
||||
use openssl::ssl::{SslAcceptor, SslFiletype, SslMethod};
|
||||
use std::net::{SocketAddr, TcpListener};
|
||||
use std::path::PathBuf;
|
||||
use std::sync::Arc;
|
||||
use std::thread;
|
||||
use std::vec::Vec;
|
||||
|
@ -13,24 +12,38 @@ pub mod response;
|
|||
#[derive(Clone)]
|
||||
pub struct ServerConfig {
|
||||
default_host: Url,
|
||||
www_root: PathBuf,
|
||||
gem_root: PathBuf,
|
||||
addrs: Vec<SocketAddr>,
|
||||
}
|
||||
|
||||
impl ServerConfig {
|
||||
pub fn new(default_host: String, www_root: String) -> ServerConfig {
|
||||
let mut url = Url::parse("gemini://default").unwrap();
|
||||
url.set_host(Some(default_host.as_str()));
|
||||
|
||||
pub fn new() -> ServerConfig {
|
||||
ServerConfig {
|
||||
default_host: url,
|
||||
www_root: PathBuf::from(www_root),
|
||||
default_host: Url::parse("gemini://localhost").unwrap(),
|
||||
gem_root: PathBuf::from(""),
|
||||
addrs: Vec::new(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn set_default_host(&mut self, default_host: String) {
|
||||
let mut url = Url::parse("gemini://default").unwrap();
|
||||
|
||||
match url.set_host(Some(default_host.as_str())) {
|
||||
Ok(_) => {}
|
||||
Err(e) => panic!(e),
|
||||
};
|
||||
self.default_host = url;
|
||||
}
|
||||
|
||||
pub fn set_gem_root(&mut self, gem_root: String) {
|
||||
self.gem_root = PathBuf::from(gem_root);
|
||||
}
|
||||
|
||||
pub fn add_addr(&mut self, addr: String) {
|
||||
self.addrs.push(addr.parse().unwrap());
|
||||
self.addrs.push(match addr.parse() {
|
||||
Ok(addr) => addr,
|
||||
Err(e) => panic!(e),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -42,11 +55,21 @@ pub struct Server {
|
|||
impl Server {
|
||||
pub fn new(config: &ServerConfig) -> Server {
|
||||
Server {
|
||||
acceptor: build_acceptor(),
|
||||
acceptor: Server::build_acceptor(),
|
||||
config: config.clone(),
|
||||
}
|
||||
}
|
||||
|
||||
fn build_acceptor() -> std::sync::Arc<SslAcceptor> {
|
||||
let mut acceptor = SslAcceptor::mozilla_intermediate_v5(SslMethod::tls()).unwrap();
|
||||
acceptor
|
||||
.set_private_key_file("doc/key.pem", SslFiletype::PEM)
|
||||
.unwrap();
|
||||
acceptor.set_certificate_chain_file("doc/cert.pem").unwrap();
|
||||
acceptor.check_private_key().unwrap();
|
||||
return Arc::new(acceptor.build());
|
||||
}
|
||||
|
||||
pub fn serve(&self) {
|
||||
let listener = TcpListener::bind(&self.config.addrs[..]).unwrap();
|
||||
|
||||
|
@ -57,34 +80,11 @@ impl Server {
|
|||
let config = self.config.clone();
|
||||
thread::spawn(move || {
|
||||
let stream = acceptor.accept(stream).unwrap();
|
||||
handle_client(&config, stream);
|
||||
handler::handle_request(&config, stream);
|
||||
});
|
||||
}
|
||||
Err(e) => { /* connection failed */ }
|
||||
Err(_) => { /* connection failed */ }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn handle_client(config: &ServerConfig, mut stream: SslStream<TcpStream>) {
|
||||
let mut buffer = [0; 1026];
|
||||
stream.ssl_read(&mut buffer);
|
||||
let request = String::from_utf8(buffer.to_vec()).unwrap();
|
||||
|
||||
let location = match Url::parse(&request) {
|
||||
Ok(url) => url,
|
||||
Err(e) => config.default_host.join(&request).unwrap(),
|
||||
};
|
||||
|
||||
handler::handle(config, location, &mut stream);
|
||||
}
|
||||
|
||||
fn build_acceptor() -> std::sync::Arc<SslAcceptor> {
|
||||
let mut acceptor = SslAcceptor::mozilla_intermediate_v5(SslMethod::tls()).unwrap();
|
||||
acceptor
|
||||
.set_private_key_file("doc/key.pem", SslFiletype::PEM)
|
||||
.unwrap();
|
||||
acceptor.set_certificate_chain_file("doc/cert.pem").unwrap();
|
||||
acceptor.check_private_key().unwrap();
|
||||
return Arc::new(acceptor.build());
|
||||
}
|
||||
|
|
|
@ -2,12 +2,11 @@ use std::vec::Vec;
|
|||
|
||||
#[derive(Copy, Clone)]
|
||||
pub enum Status {
|
||||
Input = 10,
|
||||
Success = 20,
|
||||
Redirect = 30,
|
||||
TemporaryFailure = 40,
|
||||
PermanentFailure = 50,
|
||||
ClientCertificateRequired = 60,
|
||||
NotFound = 51,
|
||||
ProxyRequestRefused = 53,
|
||||
BadRequest = 59,
|
||||
}
|
||||
|
||||
pub struct Header {
|
||||
|
@ -32,18 +31,18 @@ impl Header {
|
|||
}
|
||||
}
|
||||
|
||||
pub fn invalid_protocol() -> Header {
|
||||
Header::new(Status::PermanentFailure, "this protocol is not supported")
|
||||
}
|
||||
|
||||
pub fn not_understood() -> Header {
|
||||
Header::new(Status::PermanentFailure, "request not understood")
|
||||
pub fn permanent_failure() -> Header {
|
||||
Header::new(Status::PermanentFailure, "permanent failure")
|
||||
}
|
||||
|
||||
pub fn not_found() -> Header {
|
||||
Header::new(Status::PermanentFailure, "resource not found")
|
||||
Header::new(Status::NotFound, "not found")
|
||||
}
|
||||
|
||||
pub fn internal_error() -> Header {
|
||||
Header::new(Status::PermanentFailure, "internal server error")
|
||||
pub fn proxy_request_refused() -> Header {
|
||||
Header::new(Status::ProxyRequestRefused, "proxy request refused")
|
||||
}
|
||||
|
||||
pub fn bad_request() -> Header {
|
||||
Header::new(Status::BadRequest, "bad request")
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue