Skip to content

Commit eb1a5f0

Browse files
committed
refactor: avoid cloning server instance by using Arc<Server>
Currently when creating a server instance and spawing a Tokio task the server instance is cloned (server derive-implements "Clone" trait). Instead the same instance is used and an Arc<Server> is handled to the concurrent Tokio task. This reduces repetition and memory consumption and keeps the Server instance unique and consistent through threads.
1 parent b917612 commit eb1a5f0

File tree

12 files changed

+1509
-102
lines changed

12 files changed

+1509
-102
lines changed

CHANGELOG.md

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,56 @@
22

33
<Empty>
44

5+
<a name="v0.3.2"></a>
6+
## v0.3.2 (2021-06-08)
7+
8+
> Requires Rust: rustc 1.49.0 (e1884a8e3 2020-12-29)
9+
10+
#### Features
11+
12+
* Add crate logo by @PalyZambrano
13+
14+
#### Improvements
15+
16+
* Avoid clonning server instance by using Arc<Server> over `Server.clone()`
17+
18+
Currently when creating a server instance and spawing a Tokio task
19+
the server instance is cloned (`Server` derive-implements the "Clone" trait).
20+
21+
Instead the same instance is used and a single Arc<Server> is handled to the
22+
concurrent Tokio task.
23+
24+
This reduces repetition and memory consumption and also keeps the Server
25+
instance unique and consistent through threads.
26+
27+
* Resolve static file serving with `resolve_path` instead of `resolve` from
28+
`hyper_staticfile`. This makes file serving more conventional and also
29+
versatile, by relying on the Request URI instead of the whole `hyper::Request`
30+
instance.
31+
32+
* `FileExplorer` middleware now consumes an `Arc<hyper::Request<Body>>` instead
33+
of taking ownership of the `hyper::Request<Body>` instance
34+
35+
* Less transformations are required for a `Request` instance in order to serve
36+
a static file.
37+
38+
* `HttpHandler` now is built over an `Arc<Config>` instead of consuming the
39+
`Config` instance. Previously the `Config` instance uses to implement the `Clone`
40+
trait, now an `Arc<Config>` is used in its place making the `Config` struct
41+
easier to mantain an extensible.
42+
43+
* `MiddlewareAfter` now supports the `hyper::Request<Body>` as well, this brings
44+
support to read from the `hyper::Request<Body>` on middleware after execution.
45+
46+
<a name="v0.3.1"></a>
47+
## v0.3.1 (2021-05-31)
48+
49+
> Requires Rust: rustc 1.49.0 (e1884a8e3 2020-12-29)
50+
51+
#### Fixes
52+
53+
* Fix crate binary name to be "http-server" instead of "main"
54+
555
<a name="v0.3.0"></a>
656
## v0.3.0 (2021-05-09)
757

Cargo.lock

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[package]
22
name = "http-server"
3-
version = "0.3.1"
3+
version = "0.3.2"
44
authors = ["Esteban Borai <estebanborai@gmail.com>"]
55
edition = "2018"
66
description = "Simple and configurable command-line HTTP server"

README.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,7 @@
11
<div>
2+
<div align="center" style="display: block; text-align: center;">
3+
<img src="./assets/logo.svg" height="120" width="120" />
4+
</div>
25
<h1 align="center">http-server</h1>
36
<h4 align="center">Simple and configurable command-line HTTP server</h4>
47
</div>

assets/logo.ai

Lines changed: 1388 additions & 0 deletions
Large diffs are not rendered by default.

assets/logo.svg

Lines changed: 1 addition & 0 deletions
Loading

src/config/mod.rs

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,6 @@ use self::file::ConfigFile;
1616
use self::tls::TlsConfig;
1717

1818
/// Server instance configuration used on initialization
19-
#[derive(Debug, Clone)]
2019
pub struct Config {
2120
address: SocketAddr,
2221
host: IpAddr,
@@ -28,6 +27,14 @@ pub struct Config {
2827
}
2928

3029
impl Config {
30+
pub fn host(&self) -> IpAddr {
31+
self.host
32+
}
33+
34+
pub fn port(&self) -> u16 {
35+
self.port
36+
}
37+
3138
pub fn address(&self) -> SocketAddr {
3239
self.address
3340
}

src/server/handler/file_explorer.rs

Lines changed: 18 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ use handlebars::Handlebars;
55
use http::response::Builder as HttpResponseBuilder;
66
use http::{Method, StatusCode};
77
use hyper::{Body, Request, Response};
8-
use hyper_staticfile::{resolve, ResolveResult, ResponseBuilder as FileResponseBuilder};
8+
use hyper_staticfile::{resolve_path, ResolveResult, ResponseBuilder as FileResponseBuilder};
99
use serde::Serialize;
1010
use std::cmp::{Ord, Ordering};
1111
use std::fs::read_dir;
@@ -18,13 +18,14 @@ use crate::server::middleware::Handler;
1818

1919
/// Creates a `middleware::Handler` which makes use of the provided `FileExplorer`
2020
pub fn make_file_explorer_handler(file_explorer: Arc<FileExplorer>) -> Handler {
21-
Box::new(move |request: Request<Body>| {
21+
Box::new(move |request: Arc<Request<Body>>| {
2222
let file_explorer = Arc::clone(&file_explorer);
23+
let req_path = request.uri().to_string();
2324

2425
Box::pin(async move {
2526
if request.method() == Method::GET {
2627
return file_explorer
27-
.resolve(request)
28+
.resolve(req_path)
2829
.await
2930
.map_err(|e| {
3031
HttpResponseBuilder::new()
@@ -181,8 +182,11 @@ impl<'a> FileExplorer {
181182
///
182183
/// If the matched path resolves to a file, attempts to render it if the
183184
/// MIME type is supported, otherwise returns the binary (downloadable file)
184-
pub async fn resolve(&self, req: Request<Body>) -> Result<Response<Body>> {
185-
match resolve(&self.root_dir, &req).await.unwrap() {
185+
pub async fn resolve(&self, req_path: String) -> Result<Response<Body>> {
186+
match resolve_path(&self.root_dir, req_path.as_str())
187+
.await
188+
.unwrap()
189+
{
186190
ResolveResult::MethodNotMatched => Ok(HttpResponseBuilder::new()
187191
.status(StatusCode::BAD_REQUEST)
188192
.body(Body::empty())
@@ -192,8 +196,10 @@ impl<'a> FileExplorer {
192196
.body(Body::empty())
193197
.expect("Failed to build response")),
194198
ResolveResult::NotFound => {
195-
if req.uri() == "/" {
196-
let directory_path = self.make_absolute_path_from_request(&req).unwrap();
199+
if req_path == "/" {
200+
let directory_path = self
201+
.make_absolute_path_from_req_path(req_path.as_str())
202+
.unwrap();
197203

198204
return self.render_directory_index(directory_path).await;
199205
}
@@ -208,12 +214,13 @@ impl<'a> FileExplorer {
208214
.body(Body::empty())
209215
.expect("Failed to build response")),
210216
ResolveResult::IsDirectory => {
211-
let directory_path = self.make_absolute_path_from_request(&req).unwrap();
217+
let directory_path = self
218+
.make_absolute_path_from_req_path(req_path.as_str())
219+
.unwrap();
212220

213221
return self.render_directory_index(directory_path).await;
214222
}
215223
ResolveResult::Found(file, metadata, mime) => Ok(FileResponseBuilder::new()
216-
.request(&req)
217224
.cache_headers(self.cache_headers)
218225
.build(ResolveResult::Found(file, metadata, mime))
219226
.expect("Failed to build response")),
@@ -281,9 +288,8 @@ impl<'a> FileExplorer {
281288

282289
/// Creates an absolute path by appending the HTTP Request URI to the
283290
/// `root_dir`
284-
fn make_absolute_path_from_request(&self, req: &Request<Body>) -> Result<PathBuf> {
291+
fn make_absolute_path_from_req_path(&self, req_path: &str) -> Result<PathBuf> {
285292
let mut root_dir = self.root_dir.clone();
286-
let req_path = req.uri().to_string();
287293
let req_path = if req_path.starts_with('/') {
288294
let path = req_path[1..req_path.len()].to_string();
289295

@@ -294,7 +300,7 @@ impl<'a> FileExplorer {
294300

295301
PathBuf::from_str(path.as_str())?
296302
} else {
297-
PathBuf::from_str(req_path.as_str())?
303+
PathBuf::from_str(req_path)?
298304
};
299305

300306
root_dir.push(req_path);

src/server/handler/mod.rs

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,8 @@ mod file_explorer;
22

33
use anyhow::Result;
44
use hyper::{Body, Request, Response};
5-
use std::{convert::TryFrom, sync::Arc};
5+
use std::convert::TryFrom;
6+
use std::sync::Arc;
67

78
use crate::Config;
89

@@ -26,8 +27,8 @@ impl HttpHandler {
2627
}
2728
}
2829

29-
impl From<Config> for HttpHandler {
30-
fn from(config: Config) -> Self {
30+
impl From<Arc<Config>> for HttpHandler {
31+
fn from(config: Arc<Config>) -> Self {
3132
let file_explorer = Arc::new(FileExplorer::new(config.root_dir()));
3233
let middleware = Middleware::try_from(config).unwrap();
3334
let middleware = Arc::new(middleware);

src/server/middleware/make_cors_middleware.rs

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
use hyper::header::{self, HeaderName, HeaderValue};
22
use hyper::{Body, Response};
33

4-
use crate::config::Config;
4+
use crate::config::cors::CorsConfig;
55

66
use super::MiddlewareAfter;
77

@@ -19,8 +19,7 @@ use super::MiddlewareAfter;
1919
/// `make_cors_middlware` should only be called when a `CorsConfig` is defined.
2020
///
2121
/// Also panics if any CORS header value is not a valid UTF-8 string
22-
pub fn make_cors_middleware(config: Config) -> MiddlewareAfter {
23-
let cors_config = config.cors().unwrap();
22+
pub fn make_cors_middleware(cors_config: CorsConfig) -> MiddlewareAfter {
2423
let mut cors_headers: Vec<(HeaderName, HeaderValue)> = Vec::new();
2524

2625
if cors_config.allow_credentials {
@@ -87,7 +86,7 @@ pub fn make_cors_middleware(config: Config) -> MiddlewareAfter {
8786
));
8887
}
8988

90-
Box::new(move |response: &mut Response<Body>| {
89+
Box::new(move |_: _, response: &mut Response<Body>| {
9190
let headers = response.headers_mut();
9291

9392
cors_headers.iter().for_each(|(header, value)| {

0 commit comments

Comments
 (0)