Skip to content

Commit cf47906

Browse files
authored
feat: SPA and index.html serving (#420)
Copy/pasted every important bit from the previous PR. Everything looks great except for one small problem: When the `--spa` flag is used, the `--index` flag has to be enabled as well. But the code is really split up into a lot of files and I don't know where I can set `cli.index` to `true` if `cli.spa` is `true` in the code
1 parent 4faa021 commit cf47906

File tree

10 files changed

+221
-67
lines changed

10 files changed

+221
-67
lines changed

README.md

Lines changed: 50 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -8,13 +8,13 @@
88

99
<div align="center">
1010

11-
[![Crates.io](https://img.shields.io/crates/v/http-server.svg)](https://crates.io/crates/http-server)
12-
[![Documentation](https://docs.rs/http-server/badge.svg)](https://docs.rs/http-server)
13-
![Build](https://github.com/http-server-rs/http-server/workflows/build/badge.svg)
14-
![Clippy](https://github.com/http-server-rs/http-server/workflows/clippy/badge.svg)
15-
![Formatter](https://github.com/http-server-rs/http-server/workflows/fmt/badge.svg)
16-
![Tests](https://github.com/http-server-rs/http-server/workflows/test/badge.svg)
17-
![Benchs](https://github.com/http-server-rs/http-server/workflows/bench/badge.svg)
11+
[![Crates.io](https://img.shields.io/crates/v/http-server.svg)](https://crates.io/crates/http-server)
12+
[![Documentation](https://docs.rs/http-server/badge.svg)](https://docs.rs/http-server)
13+
![Build](https://github.com/http-server-rs/http-server/workflows/build/badge.svg)
14+
![Clippy](https://github.com/http-server-rs/http-server/workflows/clippy/badge.svg)
15+
![Formatter](https://github.com/http-server-rs/http-server/workflows/fmt/badge.svg)
16+
![Tests](https://github.com/http-server-rs/http-server/workflows/test/badge.svg)
17+
![Benches](https://github.com/http-server-rs/http-server/workflows/bench/badge.svg)
1818

1919
</div>
2020

@@ -45,10 +45,12 @@ FLAGS:
4545
--graceful-shutdown Waits for all requests to fulfill before shutting down the server
4646
--gzip Enable GZip compression for HTTP Responses
4747
--help Prints help information
48-
--logger Prints HTTP request and response details to stdout
48+
-l, --logger Prints HTTP request and response details to stdout
49+
-q, --quiet Turns off stdout/stderr logging
50+
--spa Route non-existent files to /index.html
4951
--tls Enables HTTPS serving using TLS
52+
-i, --index Route directories to index.html if present
5053
-V, --version Prints version information
51-
-q, --quiet Turns off stdout/stderr logging
5254
5355
OPTIONS:
5456
-c, --config <config> Path to TOML configuration file
@@ -74,19 +76,21 @@ configurations will be used. You can always change this behavior by either
7476
creating your own config with the [Configuration TOML](https://github.com/http-server-rs/http-server/blob/main/fixtures/config.toml) file
7577
or by providing CLI arguments described in the [usage](#usage) section.
7678

77-
Name | Description | Default
78-
--- | --- | ---
79-
Host | Address to bind the server | `127.0.0.1`
80-
Port | Port to bind the server | `7878`
81-
Root Directory | The directory to serve files from | `CWD`
82-
File Explorer UI | A File Explorer UI for the directory configured as the _Root Directory_ | Enabled
83-
Configuration File | Specifies a configuration file. [Example](https://github.com/http-server-rs/http-server/blob/main/fixtures/config.toml) | Disabled
84-
HTTPS (TLS) | HTTPS Secure connection configuration. Refer to [TLS (HTTPS)](https://github.com/http-server-rs/http-server#tls-https) reference | Disabled
85-
CORS | Cross-Origin-Resource-Sharing headers support. Refer to [CORS](https://github.com/http-server-rs/http-server#cross-origin-resource-sharing-cors) reference | Disabled
86-
Compression | GZip compression for HTTP Response Bodies. Refer to [Compression](https://github.com/http-server-rs/http-server#compression) reference | Disabled
87-
Quiet | Don't print server details when running. This doesn't include any logging capabilities. | Disabled
88-
Basic Authentication | Authorize requests using Basic Authentication. Refer to [Basic Authentication](https://github.com/http-server-rs/http-server#basic-authentication) | Disabled
89-
Logger | Prints HTTP request and response details to stdout | Disabled
79+
| Name | Description | Default |
80+
| -------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------- |
81+
| Host | Address to bind the server | `127.0.0.1` |
82+
| Port | Port to bind the server | `7878` |
83+
| Root Directory | The directory to serve files from | `CWD` |
84+
| File Explorer UI | A File Explorer UI for the directory configured as the _Root Directory_ | Enabled |
85+
| Configuration File | Specifies a configuration file. [Example](https://github.com/http-server-rs/http-server/blob/main/fixtures/config.toml) | Disabled |
86+
| HTTPS (TLS) | HTTPS Secure connection configuration. Refer to [TLS (HTTPS)](https://github.com/http-server-rs/http-server#tls-https) reference | Disabled |
87+
| CORS | Cross-Origin-Resource-Sharing headers support. Refer to [CORS](https://github.com/http-server-rs/http-server#cross-origin-resource-sharing-cors) reference | Disabled |
88+
| Compression | GZip compression for HTTP Response Bodies. Refer to [Compression](https://github.com/http-server-rs/http-server#compression) reference | Disabled |
89+
| Quiet | Don't print server details when running. This doesn't include any logging capabilities. | Disabled |
90+
| Index | Route directories to index.html if present | Disabled |
91+
| SPA | Route non-existent files to /index.html | Disabled |
92+
| Basic Authentication | Authorize requests using Basic Authentication. Refer to [Basic Authentication](https://github.com/http-server-rs/http-server#basic-authentication) | Disabled |
93+
| Logger | Prints HTTP request and response details to stdout | Disabled |
9094

9195
## Usage
9296

@@ -102,15 +106,17 @@ Flags are provided without any values. For example:
102106
http-server --help
103107
```
104108

105-
Name | Short | Long | Description
106-
--- | --- | --- | ---
107-
Cross-Origin Resource Sharing | N/A | `--cors` | Enable Cross-Origin Resource Sharing allowing any origin
108-
GZip Compression | N/A | `--gzip` | Enable GZip compression for responses
109-
Graceful Shutdown | N/A | `--graceful-shutdown` | Wait for all requests to be fulfilled before shutting down the server
110-
Help | N/A | `--help` | Print help information
111-
Logger | `-l` | `--logger` | Print HTTP request and response details to stdout
112-
Version | `-V` | `--version` | Print version information
113-
Quiet | `-q` | `--quiet` | Don't print output to console
109+
| Name | Short | Long | Description |
110+
| ----------------------------- | ----- | --------------------- | --------------------------------------------------------------------- |
111+
| Cross-Origin Resource Sharing | N/A | `--cors` | Enable Cross-Origin Resource Sharing allowing any origin |
112+
| GZip Compression | N/A | `--gzip` | Enable GZip compression for responses |
113+
| Graceful Shutdown | N/A | `--graceful-shutdown` | Wait for all requests to be fulfilled before shutting down the server |
114+
| Help | N/A | `--help` | Print help information |
115+
| Logger | `-l` | `--logger` | Print HTTP request and response details to stdout |
116+
| Version | `-V` | `--version` | Print version information |
117+
| Quiet | `-q` | `--quiet` | Don't print output to console |
118+
| Index | `-i` | `--index` | Route directories to index.html if present |
119+
| SPA | N/A | `--spa` | Route non-existent files to /index.html |
114120

115121
### Options
116122

@@ -120,18 +126,18 @@ Options receive a value and support default values as well.
120126
http-server --host 127.0.0.1
121127
```
122128

123-
Name | Short | Long | Description | Default Value
124-
--- | --- | --- | --- | ---
125-
Host | `-h` | `--host` | Address to bind the server | `127.0.0.1`
126-
Port | `-p` | `--port` | Port to bind the server | `7878`
127-
Configuration File | `-c` | `--config` | Configuration file. [Example](https://github.com/http-server-rs/http-server/blob/main/fixtures/config.toml) | N/A
128-
TLS | N/A | `--tls` | Enable TLS for HTTPS connections. Requires a Certificate and Key. [Reference](#tls-reference) | N/A
129-
TLS Ceritificate | N/A | `--tls-cert` | Path to TLS certificate file. **Depends on `--tls`** | `cert.pem`
130-
TLS Key | N/A | `--tls-key` | Path to TLS key file. **Depends on `--tls`** | `key.rsa`
131-
TLS Key Algorithm | N/A | `--tls-key-algorithm` | Algorithm used to generate certificate key. **Depends on `--tls`** | `rsa`
132-
Username | N/A | `--username` | Username to validate using basic authentication | N/A
133-
Password | N/A | `--password` | Password to validate using basic authentication. **Depends on `--username`** | N/A
134-
Proxy | N/A | `--proxy` | Proxy requests to the provided URL | N/A
129+
| Name | Short | Long | Description | Default Value |
130+
| ------------------ | ----- | --------------------- | ----------------------------------------------------------------------------------------------------------- | ------------- |
131+
| Host | `-h` | `--host` | Address to bind the server | `127.0.0.1` |
132+
| Port | `-p` | `--port` | Port to bind the server | `7878` |
133+
| Configuration File | `-c` | `--config` | Configuration file. [Example](https://github.com/http-server-rs/http-server/blob/main/fixtures/config.toml) | N/A |
134+
| TLS | N/A | `--tls` | Enable TLS for HTTPS connections. Requires a Certificate and Key. [Reference](#tls-reference) | N/A |
135+
| TLS Certificate | N/A | `--tls-cert` | Path to TLS certificate file. **Depends on `--tls`** | `cert.pem` |
136+
| TLS Key | N/A | `--tls-key` | Path to TLS key file. **Depends on `--tls`** | `key.rsa` |
137+
| TLS Key Algorithm | N/A | `--tls-key-algorithm` | Algorithm used to generate certificate key. **Depends on `--tls`** | `rsa` |
138+
| Username | N/A | `--username` | Username to validate using basic authentication | N/A |
139+
| Password | N/A | `--password` | Password to validate using basic authentication. **Depends on `--username`** | N/A |
140+
| Proxy | N/A | `--proxy` | Proxy requests to the provided URL | N/A |
135141

136142
## Request Handlers
137143

@@ -285,7 +291,7 @@ open an issue to be assigned and track the progress there.
285291

286292
- [x] Logging
287293
- [x] Request/Response Logging
288-
- [x] Service Config Loggins
294+
- [x] Service Config Logins
289295
- [ ] File Explorer
290296
- [x] Modified Date
291297
- [x] File Size

fixtures/config.toml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@ port = 7878
77
# quiet = false
88
# root_dir = "./"
99
# graceful_shutdown = false
10+
# index = false
11+
# spa = false
1012

1113
# [tls]
1214
# cert = "cert.pem"

src/addon/file_server/mod.rs

Lines changed: 71 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,9 @@ mod scoped_file_system;
66

77
use chrono::{DateTime, Local};
88

9+
pub use file::File;
910
use humansize::{format_size, DECIMAL};
11+
1012
pub use scoped_file_system::{Entry, ScopedFileSystem};
1113

1214
use anyhow::{Context, Result};
@@ -20,6 +22,7 @@ use std::path::{Component, Path, PathBuf};
2022
use std::str::FromStr;
2123
use std::sync::Arc;
2224

25+
use crate::config::Config;
2326
use crate::utils::url_encode::{decode_uri, encode_uri, PERCENT_ENCODE_SET};
2427

2528
use self::directory_entry::{BreadcrumbItem, DirectoryEntry, DirectoryIndex, Sort};
@@ -30,21 +33,21 @@ use self::query_params::{QueryParams, SortBy};
3033
const EXPLORER_TEMPLATE: &str = "explorer";
3134

3235
pub struct FileServer {
33-
root_dir: PathBuf,
3436
handlebars: Arc<Handlebars<'static>>,
3537
scoped_file_system: ScopedFileSystem,
38+
config: Arc<Config>,
3639
}
3740

3841
impl<'a> FileServer {
3942
/// Creates a new instance of the `FileExplorer` with the provided `root_dir`
40-
pub fn new(root_dir: PathBuf) -> Self {
43+
pub fn new(config: Arc<Config>) -> Self {
4144
let handlebars = FileServer::make_handlebars_engine();
42-
let scoped_file_system = ScopedFileSystem::new(root_dir.clone()).unwrap();
45+
let scoped_file_system = ScopedFileSystem::new(config.root_dir().clone()).unwrap();
4346

4447
FileServer {
45-
root_dir,
4648
handlebars,
4749
scoped_file_system,
50+
config,
4851
}
4952
}
5053

@@ -128,33 +131,80 @@ impl<'a> FileServer {
128131
/// If the matched path resolves to a file, attempts to render it if the
129132
/// MIME type is supported, otherwise returns the binary (downloadable file)
130133
pub async fn resolve(&self, req_path: String) -> Result<Response<Body>> {
131-
use std::io::ErrorKind;
132-
133134
let (path, query_params) = FileServer::parse_path(req_path.as_str())?;
134135

135136
match self.scoped_file_system.resolve(path).await {
136137
Ok(entry) => match entry {
137138
Entry::Directory(dir) => {
139+
if self.config.index() {
140+
let mut filepath = dir.path();
141+
142+
filepath.push("index.html");
143+
if let Ok(file) = tokio::fs::File::open(&filepath).await {
144+
return make_http_file_response(
145+
File {
146+
path: filepath,
147+
metadata: file.metadata().await?,
148+
file,
149+
},
150+
CacheControlDirective::MaxAge(2500),
151+
)
152+
.await;
153+
}
154+
}
155+
138156
self.render_directory_index(dir.path(), query_params).await
139157
}
140158
Entry::File(file) => {
141159
make_http_file_response(*file, CacheControlDirective::MaxAge(2500)).await
142160
}
143161
},
144-
Err(err) => match err.kind() {
145-
ErrorKind::NotFound => Ok(HttpResponseBuilder::new()
146-
.status(StatusCode::NOT_FOUND)
147-
.body(Body::from(err.to_string()))
148-
.expect("Failed to build response")),
149-
ErrorKind::PermissionDenied => Ok(HttpResponseBuilder::new()
150-
.status(StatusCode::FORBIDDEN)
151-
.body(Body::from(err.to_string()))
152-
.expect("Failed to build response")),
153-
_ => Ok(HttpResponseBuilder::new()
154-
.status(StatusCode::BAD_REQUEST)
155-
.body(Body::from(err.to_string()))
156-
.expect("Failed to build response")),
157-
},
162+
Err(err) => {
163+
if self.config.spa() {
164+
return make_http_file_response(
165+
{
166+
let mut path = self.config.root_dir();
167+
path.push("index.html");
168+
169+
let file = tokio::fs::File::open(&path).await?;
170+
171+
let metadata = file.metadata().await?;
172+
173+
File {
174+
path,
175+
metadata,
176+
file,
177+
}
178+
},
179+
CacheControlDirective::MaxAge(2500),
180+
)
181+
.await;
182+
}
183+
184+
let status = match err.kind() {
185+
std::io::ErrorKind::NotFound => hyper::StatusCode::NOT_FOUND,
186+
std::io::ErrorKind::PermissionDenied => hyper::StatusCode::FORBIDDEN,
187+
_ => hyper::StatusCode::BAD_REQUEST,
188+
};
189+
190+
let code = match err.kind() {
191+
std::io::ErrorKind::NotFound => "404",
192+
std::io::ErrorKind::PermissionDenied => "403",
193+
_ => "400",
194+
};
195+
196+
let response = hyper::Response::builder()
197+
.status(status)
198+
.header(http::header::CONTENT_TYPE, "text/html")
199+
.body(hyper::Body::from(
200+
handlebars::Handlebars::new().render_template(
201+
include_str!("./template/error.hbs"),
202+
&serde_json::json!({"error": err.to_string(), "code": code}),
203+
)?,
204+
))?;
205+
206+
Ok(response)
207+
}
158208
}
159209
}
160210

@@ -167,7 +217,7 @@ impl<'a> FileServer {
167217
query_params: Option<QueryParams>,
168218
) -> Result<Response<Body>> {
169219
let directory_index =
170-
FileServer::index_directory(self.root_dir.clone(), path, query_params)?;
220+
FileServer::index_directory(self.config.root_dir().clone(), path, query_params)?;
171221
let html = self
172222
.handlebars
173223
.render(EXPLORER_TEMPLATE, &directory_index)
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
<center>
2+
<h1>{{code}}</h1>
3+
<p>{{error}}</p>
4+
</center>

0 commit comments

Comments
 (0)