Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -13,3 +13,9 @@ Cargo.lock
# MSVC Windows builds of rustc generate these, which store debugging information
*.pdb



# FILE SERVER

*.json
!*.example.json
2 changes: 1 addition & 1 deletion LICENSE
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
BSD 3-Clause License

Copyright (c) 2023, Wolfpup Software
Copyright (c) 2023, W-lfpup
All rights reserved.

Redistribution and use in source and binary forms, with or without
Expand Down
68 changes: 65 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,13 @@
An `http` file server written in rust using [tokio](https://tokio.rs/) and
[hyper](https://hyper.rs/).

Includes support for:
- http 1.1 / 2
- boxed responses (send large files frame by frame)
- `head` requests
- `range` requests
- encoded requests

## How to use

### Install
Expand All @@ -16,23 +23,78 @@ cargo install --path file_server/file_server

### Run

Run the following command:
Bash the following command:

```sh
file_server
```

This will start `file_server` with it's default configuration in the `cwd`.

Bash the following command to serve files in the `cwd` at `localhost:3000`:
Now files can be requested from the `cwd` at `localhost:3000`:

```sh
curl localhost:3000
```

### Configuration

Click [here](./configuration.md) to learn how to configure `file_server`.
A valid [JSON configuration file](./file_server.example.json) matches the following schema.

```JSON
{
"directory": "./demo",
"host_and_port": "127.0.0.1:4000",
"content_encodings": ["gzip", "deflate", "br", "zstd"],
"filepath_404": "./demo/404.html"
}
```

Filepaths can be relative or absolute. Relative paths are "relative from" the filepath of the JSON configuration.

The `content_encodings` and `filepath_404` properties are optional.

#### Run with configuration

Bash the following command to serve files based on a an example configuration:

```sh
file_server file_server.example.json
```

Open a browser and visit `http://localhost:4000`.

### Accept-Encoding

When an `accept-encoding` header is found in a request, `file_server` will return a corresponding `zip`-ed version of file if available.

So if a request has the following header:

```
Accept-Encoding: gzip;
```

And the source file has a correspponding gziped file:

```sh
./www/index.html # source file
./www/index.html.gz # gzipped file
```

Then `file_server` will send the encoded file, if available. Otherwise, it serves the source file.

### No dynamic encoding support

`File_server` does not encode or zip files ever.

This program serves static files. Just zip them up now to save memory resources.

### Range requests

`File_server` supports single range requests.

Multipart ranges are not currently supported because multipart ranges are a memory hog and difficult to
deliver efficiently without potentially maxing out ram resources.

## Licence

Expand Down
66 changes: 0 additions & 66 deletions configuration.md

This file was deleted.

File renamed without changes.
8 changes: 4 additions & 4 deletions file_server/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ async fn main() -> Result<(), String> {

println!("file_server: {}", conf.host_and_port);

let svc = service::Svc::new(conf.directory, conf.content_encodings, conf.filepath_404);
let svc = service::Svc::from(conf);

loop {
let (stream, _remote_address) = match listener.accept().await {
Expand All @@ -45,9 +45,9 @@ async fn main() -> Result<(), String> {

async fn get_config() -> Result<Config, String> {
match env::args().nth(1) {
Some(conf_path) => {
let conf_path_buf = PathBuf::from(conf_path);
return Config::try_from(&conf_path_buf).await;
Some(conf_path_arg) => {
let conf_pathbuf = PathBuf::from(conf_path_arg);
return Config::try_from(&conf_pathbuf).await;
}
_ => Config::new(),
}
Expand Down
30 changes: 11 additions & 19 deletions file_server/src/service.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,33 +2,29 @@ use hyper::body::Incoming as IncomingBody;
use hyper::service::Service;
use hyper::Request;
use std::future::Future;
use std::path::PathBuf;
use std::pin::Pin;

use config::Config;
/*
BoxedResponse is a type.
It should work with hyper responses across
different libraries and dependencies.
*/
use response::{build_response, BoxedResponse};
use response::{build_response, BoxedResponse, ResponseParams};

#[derive(Clone, Debug)]
pub struct Svc {
directory: PathBuf,
content_encodings: Option<Vec<String>>,
fallback_404: Option<PathBuf>,
response_params: ResponseParams,
}

impl Svc {
pub fn new(
directory: PathBuf,
content_encodings: Option<Vec<String>>,
fallback_404: Option<PathBuf>,
) -> Svc {
pub fn from(config: Config) -> Svc {
Svc {
directory: directory,
content_encodings: content_encodings,
fallback_404: fallback_404,
response_params: ResponseParams::from(
config.directory,
config.filepath_404,
config.content_encodings,
),
}
}
}
Expand All @@ -39,12 +35,8 @@ impl Service<Request<IncomingBody>> for Svc {
type Future = Pin<Box<dyn Future<Output = Result<Self::Response, Self::Error>> + Send>>;

fn call(&self, req: Request<IncomingBody>) -> Self::Future {
let directory = self.directory.clone();
let content_encodings = self.content_encodings.clone();
let fallback_404 = self.fallback_404.clone();
let response_params = self.response_params.clone();

Box::pin(
async move { build_response(req, directory, content_encodings, fallback_404).await },
)
Box::pin(async move { build_response(req, response_params).await })
}
}
34 changes: 15 additions & 19 deletions response/src/available_encodings.rs
Original file line number Diff line number Diff line change
@@ -1,39 +1,35 @@
#[derive(Clone, Debug)]
pub struct AvailableEncodings {
pub gzip: bool,
pub deflate: bool,
pub br: bool,
pub zstd: bool,
gzip: bool,
deflate: bool,
br: bool,
zstd: bool,
}

impl AvailableEncodings {
pub fn new(potential_encodings: &Option<Vec<String>>) -> AvailableEncodings {
pub fn from(potential_encodings: Option<Vec<String>>) -> AvailableEncodings {
let mut av_enc = AvailableEncodings {
gzip: false,
deflate: false,
br: false,
zstd: false,
};

if let Some(pe) = potential_encodings {
av_enc.update(pe);
if let Some(encodings) = potential_encodings {
for encoding in encodings {
match encoding.as_str() {
"gzip" => av_enc.gzip = true,
"deflate" => av_enc.deflate = true,
"br" => av_enc.br = true,
"zstd" => av_enc.zstd = true,
_ => {}
}
}
}

av_enc
}

pub fn update(&mut self, potential_encodings: &Vec<String>) {
for encoding in potential_encodings {
match encoding.as_str() {
"gzip" => self.gzip = true,
"deflate" => self.deflate = true,
"br" => self.br = true,
"zstd" => self.zstd = true,
_ => {}
}
}
}

pub fn encoding_is_available(&self, encoding: &str) -> bool {
match encoding {
"gzip" => self.gzip,
Expand Down
Loading