♻️ Extract http_serde::Body so I can make the file server standalone
parent
3b8a58df1d
commit
ba17f11297
|
@ -0,0 +1,113 @@
|
||||||
|
use std::{
|
||||||
|
convert::Infallible,
|
||||||
|
error::Error,
|
||||||
|
path::PathBuf,
|
||||||
|
sync::Arc,
|
||||||
|
net::SocketAddr,
|
||||||
|
};
|
||||||
|
|
||||||
|
use hyper::{
|
||||||
|
Body,
|
||||||
|
Request,
|
||||||
|
Response,
|
||||||
|
Server,
|
||||||
|
service::{
|
||||||
|
make_service_fn,
|
||||||
|
service_fn,
|
||||||
|
},
|
||||||
|
StatusCode,
|
||||||
|
};
|
||||||
|
|
||||||
|
#[derive (Default)]
|
||||||
|
struct ServerState {
|
||||||
|
// Pass
|
||||||
|
}
|
||||||
|
|
||||||
|
fn status_reply <B: Into <Body>> (status: StatusCode, b: B)
|
||||||
|
-> Response <Body>
|
||||||
|
{
|
||||||
|
Response::builder ().status (status).body (b.into ()).unwrap ()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn prefix_match <'a> (hay: &'a str, needle: &str) -> Option <&'a str>
|
||||||
|
{
|
||||||
|
if hay.starts_with (needle) {
|
||||||
|
Some (&hay [needle.len ()..])
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn handle_all (req: Request <Body>, _state: Arc <ServerState>)
|
||||||
|
-> Result <Response <Body>, Infallible>
|
||||||
|
{
|
||||||
|
use ptth::{
|
||||||
|
http_serde::RequestParts,
|
||||||
|
server::file_server,
|
||||||
|
};
|
||||||
|
|
||||||
|
let path = req.uri ().path ();
|
||||||
|
//println! ("{}", path);
|
||||||
|
|
||||||
|
if let Some (path) = prefix_match (path, "/files") {
|
||||||
|
let root = PathBuf::from ("./");
|
||||||
|
|
||||||
|
let path = path.into ();
|
||||||
|
|
||||||
|
let (parts, _) = req.into_parts ();
|
||||||
|
|
||||||
|
let ptth_req = match RequestParts::from_hyper (parts.method, path, parts.headers) {
|
||||||
|
Ok (x) => x,
|
||||||
|
_ => return Ok (status_reply (StatusCode::BAD_REQUEST, "Bad request")),
|
||||||
|
};
|
||||||
|
|
||||||
|
let ptth_resp = file_server::serve_all (&root, ptth_req).await;
|
||||||
|
|
||||||
|
let mut resp = Response::builder ()
|
||||||
|
.status (StatusCode::from (ptth_resp.parts.status_code));
|
||||||
|
|
||||||
|
use std::str::FromStr;
|
||||||
|
|
||||||
|
for (k, v) in ptth_resp.parts.headers.into_iter () {
|
||||||
|
resp = resp.header (hyper::header::HeaderName::from_str (&k).unwrap (), v);
|
||||||
|
}
|
||||||
|
|
||||||
|
let body = ptth_resp.body
|
||||||
|
.map (|b| Body::wrap_stream (b))
|
||||||
|
.unwrap_or_else (|| Body::empty ())
|
||||||
|
;
|
||||||
|
|
||||||
|
let resp = resp.body (body).unwrap ();
|
||||||
|
|
||||||
|
Ok (resp)
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
Ok (status_reply (StatusCode::NOT_FOUND, "404 Not Found\n"))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::main]
|
||||||
|
async fn main () -> Result <(), Box <dyn Error>> {
|
||||||
|
let addr = SocketAddr::from(([0, 0, 0, 0], 4000));
|
||||||
|
|
||||||
|
let state = Arc::new (ServerState::default ());
|
||||||
|
|
||||||
|
let make_svc = make_service_fn (|_conn| {
|
||||||
|
let state = state.clone ();
|
||||||
|
|
||||||
|
async {
|
||||||
|
Ok::<_, Infallible> (service_fn (move |req| {
|
||||||
|
let state = state.clone ();
|
||||||
|
|
||||||
|
handle_all (req, state)
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
let server = Server::bind (&addr).serve (make_svc);
|
||||||
|
|
||||||
|
server.await?;
|
||||||
|
|
||||||
|
Ok (())
|
||||||
|
}
|
|
@ -4,12 +4,13 @@ use std::{
|
||||||
};
|
};
|
||||||
|
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
use tokio::sync::mpsc::Receiver;
|
||||||
|
|
||||||
// Hyper doesn't seem to make it easy to de/ser requests
|
// Hyper doesn't seem to make it easy to de/ser requests
|
||||||
// and responses and stuff like that, so I do it by hand here.
|
// and responses and stuff like that, so I do it by hand here.
|
||||||
|
|
||||||
pub enum Error {
|
pub enum Error {
|
||||||
Unsupported,
|
UnsupportedMethod,
|
||||||
InvalidHeaderName,
|
InvalidHeaderName,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -32,7 +33,7 @@ impl TryFrom <hyper::Method> for Method {
|
||||||
match x {
|
match x {
|
||||||
hyper::Method::GET => Ok (Self::Get),
|
hyper::Method::GET => Ok (Self::Get),
|
||||||
hyper::Method::HEAD => Ok (Self::Head),
|
hyper::Method::HEAD => Ok (Self::Head),
|
||||||
_ => Err (Error::Unsupported),
|
_ => Err (Error::UnsupportedMethod),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -52,6 +53,30 @@ pub struct RequestParts {
|
||||||
pub headers: HashMap <String, Vec <u8>>,
|
pub headers: HashMap <String, Vec <u8>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl RequestParts {
|
||||||
|
pub fn from_hyper (
|
||||||
|
method: hyper::Method,
|
||||||
|
uri: String,
|
||||||
|
headers: hyper::HeaderMap <hyper::header::HeaderValue>
|
||||||
|
) -> Result <Self, Error>
|
||||||
|
{
|
||||||
|
use std::iter::FromIterator;
|
||||||
|
|
||||||
|
let method = Method::try_from (method)?;
|
||||||
|
let headers = HashMap::from_iter (
|
||||||
|
headers.into_iter ()
|
||||||
|
.filter_map (|(k, v)| k.map (|k| (k, v)))
|
||||||
|
.map (|(k, v)| (String::from (k.as_str ()), v.as_bytes ().to_vec ()))
|
||||||
|
);
|
||||||
|
|
||||||
|
Ok (Self {
|
||||||
|
method,
|
||||||
|
uri,
|
||||||
|
headers,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[derive (Deserialize, Serialize)]
|
#[derive (Deserialize, Serialize)]
|
||||||
pub struct WrappedRequest {
|
pub struct WrappedRequest {
|
||||||
pub id: String,
|
pub id: String,
|
||||||
|
@ -92,10 +117,15 @@ pub struct ResponseParts {
|
||||||
pub headers: HashMap <String, Vec <u8>>,
|
pub headers: HashMap <String, Vec <u8>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// reqwest and hyper have different Body types for _some reason_
|
||||||
|
// so I have to do everything myself
|
||||||
|
|
||||||
|
type Body = Receiver <Result <Vec <u8>, std::convert::Infallible>>;
|
||||||
|
|
||||||
#[derive (Default)]
|
#[derive (Default)]
|
||||||
pub struct Response {
|
pub struct Response {
|
||||||
pub parts: ResponseParts,
|
pub parts: ResponseParts,
|
||||||
pub body: Option <reqwest::Body>,
|
pub body: Option <Body>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Response {
|
impl Response {
|
||||||
|
@ -109,8 +139,17 @@ impl Response {
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn body (&mut self, b: reqwest::Body) -> &mut Self {
|
pub fn body (&mut self, b: Body) -> &mut Self {
|
||||||
self.body = Some (b);
|
self.body = Some (b);
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn body_bytes (&mut self, b: Vec <u8>) -> &mut Self {
|
||||||
|
let (mut tx, rx) = tokio::sync::mpsc::channel (1);
|
||||||
|
tokio::spawn (async move {
|
||||||
|
tx.send (Ok (b)).await.unwrap ();
|
||||||
|
});
|
||||||
|
self.body = Some (rx);
|
||||||
|
self
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,13 +1,8 @@
|
||||||
pub mod watcher;
|
pub mod watcher;
|
||||||
|
|
||||||
use std::{
|
use std::{
|
||||||
collections::*,
|
|
||||||
error::Error,
|
error::Error,
|
||||||
convert::{
|
convert::Infallible,
|
||||||
Infallible,
|
|
||||||
TryFrom,
|
|
||||||
},
|
|
||||||
iter::FromIterator,
|
|
||||||
net::SocketAddr,
|
net::SocketAddr,
|
||||||
sync::{
|
sync::{
|
||||||
Arc
|
Arc
|
||||||
|
@ -125,23 +120,14 @@ async fn handle_http_request (
|
||||||
{
|
{
|
||||||
let parts = {
|
let parts = {
|
||||||
let id = ulid::Ulid::new ().to_string ();
|
let id = ulid::Ulid::new ().to_string ();
|
||||||
let method = match http_serde::Method::try_from (req.method) {
|
let req = match http_serde::RequestParts::from_hyper (req.method, uri, req.headers) {
|
||||||
Ok (x) => x,
|
Ok (x) => x,
|
||||||
_ => return status_reply (StatusCode::BAD_REQUEST, "Method not supported"),
|
_ => return status_reply (StatusCode::BAD_REQUEST, "Bad request"),
|
||||||
};
|
};
|
||||||
let headers = HashMap::from_iter (
|
|
||||||
req.headers.into_iter ()
|
|
||||||
.filter_map (|(k, v)| k.map (|k| (k, v)))
|
|
||||||
.map (|(k, v)| (String::from (k.as_str ()), v.as_bytes ().to_vec ()))
|
|
||||||
);
|
|
||||||
|
|
||||||
http_serde::WrappedRequest {
|
http_serde::WrappedRequest {
|
||||||
id,
|
id,
|
||||||
req: http_serde::RequestParts {
|
req,
|
||||||
method,
|
|
||||||
uri,
|
|
||||||
headers,
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -211,6 +197,9 @@ async fn handle_all (req: Request <Body>, state: Arc <ServerState>)
|
||||||
//println! ("{}", path);
|
//println! ("{}", path);
|
||||||
|
|
||||||
if req.method () == Method::POST {
|
if req.method () == Method::POST {
|
||||||
|
// This is stuff the server can use. Clients can't
|
||||||
|
// POST right now
|
||||||
|
|
||||||
return Ok (if let Some (request_code) = prefix_match (path, "/http_response/") {
|
return Ok (if let Some (request_code) = prefix_match (path, "/http_response/") {
|
||||||
let request_code = request_code.into ();
|
let request_code = request_code.into ();
|
||||||
handle_http_response (req, state, request_code).await
|
handle_http_response (req, state, request_code).await
|
||||||
|
|
|
@ -7,10 +7,6 @@ use std::{
|
||||||
path::{Path, PathBuf},
|
path::{Path, PathBuf},
|
||||||
};
|
};
|
||||||
|
|
||||||
// file_server shouldn't depend on reqwest, but for now it
|
|
||||||
// does depend on their Body struct
|
|
||||||
use reqwest::Body;
|
|
||||||
|
|
||||||
use tokio::{
|
use tokio::{
|
||||||
fs::{
|
fs::{
|
||||||
File,
|
File,
|
||||||
|
@ -103,7 +99,7 @@ async fn serve_dir (mut dir: ReadDir) -> http_serde::Response {
|
||||||
let mut response = http_serde::Response::default ();
|
let mut response = http_serde::Response::default ();
|
||||||
|
|
||||||
response.header ("content-type".into (), String::from ("text/html").into_bytes ());
|
response.header ("content-type".into (), String::from ("text/html").into_bytes ());
|
||||||
response.body (Body::wrap_stream (rx));
|
response.body (rx);
|
||||||
|
|
||||||
response
|
response
|
||||||
}
|
}
|
||||||
|
@ -116,7 +112,7 @@ async fn serve_file (
|
||||||
) -> http_serde::Response {
|
) -> http_serde::Response {
|
||||||
let (tx, rx) = channel (2);
|
let (tx, rx) = channel (2);
|
||||||
let body = if should_send_body {
|
let body = if should_send_body {
|
||||||
Some (Body::wrap_stream (rx))
|
Some (rx)
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
None
|
None
|
||||||
|
@ -200,7 +196,7 @@ async fn serve_error (
|
||||||
{
|
{
|
||||||
let mut resp = http_serde::Response::default ();
|
let mut resp = http_serde::Response::default ();
|
||||||
resp.status_code (status_code)
|
resp.status_code (status_code)
|
||||||
.body (msg.into ());
|
.body_bytes (msg.into_bytes ());
|
||||||
resp
|
resp
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -232,7 +228,10 @@ pub async fn serve_all (
|
||||||
|
|
||||||
use percent_encoding::*;
|
use percent_encoding::*;
|
||||||
|
|
||||||
|
// TODO: There is totally a dir traversal attack in here somewhere
|
||||||
|
|
||||||
let encoded_path = &parts.uri [1..];
|
let encoded_path = &parts.uri [1..];
|
||||||
|
|
||||||
let path = percent_decode (encoded_path.as_bytes ()).decode_utf8 ().unwrap ();
|
let path = percent_decode (encoded_path.as_bytes ()).decode_utf8 ().unwrap ();
|
||||||
|
|
||||||
let mut full_path = PathBuf::from (root);
|
let mut full_path = PathBuf::from (root);
|
||||||
|
@ -253,3 +252,33 @@ pub async fn serve_all (
|
||||||
serve_error (http_serde::StatusCode::NotFound, "404 Not Found".into ()).await
|
serve_error (http_serde::StatusCode::NotFound, "404 Not Found".into ()).await
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg (test)]
|
||||||
|
mod tests {
|
||||||
|
#[test]
|
||||||
|
fn i_hate_paths () {
|
||||||
|
use std::{
|
||||||
|
ffi::OsStr,
|
||||||
|
path::{Component, Path}
|
||||||
|
};
|
||||||
|
|
||||||
|
let mut components = Path::new ("/home/user").components ();
|
||||||
|
|
||||||
|
assert_eq! (components.next (), Some (Component::RootDir));
|
||||||
|
assert_eq! (components.next (), Some (Component::Normal (OsStr::new ("home"))));
|
||||||
|
assert_eq! (components.next (), Some (Component::Normal (OsStr::new ("user"))));
|
||||||
|
assert_eq! (components.next (), None);
|
||||||
|
|
||||||
|
let mut components = Path::new ("./home/user").components ();
|
||||||
|
|
||||||
|
assert_eq! (components.next (), Some (Component::CurDir));
|
||||||
|
assert_eq! (components.next (), Some (Component::Normal (OsStr::new ("home"))));
|
||||||
|
assert_eq! (components.next (), Some (Component::Normal (OsStr::new ("user"))));
|
||||||
|
assert_eq! (components.next (), None);
|
||||||
|
|
||||||
|
let mut components = Path::new (".").components ();
|
||||||
|
|
||||||
|
assert_eq! (components.next (), Some (Component::CurDir));
|
||||||
|
assert_eq! (components.next (), None);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -15,7 +15,7 @@ use tokio::{
|
||||||
|
|
||||||
use crate::http_serde;
|
use crate::http_serde;
|
||||||
|
|
||||||
mod file_server;
|
pub mod file_server;
|
||||||
|
|
||||||
const SERVER_NAME: &str = "alien_wildlands";
|
const SERVER_NAME: &str = "alien_wildlands";
|
||||||
|
|
||||||
|
@ -47,7 +47,7 @@ async fn handle_req_resp (
|
||||||
.header (crate::PTTH_MAGIC_HEADER, base64::encode (rmp_serde::to_vec (&response.parts).unwrap ()));
|
.header (crate::PTTH_MAGIC_HEADER, base64::encode (rmp_serde::to_vec (&response.parts).unwrap ()));
|
||||||
|
|
||||||
if let Some (body) = response.body {
|
if let Some (body) = response.body {
|
||||||
resp_req = resp_req.body (body);
|
resp_req = resp_req.body (reqwest::Body::wrap_stream (body));
|
||||||
}
|
}
|
||||||
|
|
||||||
//println! ("Step 6");
|
//println! ("Step 6");
|
||||||
|
|
Loading…
Reference in New Issue