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 = [
|
||||
"axum",
|
||||
"clap",
|
||||
"serve_zip",
|
||||
"tokio",
|
||||
"tower-http",
|
||||
"tracing-subscriber",
|
||||
|
|
|
|||
|
|
@ -9,3 +9,4 @@ clap = { version = "4.6.1", features = ["derive"] }
|
|||
tokio = { version = "1.52.3", features = ["full"] }
|
||||
tower-http = { version = "0.6.10", features = ["trace"] }
|
||||
tracing-subscriber = "0.3.23"
|
||||
serve_zip = { path = "../serve_zip" }
|
||||
|
|
|
|||
|
|
@ -1,5 +1,7 @@
|
|||
use axum::{routing::get, Router};
|
||||
use clap::Parser;
|
||||
use serve_zip::serve_zip::ZipServe;
|
||||
use serve_zip::source::ZipSource;
|
||||
use std::net::{IpAddr, Ipv4Addr, SocketAddr};
|
||||
use std::path::PathBuf;
|
||||
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
|
||||
// folder, opt for using serve_dir instead.
|
||||
match &args.zip {
|
||||
Some(path) => println!("Using external zip file: {}", path.display()),
|
||||
let source = match &args.zip {
|
||||
Some(path) => {
|
||||
println!("Using external zip file: {}", path.display());
|
||||
ZipSource::File(path.clone())
|
||||
}
|
||||
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 listener = TcpListener::bind(addr)
|
||||
.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 serve_zip;
|
||||
pub mod service;
|
||||
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