Well, it's serving 'I know it exists, but I can't show it to you.'
This commit is contained in:
parent
e85da1cf34
commit
5245420073
|
|
@ -262,6 +262,7 @@ version = "0.1.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"axum",
|
"axum",
|
||||||
"clap",
|
"clap",
|
||||||
|
"serve_zip",
|
||||||
"tokio",
|
"tokio",
|
||||||
"tower-http",
|
"tower-http",
|
||||||
"tracing-subscriber",
|
"tracing-subscriber",
|
||||||
|
|
|
||||||
|
|
@ -9,3 +9,4 @@ clap = { version = "4.6.1", features = ["derive"] }
|
||||||
tokio = { version = "1.52.3", features = ["full"] }
|
tokio = { version = "1.52.3", features = ["full"] }
|
||||||
tower-http = { version = "0.6.10", features = ["trace"] }
|
tower-http = { version = "0.6.10", features = ["trace"] }
|
||||||
tracing-subscriber = "0.3.23"
|
tracing-subscriber = "0.3.23"
|
||||||
|
serve_zip = { path = "../serve_zip" }
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,7 @@
|
||||||
use axum::{routing::get, Router};
|
use axum::{routing::get, Router};
|
||||||
use clap::Parser;
|
use clap::Parser;
|
||||||
|
use serve_zip::serve_zip::ZipServe;
|
||||||
|
use serve_zip::source::ZipSource;
|
||||||
use std::net::{IpAddr, Ipv4Addr, SocketAddr};
|
use std::net::{IpAddr, Ipv4Addr, SocketAddr};
|
||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
use tokio::net::TcpListener;
|
use tokio::net::TcpListener;
|
||||||
|
|
@ -20,14 +22,25 @@ async fn main() {
|
||||||
|
|
||||||
// TODO: Add a test to see if `path`, if it exists, is a path to a folder or to a zip file. If a
|
// TODO: Add a test to see if `path`, if it exists, is a path to a folder or to a zip file. If a
|
||||||
// folder, opt for using serve_dir instead.
|
// folder, opt for using serve_dir instead.
|
||||||
match &args.zip {
|
let source = match &args.zip {
|
||||||
Some(path) => println!("Using external zip file: {}", path.display()),
|
Some(path) => {
|
||||||
|
println!("Using external zip file: {}", path.display());
|
||||||
|
ZipSource::File(path.clone())
|
||||||
|
}
|
||||||
None => {
|
None => {
|
||||||
println!("Using embedded zip file, if present. (TODO: Make this an error otherwise)")
|
println!("Using embedded zip file, if present. (TODO: Make this an error otherwise)");
|
||||||
}
|
unimplemented!("Embedding isn't ready yet.");
|
||||||
}
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let app = Router::new().fallback_service(
|
||||||
|
ZipServe::builder(source)
|
||||||
|
.append_index_html(true)
|
||||||
|
.build()
|
||||||
|
.await
|
||||||
|
.unwrap(),
|
||||||
|
);
|
||||||
|
|
||||||
let app = Router::new().route("/check", get(|| async { "OK" }));
|
|
||||||
let addr = SocketAddr::new(IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)), args.port);
|
let addr = SocketAddr::new(IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)), args.port);
|
||||||
let listener = TcpListener::bind(addr)
|
let listener = TcpListener::bind(addr)
|
||||||
.await
|
.await
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,69 @@
|
||||||
|
use std::convert::Infallible;
|
||||||
|
use std::sync::Arc;
|
||||||
|
|
||||||
|
use axum::body::Body;
|
||||||
|
use http::{Method, Request, Response, StatusCode};
|
||||||
|
|
||||||
|
use crate::index::Entries;
|
||||||
|
use crate::serve_zip::ZipServeConfig;
|
||||||
|
|
||||||
|
pub async fn serve<B>(
|
||||||
|
data: Arc<[u8]>,
|
||||||
|
index: Arc<Entries>,
|
||||||
|
config: ZipServeConfig,
|
||||||
|
req: Request<B>,
|
||||||
|
) -> Result<Response<Body>, Infallible> {
|
||||||
|
if req.method() != Method::GET && req.method() != Method::HEAD {
|
||||||
|
return Ok(Response::builder()
|
||||||
|
.status(StatusCode::METHOD_NOT_ALLOWED)
|
||||||
|
.header("allow", "GET, HEAD")
|
||||||
|
.body(Body::empty())
|
||||||
|
.unwrap());
|
||||||
|
}
|
||||||
|
|
||||||
|
let path = req.uri().path();
|
||||||
|
|
||||||
|
match resolve_path(path, &index, &config) {
|
||||||
|
None => Ok(not_found()),
|
||||||
|
Some(entry_name) => serve_entry(data, config, entry_name, req.method().clone()).await,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn resolve_path(path: &str, index: &Entries, config: &ZipServeConfig) -> Option<String> {
|
||||||
|
// Can't use the same trick we did in `index.rs`; need the path for more than just the lookup.
|
||||||
|
let normalized = path.strip_prefix('/').unwrap_or(path);
|
||||||
|
|
||||||
|
if let Some(meta) = index.get(normalized) {
|
||||||
|
if meta.is_dir {
|
||||||
|
if config.append_index_html {
|
||||||
|
let index_path = format!("{}/index.html", normalized.trim_end_matches('/'));
|
||||||
|
if index.get(&index_path).is_some() {
|
||||||
|
return Some(index_path);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
return Some(normalized.to_string());
|
||||||
|
}
|
||||||
|
None
|
||||||
|
}
|
||||||
|
|
||||||
|
fn not_found() -> Response<Body> {
|
||||||
|
Response::builder()
|
||||||
|
.status(StatusCode::NOT_FOUND)
|
||||||
|
.header("content-type", "text/plain; charset=utf-8")
|
||||||
|
.body(Body::from("Not Found"))
|
||||||
|
.unwrap()
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn serve_entry(
|
||||||
|
_data: Arc<[u8]>,
|
||||||
|
_config: ZipServeConfig,
|
||||||
|
_entry_name: String,
|
||||||
|
_method: Method,
|
||||||
|
) -> Result<Response<Body>, Infallible> {
|
||||||
|
Ok(Response::builder()
|
||||||
|
.status(StatusCode::NOT_IMPLEMENTED)
|
||||||
|
.body(Body::empty())
|
||||||
|
.unwrap())
|
||||||
|
}
|
||||||
|
|
@ -1,2 +1,5 @@
|
||||||
|
pub mod handler;
|
||||||
pub mod index;
|
pub mod index;
|
||||||
|
pub mod serve_zip;
|
||||||
|
pub mod service;
|
||||||
pub mod source;
|
pub mod source;
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,70 @@
|
||||||
|
use std::sync::Arc;
|
||||||
|
|
||||||
|
use crate::index::Entries;
|
||||||
|
use crate::source::ZipSource;
|
||||||
|
|
||||||
|
#[derive(Clone, Debug)]
|
||||||
|
pub struct ZipServeConfig {
|
||||||
|
pub append_index_html: bool,
|
||||||
|
pub fallback_content_type: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub struct ZipServe {
|
||||||
|
pub(crate) data: Arc<[u8]>,
|
||||||
|
pub(crate) index: Arc<Entries>,
|
||||||
|
pub(crate) config: ZipServeConfig,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct ZipServeBuilder {
|
||||||
|
source: ZipSource,
|
||||||
|
config: ZipServeConfig,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for ZipServeConfig {
|
||||||
|
fn default() -> Self {
|
||||||
|
ZipServeConfig {
|
||||||
|
append_index_html: false,
|
||||||
|
fallback_content_type: "application/octet-stream".to_string(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ZipServeBuilder {
|
||||||
|
pub fn new(source: ZipSource) -> Self {
|
||||||
|
ZipServeBuilder {
|
||||||
|
source,
|
||||||
|
config: ZipServeConfig::default(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn append_index_html(mut self, val: bool) -> Self {
|
||||||
|
self.config.append_index_html = val;
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn fallback_content_type(mut self, content_type: impl Into<String>) -> Self {
|
||||||
|
self.config.fallback_content_type = content_type.into();
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn build(self) -> Result<ZipServe, Box<dyn std::error::Error + Send + Sync>> {
|
||||||
|
let data = self.source.load().await?;
|
||||||
|
let index = Entries::new(&data).await?;
|
||||||
|
Ok(ZipServe {
|
||||||
|
data,
|
||||||
|
index,
|
||||||
|
config: self.config,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ZipServe {
|
||||||
|
pub async fn new(source: ZipSource) -> Result<Self, Box<dyn std::error::Error + Send + Sync>> {
|
||||||
|
ZipServeBuilder::new(source).build().await
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn builder(source: ZipSource) -> ZipServeBuilder {
|
||||||
|
ZipServeBuilder::new(source)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,36 @@
|
||||||
|
use std::convert::Infallible;
|
||||||
|
use std::future::Future;
|
||||||
|
use std::pin::Pin;
|
||||||
|
use std::task::{Context, Poll};
|
||||||
|
|
||||||
|
use axum::body::Body;
|
||||||
|
use http::{Request, Response};
|
||||||
|
use tower::Service;
|
||||||
|
|
||||||
|
use crate::serve_zip::ZipServe;
|
||||||
|
|
||||||
|
type ServeFuture =
|
||||||
|
Pin<Box<dyn Future<Output = Result<Response<Body>, Infallible>> + Send + 'static>>;
|
||||||
|
|
||||||
|
impl<ReqBody> Service<Request<ReqBody>> for ZipServe
|
||||||
|
where
|
||||||
|
ReqBody: Send + 'static,
|
||||||
|
{
|
||||||
|
type Response = Response<Body>;
|
||||||
|
type Error = Infallible;
|
||||||
|
|
||||||
|
type Future = ServeFuture;
|
||||||
|
|
||||||
|
// We're always ready. The content's entirely internal at this point.
|
||||||
|
fn poll_ready(&mut self, _cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
|
||||||
|
Poll::Ready(Ok(()))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn call(&mut self, req: Request<ReqBody>) -> Self::Future {
|
||||||
|
let data = self.data.clone();
|
||||||
|
let index = self.index.clone();
|
||||||
|
let config = self.config.clone();
|
||||||
|
|
||||||
|
Box::pin(async move { crate::handler::serve(data, index, config, req).await })
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
Reference in New Issue