add majsite server example
This commit is contained in:
parent
a0c8ea4f90
commit
bd1d37548a
20
Cargo.toml
20
Cargo.toml
|
@ -7,24 +7,38 @@ edition = "2018"
|
|||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[dependencies]
|
||||
async-trait = { version = "0", optional = true }
|
||||
num = "0.2"
|
||||
num-derive = "0.3"
|
||||
num-traits = "0.2"
|
||||
rustls = { version = "0.18", optional = true, features = ["dangerous_configuration"] }
|
||||
webpki = { version = "0.21.0", optional = true }
|
||||
webpki-roots = { version = "0.20", optional = true }
|
||||
tokio-rustls = { version = "0.14", features = ["dangerous_configuration"], optional = true }
|
||||
log = "0.4"
|
||||
url = "2"
|
||||
thiserror = "1"
|
||||
|
||||
[dev-dependencies]
|
||||
structopt = "0.3"
|
||||
pretty_env_logger = "0.4"
|
||||
|
||||
[dependencies.tokio]
|
||||
version = "0.2.0"
|
||||
features = [
|
||||
"macros",
|
||||
"net",
|
||||
"tcp",
|
||||
"io-util",
|
||||
"rt-core",
|
||||
"time",
|
||||
"stream"
|
||||
]
|
||||
optional = true
|
||||
|
||||
[features]
|
||||
default = ["client", "server"]
|
||||
|
||||
client = ["rustls", "webpki", "webpki-roots"]
|
||||
server = ["rustls", "webpki", "webpki-roots"]
|
||||
server = ["rustls", "webpki", "webpki-roots", "tokio", "async-trait", "tokio-rustls"]
|
||||
|
||||
[workspace]
|
||||
members = [
|
||||
|
|
|
@ -0,0 +1,78 @@
|
|||
use std::fs::File;
|
||||
use std::io::{self, BufReader};
|
||||
use std::path::{Path, PathBuf};
|
||||
use structopt::StructOpt;
|
||||
use tokio_rustls::rustls::internal::pemfile::{certs, rsa_private_keys};
|
||||
use tokio_rustls::rustls::{Certificate, NoClientAuth, PrivateKey, ServerConfig};
|
||||
|
||||
#[derive(StructOpt, Debug)]
|
||||
struct Options {
|
||||
/// host to listen on
|
||||
#[structopt(short = "H", long, env = "HOST", default_value = "0.0.0.0")]
|
||||
host: String,
|
||||
|
||||
/// port to listen on
|
||||
#[structopt(short = "p", long, env = "PORT", default_value = "1965")]
|
||||
port: u16,
|
||||
|
||||
/// cert file
|
||||
#[structopt(short = "c", long = "cert", env = "CERT_FILE")]
|
||||
cert: PathBuf,
|
||||
|
||||
/// key file
|
||||
#[structopt(short = "k", long = "key", env = "KEY_FILE")]
|
||||
key: PathBuf,
|
||||
}
|
||||
|
||||
fn load_certs(path: &Path) -> io::Result<Vec<Certificate>> {
|
||||
certs(&mut BufReader::new(File::open(path)?))
|
||||
.map_err(|_| io::Error::new(io::ErrorKind::InvalidInput, "invalid cert"))
|
||||
}
|
||||
|
||||
fn load_keys(path: &Path) -> io::Result<Vec<PrivateKey>> {
|
||||
rsa_private_keys(&mut BufReader::new(File::open(path)?))
|
||||
.map_err(|_| io::Error::new(io::ErrorKind::InvalidInput, "invalid key"))
|
||||
}
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() -> Result<(), maj::server::Error> {
|
||||
pretty_env_logger::init();
|
||||
let opts = Options::from_args();
|
||||
let certs = load_certs(&opts.cert)?;
|
||||
let mut keys = load_keys(&opts.key)?;
|
||||
|
||||
let mut config = ServerConfig::new(NoClientAuth::new());
|
||||
config
|
||||
.set_single_cert(certs, keys.remove(0))
|
||||
.map_err(|err| io::Error::new(io::ErrorKind::InvalidInput, err))?;
|
||||
|
||||
maj::server::serve(Handler{}, config, opts.host, opts.port).await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
struct Handler {}
|
||||
|
||||
fn index() -> Result<maj::Response, maj::server::Error> {
|
||||
let msg = include_bytes!("../../majc/src/help.gmi");
|
||||
|
||||
Ok(maj::Response {
|
||||
status: maj::StatusCode::Success,
|
||||
meta: "text/gemini".to_string(),
|
||||
body: msg.to_vec(),
|
||||
})
|
||||
}
|
||||
|
||||
#[async_trait::async_trait]
|
||||
impl maj::server::Handler for Handler {
|
||||
async fn handle(&self, r: maj::server::Request) -> Result<maj::Response, maj::server::Error> {
|
||||
match r.url.path() {
|
||||
"/" | "" => index(),
|
||||
_ => Ok(maj::Response {
|
||||
status: maj::StatusCode::NotFound,
|
||||
meta: format!("{} not found", r.url),
|
||||
body: vec![],
|
||||
}),
|
||||
}
|
||||
}
|
||||
}
|
|
@ -12,6 +12,4 @@ mod client;
|
|||
pub use client::{get, Error as ClientError};
|
||||
|
||||
#[cfg(feature = "server")]
|
||||
mod server;
|
||||
#[cfg(feature = "server")]
|
||||
pub use server::{serve, serve_plain, Error as AnyError, Handler};
|
||||
pub mod server;
|
||||
|
|
|
@ -1,10 +1,17 @@
|
|||
use crate::Response;
|
||||
use std::{error::Error as StdError, io};
|
||||
use crate::{Response, StatusCode};
|
||||
use async_trait::async_trait;
|
||||
use rustls::{Certificate, Session};
|
||||
use std::{error::Error as StdError, sync::Arc};
|
||||
use tokio::io::{AsyncBufReadExt, AsyncWriteExt, BufReader};
|
||||
use tokio::{net::TcpListener, stream::StreamExt};
|
||||
use tokio_rustls::TlsAcceptor;
|
||||
use url::Url;
|
||||
|
||||
/// A Gemini request and its associated metadata.
|
||||
#[allow(dead_code)]
|
||||
pub struct Request {
|
||||
url: Url,
|
||||
pub url: Url,
|
||||
pub certs: Option<Vec<Certificate>>,
|
||||
}
|
||||
|
||||
pub type Error = Box<dyn StdError + Sync + Send>;
|
||||
|
@ -13,14 +20,103 @@ pub type Error = Box<dyn StdError + Sync + Send>;
|
|||
mod routes;
|
||||
pub use routes::*;
|
||||
|
||||
#[async_trait]
|
||||
pub trait Handler {
|
||||
fn handle(r: Request) -> Result<Response, Error>;
|
||||
async fn handle(&self, r: Request) -> Result<Response, Error>;
|
||||
}
|
||||
|
||||
pub async fn serve<T>(h: T, cfg: rustls::ServerConfig, host: String, port: u16) -> Result<(), Error>
|
||||
where
|
||||
T: Handler,
|
||||
{
|
||||
let cfg = Arc::new(cfg);
|
||||
let mut listener = TcpListener::bind(&format!("{}:{}", host, port)).await?;
|
||||
let mut incoming = listener.incoming();
|
||||
let acceptor = TlsAcceptor::from(cfg.clone());
|
||||
|
||||
while let Some(stream) = incoming.next().await {
|
||||
let stream = stream?;
|
||||
let addr = stream.peer_addr().unwrap();
|
||||
let acceptor = acceptor.clone();
|
||||
let mut stream = acceptor.accept(stream).await?;
|
||||
let mut rd = BufReader::new(&mut stream);
|
||||
let mut u = String::new();
|
||||
rd.read_line(&mut u).await?;
|
||||
if u.len() > 1025 {
|
||||
stream
|
||||
.write(format!("{} URL too long", StatusCode::BadRequest as u8).as_bytes())
|
||||
.await?;
|
||||
continue;
|
||||
}
|
||||
|
||||
let u = Url::parse(&u)?;
|
||||
match h
|
||||
.handle(Request {
|
||||
url: u.clone(),
|
||||
certs: stream.get_ref().1.get_peer_certificates(),
|
||||
})
|
||||
.await
|
||||
{
|
||||
Ok(resp) => {
|
||||
stream
|
||||
.write(format!("{} {}\r\n", resp.status as u8, resp.meta).as_bytes())
|
||||
.await?;
|
||||
stream.write(&resp.body).await?;
|
||||
log::info!("{}: {} {:?}", addr, u, resp.status);
|
||||
}
|
||||
Err(why) => {
|
||||
stream
|
||||
.write(format!("{} {:?}\r\n", StatusCode::PermanentFailure as u8, why).as_bytes())
|
||||
.await?;
|
||||
log::error!("{}: {}: {:?}", addr, u, why);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
pub fn serve(_h: impl Handler, _port: u16) -> io::Result<()> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn serve_plain(_h: impl Handler, _port: u16) -> io::Result<()> {
|
||||
pub async fn serve_plain<T>(h: T, host: String, port: u16) -> Result<(), Error>
|
||||
where
|
||||
T: Handler,
|
||||
{
|
||||
let mut listener = TcpListener::bind(&format!("{}:{}", host, port)).await?;
|
||||
let mut incoming = listener.incoming();
|
||||
while let Some(stream) = incoming.next().await {
|
||||
let mut stream = stream?;
|
||||
let mut rd = BufReader::new(&mut stream);
|
||||
let mut u = String::new();
|
||||
rd.read_line(&mut u).await?;
|
||||
if u.len() > 1025 {
|
||||
stream
|
||||
.write(format!("{} URL too long", StatusCode::BadRequest as u8).as_bytes())
|
||||
.await?;
|
||||
continue;
|
||||
}
|
||||
|
||||
let u = Url::parse(&u)?;
|
||||
match h
|
||||
.handle(Request {
|
||||
url: u.clone(),
|
||||
certs: None,
|
||||
})
|
||||
.await
|
||||
{
|
||||
Ok(resp) => {
|
||||
stream
|
||||
.write(format!("{} {}", resp.status as u8, resp.meta).as_bytes())
|
||||
.await?;
|
||||
stream.write(&resp.body).await?;
|
||||
log::info!("{}: {} {:?}", stream.peer_addr().unwrap(), u, resp.status);
|
||||
}
|
||||
Err(why) => {
|
||||
stream
|
||||
.write(format!("{} {:?}", StatusCode::PermanentFailure as u8, why).as_bytes())
|
||||
.await?;
|
||||
log::error!("{}: {}: {:?}", stream.peer_addr().unwrap(), u, why);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue