input test and static file serving with majsite

This commit is contained in:
Cadey Ratio 2020-07-31 13:13:51 -04:00
parent d2af2c5f08
commit 28ae14ffa7
5 changed files with 61 additions and 27 deletions

View File

@ -11,7 +11,8 @@ anyhow = "1"
async-std = "1.5" async-std = "1.5"
async-trait = "0" async-trait = "0"
log = "0" log = "0"
pretty_env_logger = "0.4" env_logger = "0"
percent-encoding = "2"
rustls = { version = "0.18", features = ["dangerous_configuration"] } rustls = { version = "0.18", features = ["dangerous_configuration"] }
structopt = "0.3" structopt = "0.3"

View File

@ -5,6 +5,7 @@ use maj::{
server::{Error, Handler as MajHandler, Request}, server::{Error, Handler as MajHandler, Request},
split, Response, split, Response,
}; };
use percent_encoding::percent_decode_str;
use rustls::internal::pemfile::{certs, rsa_private_keys}; use rustls::internal::pemfile::{certs, rsa_private_keys};
use rustls::{ use rustls::{
AllowAnyAnonymousOrAuthenticatedClient, Certificate, PrivateKey, RootCertStore, ServerConfig, AllowAnyAnonymousOrAuthenticatedClient, Certificate, PrivateKey, RootCertStore, ServerConfig,
@ -33,6 +34,10 @@ struct Options {
#[structopt(short = "k", long = "key", env = "KEY_FILE")] #[structopt(short = "k", long = "key", env = "KEY_FILE")]
key: PathBuf, key: PathBuf,
/// static path
#[structopt(short = "s", long, env = "STATIC_PATH")]
static_path: PathBuf,
/// server hostname /// server hostname
#[structopt( #[structopt(
long = "hostname", long = "hostname",
@ -53,7 +58,7 @@ fn load_keys(path: &Path) -> io::Result<Vec<PrivateKey>> {
} }
fn main() -> Result<(), maj::server::Error> { fn main() -> Result<(), maj::server::Error> {
pretty_env_logger::init(); env_logger::init();
let opts = Options::from_args(); let opts = Options::from_args();
let certs = load_certs(&opts.cert)?; let certs = load_certs(&opts.cert)?;
let mut keys = load_keys(&opts.key)?; let mut keys = load_keys(&opts.key)?;
@ -75,6 +80,7 @@ fn main() -> Result<(), maj::server::Error> {
task::block_on(maj::server::serve( task::block_on(maj::server::serve(
Arc::new(Handler { Arc::new(Handler {
hostname: opts.hostname, hostname: opts.hostname,
files: maj::server::files::Handler::new(opts.static_path),
}), }),
config, config,
opts.host, opts.host,
@ -86,6 +92,7 @@ fn main() -> Result<(), maj::server::Error> {
struct Handler { struct Handler {
hostname: String, hostname: String,
files: maj::server::files::Handler,
} }
async fn index() -> Result<maj::Response, maj::server::Error> { async fn index() -> Result<maj::Response, maj::server::Error> {
@ -110,6 +117,26 @@ async fn need_cert(req: Request) -> Result<Response, Error> {
} }
} }
async fn input(req: Request) -> Result<Response, Error> {
match req.url.query() {
None => Ok(Response::input("test")),
Some(q) => Ok({
use maj::gemini::Node::*;
let result = vec![
Heading {
level: 1,
body: "Input test".to_string(),
},
Text("".to_string()),
Text("You gave me:".to_string()),
Preformatted(format!("{}", percent_decode_str(q).decode_utf8()?)),
];
Response::render(result)
}),
}
}
#[async_trait::async_trait] #[async_trait::async_trait]
impl MajHandler for Handler { impl MajHandler for Handler {
async fn handle(&self, req: Request) -> Result<Response, Error> { async fn handle(&self, req: Request) -> Result<Response, Error> {
@ -127,7 +154,9 @@ impl MajHandler for Handler {
route!(req.url.path(), { route!(req.url.path(), {
(/) => index().await; (/) => index().await;
(/"cert") => need_cert(req).await; (/"cert") => need_cert(req).await;
(/"input") => input(req).await;
(/"majc") => majc().await; (/"majc") => majc().await;
(/"static"[/rest..]) => self.files.handle(req).await;
}); });
Ok(Response::not_found()) Ok(Response::not_found())

3
site/static/index.gmi Normal file
View File

@ -0,0 +1,3 @@
# test
Hi there

View File

@ -3,46 +3,45 @@ use super::{Handler as MajHandler, Request, Result};
use crate::Response; use crate::Response;
use async_trait::async_trait; use async_trait::async_trait;
use std::ffi::OsStr; use std::ffi::OsStr;
use std::path::PathBuf;
pub struct Handler { pub struct Handler {
chop_off: String, base_dir: PathBuf,
base_dir: String,
} }
impl Handler { impl Handler {
/// Serves static files from an OS directory with a given prefix chopped off. /// Serves static files from an OS directory with a given prefix chopped off.
pub fn new(chop_off: String, base_dir: String) -> Self { pub fn new(base_dir: PathBuf) -> Self {
Handler { Handler {
chop_off: chop_off,
base_dir: base_dir, base_dir: base_dir,
} }
} }
fn chop(&self, path: String) -> Option<String> {
if path.starts_with(&self.chop_off) {
path.strip_prefix(&self.chop_off).map(|val| val.to_string())
} else {
Some(path)
}
}
} }
#[async_trait] #[async_trait]
impl MajHandler for Handler { impl MajHandler for Handler {
async fn handle(&self, r: Request) -> Result<Response> { async fn handle(&self, r: Request) -> Result<Response> {
let mut path = std::path::PathBuf::from(&self.base_dir); let mut path = std::path::PathBuf::from(&self.base_dir);
let mut url = r.url.clone();
url.set_path(&self.chop(r.url.path().to_string()).unwrap());
if let Some(segments) = r.url.path_segments() { if let Some(segments) = r.url.path_segments() {
path.extend(segments); path.extend(segments);
} }
if async_std::fs::metadata(&path).await?.is_dir() { log::debug!("opening file {:?}", path);
if url.as_str().ends_with('/') {
path.push("index.gmi"); match async_std::fs::metadata(&path).await {
} else { Ok(stat) => {
// Send a redirect when the URL for a directory has no trailing slash. if stat.is_dir() {
return Ok(Response::perm_redirect(format!("{}/", url))); if r.url.as_str().ends_with('/') {
path.push("index.gmi");
} else {
// Send a redirect when the URL for a directory has no trailing slash.
return Ok(Response::perm_redirect(format!("{}/", r.url)));
}
}
}
Err(why) => {
log::error!("file {} not found: {}", path.to_str().unwrap(), why);
return Ok(Response::not_found());
} }
} }
@ -51,7 +50,9 @@ impl MajHandler for Handler {
async_std::io::copy(&mut file, &mut buf).await?; async_std::io::copy(&mut file, &mut buf).await?;
// Send header. // Send header.
if path.extension() == Some(OsStr::new("gmi")) { if path.extension() == Some(OsStr::new("gmi"))
|| path.extension() == Some(OsStr::new("gemini"))
{
return Ok(Response::gemini(buf)); return Ok(Response::gemini(buf));
} }

View File

@ -107,8 +107,8 @@ async fn handle_request(
handle(h, req, &mut stream, addr).await; handle(h, req, &mut stream, addr).await;
} }
Err(e) => { Err(e) => {
let _ = write_header(&mut stream, StatusCode::BadRequest, "Invalid request.").await; let _ = write_header(&mut stream, StatusCode::BadRequest, "Invalid request").await;
log::error!("error from {}: {:?}", addr, e); log::error!("error from {}: {}", addr, e);
} }
} }
Ok(()) Ok(())
@ -171,11 +171,11 @@ async fn handle<T>(
.write(format!("{} {}\r\n", resp.status as u8, resp.meta).as_bytes()) .write(format!("{} {}\r\n", resp.status as u8, resp.meta).as_bytes())
.await; .await;
let _ = stream.write(&resp.body).await; let _ = stream.write(&resp.body).await;
log::info!("{}: {} {} {:?}", addr, u, resp.meta, resp.status); log::info!("{}: {} {:?} {}", addr, u, resp.status, resp.meta);
} }
Err(why) => { Err(why) => {
let _ = stream let _ = stream
.write(format!("{} {:?}\r\n", StatusCode::PermanentFailure as u8, why).as_bytes()) .write(format!("{} {}\r\n", StatusCode::PermanentFailure as u8, why.to_string()).as_bytes())
.await; .await;
log::error!("{}: {}: {:?}", addr, u, why); log::error!("{}: {}: {:?}", addr, u, why);
} }