Skip to content

Commit 0156f52

Browse files
committed
feat: provide support for Handler trait
1 parent 97cd9a7 commit 0156f52

File tree

10 files changed

+111
-422
lines changed

10 files changed

+111
-422
lines changed

src/http-server/src/handler/file_explorer/mod.rs

Lines changed: 40 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ use std::fs::read_dir;
77
use std::path::{Component, Path, PathBuf};
88

99
use anyhow::{Context, Result};
10+
use async_trait::async_trait;
1011
use bytes::Bytes;
1112
use http::{HeaderValue, Method, Response, StatusCode, Uri, header::CONTENT_TYPE, request::Parts};
1213
use http_body_util::{BodyExt, Full};
@@ -16,6 +17,7 @@ use proto::{DirectoryEntry, DirectoryIndex, EntryType, Sort};
1617
use rust_embed::Embed;
1718
use tokio::io::AsyncWriteExt;
1819

20+
use crate::handler::Handler;
1921
use crate::server::{HttpRequest, HttpResponse};
2022

2123
use self::proto::BreadcrumbItem;
@@ -38,41 +40,6 @@ impl FileExplorer {
3840
}
3941
}
4042

41-
pub async fn handle(&self, req: HttpRequest) -> Result<HttpResponse> {
42-
let (parts, body) = req.into_parts();
43-
let body = body.collect().await.unwrap().to_bytes();
44-
45-
if parts.uri.path().starts_with("/api/v1") {
46-
return self.handle_api(parts, body).await;
47-
}
48-
49-
let path = parts.uri.path();
50-
let path = path.strip_prefix('/').unwrap_or(path);
51-
52-
if let Some(file) = FileExplorerAssets::get(path) {
53-
let content_type = mime_guess::from_path(path).first_or_octet_stream();
54-
let content_type = HeaderValue::from_str(content_type.as_ref()).unwrap();
55-
let body = Full::new(Bytes::from(file.data.to_vec()));
56-
let mut response = Response::new(body);
57-
let mut headers = response.headers().clone();
58-
59-
headers.append(CONTENT_TYPE, content_type);
60-
*response.headers_mut() = headers;
61-
62-
return Ok(response);
63-
}
64-
65-
let index = FileExplorerAssets::get("index.html").unwrap();
66-
let body = Full::new(Bytes::from(index.data.to_vec()));
67-
let mut response = Response::new(body);
68-
let mut headers = response.headers().clone();
69-
70-
headers.append(CONTENT_TYPE, "text/html".try_into().unwrap());
71-
*response.headers_mut() = headers;
72-
73-
Ok(response)
74-
}
75-
7643
async fn handle_api(&self, parts: Parts, body: Bytes) -> Result<HttpResponse> {
7744
let path = Self::parse_req_uri(parts.uri.clone()).unwrap();
7845

@@ -328,3 +295,41 @@ impl FileExplorer {
328295
Self::index_directory(self.path.clone(), path)
329296
}
330297
}
298+
299+
#[async_trait]
300+
impl Handler for FileExplorer {
301+
async fn handle(&self, req: HttpRequest) -> Result<HttpResponse> {
302+
let (parts, body) = req.into_parts();
303+
let body = body.collect().await.unwrap().to_bytes();
304+
305+
if parts.uri.path().starts_with("/api/v1") {
306+
return self.handle_api(parts, body).await;
307+
}
308+
309+
let path = parts.uri.path();
310+
let path = path.strip_prefix('/').unwrap_or(path);
311+
312+
if let Some(file) = FileExplorerAssets::get(path) {
313+
let content_type = mime_guess::from_path(path).first_or_octet_stream();
314+
let content_type = HeaderValue::from_str(content_type.as_ref()).unwrap();
315+
let body = Full::new(Bytes::from(file.data.to_vec()));
316+
let mut response = Response::new(body);
317+
let mut headers = response.headers().clone();
318+
319+
headers.append(CONTENT_TYPE, content_type);
320+
*response.headers_mut() = headers;
321+
322+
return Ok(response);
323+
}
324+
325+
let index = FileExplorerAssets::get("index.html").unwrap();
326+
let body = Full::new(Bytes::from(index.data.to_vec()));
327+
let mut response = Response::new(body);
328+
let mut headers = response.headers().clone();
329+
330+
headers.append(CONTENT_TYPE, "text/html".try_into().unwrap());
331+
*response.headers_mut() = headers;
332+
333+
Ok(response)
334+
}
335+
}

src/http-server/src/handler/file_explorer/utils.rs

Lines changed: 0 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -33,35 +33,3 @@ pub fn decode_uri(file_path: &str) -> PathBuf {
3333
})
3434
.collect::<PathBuf>()
3535
}
36-
37-
#[cfg(test)]
38-
mod tests {
39-
use std::path::PathBuf;
40-
use std::str::FromStr;
41-
42-
use super::{decode_uri, encode_uri};
43-
44-
#[test]
45-
fn encodes_uri() {
46-
let file_path = "/these are important files/do_not_delete/file name.txt";
47-
let file_path = PathBuf::from_str(file_path).unwrap();
48-
let file_path = encode_uri(&file_path);
49-
50-
assert_eq!(
51-
file_path,
52-
"/these%20are%20important%20files/do_not_delete/file%20name.txt"
53-
);
54-
}
55-
56-
#[test]
57-
fn decodes_uri() {
58-
let file_path = "these%20are%20important%20files/do_not_delete/file%20name.txt";
59-
let file_path = decode_uri(file_path);
60-
let file_path = file_path.to_str().unwrap();
61-
62-
assert_eq!(
63-
file_path,
64-
"these are important files/do_not_delete/file name.txt"
65-
);
66-
}
67-
}

src/http-server/src/handler/file_server/mod.rs

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,13 +2,16 @@ mod service;
22
mod utils;
33

44
use anyhow::Result;
5+
use async_trait::async_trait;
56
use bytes::Bytes;
67
use http::{Method, Response, StatusCode};
78
use http_body_util::Full;
89

9-
use crate::handler::file_server::service::FileServerConfig;
10+
use crate::handler::Handler;
1011
use crate::server::{HttpRequest, HttpResponse};
1112

13+
pub use crate::handler::file_server::service::FileServerConfig;
14+
1215
use self::service::FileServer as FileServerService;
1316

1417
pub struct FileServer {
@@ -21,8 +24,11 @@ impl FileServer {
2124
file_service: FileServerService::new(config),
2225
}
2326
}
27+
}
2428

25-
pub async fn handle(&self, req: HttpRequest) -> Result<HttpResponse> {
29+
#[async_trait]
30+
impl Handler for FileServer {
31+
async fn handle(&self, req: HttpRequest) -> Result<HttpResponse> {
2632
let (parts, _) = req.into_parts();
2733

2834
if parts.uri.path().starts_with("/api/v1") {
Lines changed: 5 additions & 62 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,10 @@
11
use std::fs::Metadata;
2-
use std::mem::MaybeUninit;
32
use std::path::PathBuf;
4-
use std::pin::Pin;
5-
use std::task::{self, Poll};
63

74
use anyhow::{Context, Result};
85
use chrono::{DateTime, Local};
9-
use futures::Stream;
10-
use hyper::body::Bytes;
116
use mime_guess::{Mime, from_path};
12-
use tokio::io::{AsyncRead, ReadBuf};
13-
14-
pub const FILE_BUFFER_SIZE: usize = 8 * 1024;
15-
16-
pub type FileBuffer = Box<[MaybeUninit<u8>; FILE_BUFFER_SIZE]>;
7+
use tokio::io::AsyncReadExt;
178

189
/// Wrapper around `tokio::fs::File` built from a OS ScopedFileSystem file
1910
/// providing `std::fs::Metadata` and the path to such file
@@ -51,57 +42,9 @@ impl File {
5142
Ok(modified)
5243
}
5344

54-
#[allow(dead_code)]
55-
pub fn bytes(self) -> Vec<u8> {
56-
let byte_stream = ByteStream {
57-
file: self.file,
58-
buffer: Box::new([MaybeUninit::uninit(); FILE_BUFFER_SIZE]),
59-
};
60-
61-
byte_stream
62-
.buffer
63-
.iter()
64-
.map(|muint| unsafe { muint.assume_init() })
65-
.collect::<Vec<u8>>()
66-
}
67-
}
68-
69-
pub struct ByteStream {
70-
file: tokio::fs::File,
71-
buffer: FileBuffer,
72-
}
73-
74-
impl From<File> for ByteStream {
75-
fn from(file: File) -> Self {
76-
ByteStream {
77-
file: file.file,
78-
buffer: Box::new([MaybeUninit::uninit(); FILE_BUFFER_SIZE]),
79-
}
80-
}
81-
}
82-
83-
impl Stream for ByteStream {
84-
type Item = Result<Bytes>;
85-
86-
fn poll_next(mut self: Pin<&mut Self>, cx: &mut task::Context<'_>) -> Poll<Option<Self::Item>> {
87-
let ByteStream {
88-
ref mut file,
89-
ref mut buffer,
90-
} = *self;
91-
let mut read_buffer = ReadBuf::uninit(&mut buffer[..]);
92-
93-
match Pin::new(file).poll_read(cx, &mut read_buffer) {
94-
Poll::Ready(Ok(())) => {
95-
let filled = read_buffer.filled();
96-
97-
if filled.is_empty() {
98-
Poll::Ready(None)
99-
} else {
100-
Poll::Ready(Some(Ok(Bytes::copy_from_slice(filled))))
101-
}
102-
}
103-
Poll::Ready(Err(error)) => Poll::Ready(Some(Err(error.into()))),
104-
Poll::Pending => Poll::Pending,
105-
}
45+
pub async fn bytes(&mut self) -> Result<Vec<u8>> {
46+
let mut buf = Vec::with_capacity(self.size() as usize);
47+
self.file.read_to_end(&mut buf).await?;
48+
Ok(buf)
10649
}
10750
}
Lines changed: 15 additions & 61 deletions
Original file line numberDiff line numberDiff line change
@@ -1,28 +1,19 @@
1-
use std::mem::MaybeUninit;
2-
use std::pin::Pin;
3-
use std::task::{self, Poll};
1+
use std::fmt::Display;
42

53
use anyhow::{Context, Result};
64
use chrono::{DateTime, Local, Utc};
7-
use futures::Stream;
85
use http::response::Builder as HttpResponseBuilder;
96
use http_body_util::Full;
107
use hyper::body::Bytes;
11-
use tokio::io::{AsyncRead, ReadBuf};
128

139
use crate::server::HttpResponse;
1410

1511
use super::file::File;
1612

17-
const FILE_BUFFER_SIZE: usize = 8 * 1024;
18-
19-
pub type FileBuffer = Box<[MaybeUninit<u8>; FILE_BUFFER_SIZE]>;
20-
2113
/// HTTP Response `Cache-Control` directive
2214
///
2315
/// Allow dead code until we have support for cache control configuration
2416
#[allow(dead_code)]
25-
2617
pub enum CacheControlDirective {
2718
/// Cache-Control: must-revalidate
2819
MustRevalidate,
@@ -44,25 +35,25 @@ pub enum CacheControlDirective {
4435
SMaxAge(u64),
4536
}
4637

47-
impl ToString for CacheControlDirective {
48-
fn to_string(&self) -> String {
38+
impl Display for CacheControlDirective {
39+
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
4940
match &self {
50-
Self::MustRevalidate => String::from("must-revalidate"),
51-
Self::NoCache => String::from("no-cache"),
52-
Self::NoStore => String::from("no-store"),
53-
Self::NoTransform => String::from("no-transform"),
54-
Self::Public => String::from("public"),
55-
Self::Private => String::from("private"),
56-
Self::ProxyRavalidate => String::from("proxy-revalidate"),
57-
Self::MaxAge(age) => format!("max-age={}", age),
58-
Self::SMaxAge(age) => format!("s-maxage={}", age),
41+
Self::MustRevalidate => write!(f, "must-revalidate"),
42+
Self::NoCache => write!(f, "no-cache"),
43+
Self::NoStore => write!(f, "no-store"),
44+
Self::NoTransform => write!(f, "no-transform"),
45+
Self::Public => write!(f, "public"),
46+
Self::Private => write!(f, "private"),
47+
Self::ProxyRavalidate => write!(f, "proxy-revalidate"),
48+
Self::MaxAge(age) => write!(f, "max-age={}", age),
49+
Self::SMaxAge(age) => write!(f, "s-maxage={}", age),
5950
}
6051
}
6152
}
6253

54+
#[derive(Debug)]
6355
pub struct ResponseHeaders {
6456
cache_control: String,
65-
content_length: u64,
6657
content_type: String,
6758
etag: String,
6859
last_modified: String,
@@ -77,17 +68,12 @@ impl ResponseHeaders {
7768

7869
Ok(ResponseHeaders {
7970
cache_control: cache_control_directive.to_string(),
80-
content_length: ResponseHeaders::content_length(file),
8171
content_type: ResponseHeaders::content_type(file),
8272
etag: ResponseHeaders::etag(file, &last_modified),
8373
last_modified: ResponseHeaders::last_modified(&last_modified),
8474
})
8575
}
8676

87-
fn content_length(file: &File) -> u64 {
88-
file.size()
89-
}
90-
9177
fn content_type(file: &File) -> String {
9278
file.mime().to_string()
9379
}
@@ -112,52 +98,20 @@ impl ResponseHeaders {
11298
}
11399

114100
pub async fn make_http_file_response(
115-
file: File,
101+
mut file: File,
116102
cache_control_directive: CacheControlDirective,
117103
) -> Result<HttpResponse> {
118104
let headers = ResponseHeaders::new(&file, cache_control_directive)?;
119105
let builder = HttpResponseBuilder::new()
120-
.header(http::header::CONTENT_LENGTH, headers.content_length)
121106
.header(http::header::CACHE_CONTROL, headers.cache_control)
122107
.header(http::header::CONTENT_TYPE, headers.content_type)
123108
.header(http::header::ETAG, headers.etag)
124109
.header(http::header::LAST_MODIFIED, headers.last_modified);
125110

126-
let body = Full::new(Bytes::from(file.bytes()));
111+
let body = Full::new(Bytes::from(file.bytes().await?));
127112
let response = builder
128113
.body(body)
129114
.context("Failed to build HTTP File Response")?;
130115

131116
Ok(response)
132117
}
133-
134-
pub struct ByteStream {
135-
file: tokio::fs::File,
136-
buffer: FileBuffer,
137-
}
138-
139-
impl Stream for ByteStream {
140-
type Item = Result<Bytes>;
141-
142-
fn poll_next(mut self: Pin<&mut Self>, cx: &mut task::Context<'_>) -> Poll<Option<Self::Item>> {
143-
let ByteStream {
144-
ref mut file,
145-
ref mut buffer,
146-
} = *self;
147-
let mut read_buffer = ReadBuf::uninit(&mut buffer[..]);
148-
149-
match Pin::new(file).poll_read(cx, &mut read_buffer) {
150-
Poll::Ready(Ok(())) => {
151-
let filled = read_buffer.filled();
152-
153-
if filled.is_empty() {
154-
Poll::Ready(None)
155-
} else {
156-
Poll::Ready(Some(Ok(Bytes::copy_from_slice(filled))))
157-
}
158-
}
159-
Poll::Ready(Err(error)) => Poll::Ready(Some(Err(error.into()))),
160-
Poll::Pending => Poll::Pending,
161-
}
162-
}
163-
}

0 commit comments

Comments
 (0)