working file serving
This commit is contained in:
parent
177d12b5b8
commit
87d61457bd
4 changed files with 71 additions and 68 deletions
14
src/main.rs
14
src/main.rs
|
@ -1,8 +1,18 @@
|
||||||
mod server;
|
mod server;
|
||||||
|
use std::env;
|
||||||
use std::path::Path;
|
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 main() {
|
fn main() {
|
||||||
let wwwRoot = Path::new("/var/www/gemini/");
|
let server = server::Server::new(&read_config());
|
||||||
let server = server::Server::new("gemini://localhost", &wwwRoot);
|
|
||||||
server.serve();
|
server.serve();
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,17 +1,32 @@
|
||||||
use crate::server::response;
|
use crate::server::response;
|
||||||
use crate::server::ServerConfig;
|
use crate::server::ServerConfig;
|
||||||
|
use openssl::ssl::{SslAcceptor, SslFiletype, SslMethod, SslStream};
|
||||||
|
use std::fs::File;
|
||||||
|
use std::io::{copy, BufReader, BufWriter};
|
||||||
|
use std::net::TcpStream;
|
||||||
use std::path::Path;
|
use std::path::Path;
|
||||||
use url::Url;
|
use url::Url;
|
||||||
|
|
||||||
pub fn handle(config: &ServerConfig, url: Url) -> Option<response::Response> {
|
pub fn handle(config: &ServerConfig, url: Url, stream: &mut SslStream<TcpStream>) {
|
||||||
if url.scheme() != "gemini" {
|
if url.scheme() != "gemini" {
|
||||||
return Some(response::invalid_protocol());
|
stream.ssl_write(&response::invalid_protocol().to_vec());
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
let path = Path::new(url.path());
|
let rel_path = Path::new(url.path()).strip_prefix("/").unwrap();
|
||||||
if !path.has_root() {
|
let path = config.www_root.join(rel_path);
|
||||||
return Some(response::not_understood());
|
|
||||||
}
|
|
||||||
|
|
||||||
None
|
let file = match File::open(&path) {
|
||||||
|
Ok(file) => file,
|
||||||
|
Err(_) => {
|
||||||
|
stream.ssl_write(&response::not_found().to_vec());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
stream.ssl_write(&response::Header::new(response::Status::Success, "text/gemini").to_vec());
|
||||||
|
|
||||||
|
let mut buf_file = BufReader::new(file);
|
||||||
|
let mut buf_stream = BufWriter::new(stream);
|
||||||
|
copy(&mut buf_file, &mut buf_stream);
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,4 +1,3 @@
|
||||||
use crate::server::response::Response;
|
|
||||||
use openssl::ssl::{SslAcceptor, SslFiletype, SslMethod, SslStream};
|
use openssl::ssl::{SslAcceptor, SslFiletype, SslMethod, SslStream};
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
use std::net::{SocketAddr, TcpListener, TcpStream};
|
use std::net::{SocketAddr, TcpListener, TcpStream};
|
||||||
|
@ -13,8 +12,26 @@ pub mod response;
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub struct ServerConfig {
|
pub struct ServerConfig {
|
||||||
defaultHost: Url,
|
default_host: Url,
|
||||||
wwwRoot: PathBuf,
|
www_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()));
|
||||||
|
|
||||||
|
ServerConfig {
|
||||||
|
default_host: url,
|
||||||
|
www_root: PathBuf::from(www_root),
|
||||||
|
addrs: Vec::new(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn add_addr(&mut self, addr: String) {
|
||||||
|
self.addrs.push(addr.parse().unwrap());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct Server {
|
pub struct Server {
|
||||||
|
@ -23,21 +40,15 @@ pub struct Server {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Server {
|
impl Server {
|
||||||
pub fn new(host: &str, wwwRoot: &Path) -> Server {
|
pub fn new(config: &ServerConfig) -> Server {
|
||||||
let config = ServerConfig {
|
|
||||||
defaultHost: Url::parse(host).unwrap(),
|
|
||||||
wwwRoot: PathBuf::from(wwwRoot),
|
|
||||||
};
|
|
||||||
|
|
||||||
Server {
|
Server {
|
||||||
acceptor: build_acceptor(),
|
acceptor: build_acceptor(),
|
||||||
config: config,
|
config: config.clone(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn serve(&self) {
|
pub fn serve(&self) {
|
||||||
let addrs = [SocketAddr::from(([127, 0, 0, 1], 1965))];
|
let listener = TcpListener::bind(&self.config.addrs[..]).unwrap();
|
||||||
let listener = TcpListener::bind(&addrs[..]).unwrap();
|
|
||||||
|
|
||||||
for stream in listener.incoming() {
|
for stream in listener.incoming() {
|
||||||
match stream {
|
match stream {
|
||||||
|
@ -62,15 +73,10 @@ fn handle_client(config: &ServerConfig, mut stream: SslStream<TcpStream>) {
|
||||||
|
|
||||||
let location = match Url::parse(&request) {
|
let location = match Url::parse(&request) {
|
||||||
Ok(url) => url,
|
Ok(url) => url,
|
||||||
Err(e) => config.defaultHost.join(&request).unwrap(),
|
Err(e) => config.default_host.join(&request).unwrap(),
|
||||||
};
|
};
|
||||||
|
|
||||||
let response = match handler::handle(config, location) {
|
handler::handle(config, location, &mut stream);
|
||||||
Some(response) => response,
|
|
||||||
None => response::internal_error(),
|
|
||||||
};
|
|
||||||
|
|
||||||
stream.ssl_write(&response.format());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn build_acceptor() -> std::sync::Arc<SslAcceptor> {
|
fn build_acceptor() -> std::sync::Arc<SslAcceptor> {
|
||||||
|
|
|
@ -15,11 +15,6 @@ pub struct Header {
|
||||||
meta: String,
|
meta: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct Response {
|
|
||||||
header: Header,
|
|
||||||
data: Vec<u8>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Header {
|
impl Header {
|
||||||
pub fn new(status: Status, meta: &str) -> Header {
|
pub fn new(status: Status, meta: &str) -> Header {
|
||||||
Header {
|
Header {
|
||||||
|
@ -31,47 +26,24 @@ impl Header {
|
||||||
pub fn format(&self) -> String {
|
pub fn format(&self) -> String {
|
||||||
format!("{} {}\r\n", self.status as u8, self.meta)
|
format!("{} {}\r\n", self.status as u8, self.meta)
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
impl Response {
|
pub fn to_vec(&self) -> Vec<u8> {
|
||||||
pub fn new(header: Header, data: Vec<u8>) -> Response {
|
self.format().as_bytes().to_vec()
|
||||||
Response {
|
|
||||||
header: header,
|
|
||||||
data: data,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn new_empty(header: Header) -> Response {
|
pub fn invalid_protocol() -> Header {
|
||||||
Response {
|
Header::new(Status::PermanentFailure, "this protocol is not supported")
|
||||||
header: header,
|
|
||||||
data: Vec::new(),
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn format(&self) -> Vec<u8> {
|
pub fn not_understood() -> Header {
|
||||||
let mut resp: Vec<u8> = self.header.format().as_bytes().to_vec();
|
Header::new(Status::PermanentFailure, "request not understood")
|
||||||
resp.extend(&self.data);
|
|
||||||
return resp;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn invalid_protocol() -> Response {
|
pub fn not_found() -> Header {
|
||||||
Response::new_empty(Header::new(
|
Header::new(Status::PermanentFailure, "resource not found")
|
||||||
Status::PermanentFailure,
|
|
||||||
"this protocol is not supported",
|
|
||||||
))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn not_understood() -> Response {
|
pub fn internal_error() -> Header {
|
||||||
Response::new_empty(Header::new(
|
Header::new(Status::PermanentFailure, "internal server error")
|
||||||
Status::PermanentFailure,
|
|
||||||
"request not understood",
|
|
||||||
))
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn internal_error() -> Response {
|
|
||||||
Response::new_empty(Header::new(
|
|
||||||
Status::PermanentFailure,
|
|
||||||
"internal server error",
|
|
||||||
))
|
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue