From 7c30dd6c6430f2682dea6ee7fbf9a76a80c69c62 Mon Sep 17 00:00:00 2001 From: Mufeed VH Date: Mon, 13 Jun 2022 19:59:02 +0530 Subject: [PATCH] v0.2.0 Release --- .cargo/config | 6 - .github/workflows/{CICD.yml => build.yml} | 4 +- .github/workflows/linux-rust.yml | 22 -- .github/workflows/macos-rust.yml | 22 -- .github/workflows/windows-rust.yml | 22 -- .gitignore | 21 +- BENCHMARKS.md | 113 +++++++ Cargo.toml | 46 ++- README.md | 369 ++++++++++++++++------ assets/benchmarks.jpeg | Bin 0 -> 59449 bytes assets/binserve-android.gif | Bin 0 -> 3863665 bytes assets/binserve-logo.png | Bin 0 -> 179603 bytes binserve.json | 65 ++-- src/cli/banner | 4 + src/cli/interface.rs | 45 +++ src/cli/messages.rs | 24 ++ src/cli/mod.rs | 2 + src/config.rs | 83 ----- src/core/config.json | 47 +++ src/core/config.rs | 148 +++++++++ src/core/engine.rs | 72 +++++ src/core/files.rs | 226 +++++++++++++ src/core/mod.rs | 10 + src/core/routes.rs | 163 ++++++++++ src/core/server.rs | 294 +++++++++++++++++ src/core/templates.rs | 25 ++ src/core/tls.rs | 58 ++++ src/core/watcher.rs | 105 ++++++ src/error_pages.rs | 33 -- src/main.rs | 93 +----- src/security.rs | 114 ------- src/serve.rs | 40 --- src/setup_static.rs | 65 ---- src/starter/404.html | 22 ++ src/starter/assets/css/styles.css | 61 ++++ src/starter/assets/images/binserve.webp | Bin 0 -> 22282 bytes src/starter/header.hbs | 7 + src/starter/index.html | 34 ++ src/starter/usage.hbs | 35 ++ src/template.rs | 67 ---- static/404.html | 8 - static/index.html | 9 - 42 files changed, 1861 insertions(+), 723 deletions(-) delete mode 100644 .cargo/config rename .github/workflows/{CICD.yml => build.yml} (99%) delete mode 100644 .github/workflows/linux-rust.yml delete mode 100644 .github/workflows/macos-rust.yml delete mode 100644 .github/workflows/windows-rust.yml create mode 100644 BENCHMARKS.md create mode 100644 assets/benchmarks.jpeg create mode 100644 assets/binserve-android.gif create mode 100644 assets/binserve-logo.png create mode 100644 src/cli/banner create mode 100644 src/cli/interface.rs create mode 100644 src/cli/messages.rs create mode 100644 src/cli/mod.rs delete mode 100644 src/config.rs create mode 100644 src/core/config.json create mode 100644 src/core/config.rs create mode 100644 src/core/engine.rs create mode 100644 src/core/files.rs create mode 100644 src/core/mod.rs create mode 100644 src/core/routes.rs create mode 100644 src/core/server.rs create mode 100644 src/core/templates.rs create mode 100644 src/core/tls.rs create mode 100644 src/core/watcher.rs delete mode 100644 src/error_pages.rs delete mode 100644 src/security.rs delete mode 100644 src/serve.rs delete mode 100644 src/setup_static.rs create mode 100755 src/starter/404.html create mode 100755 src/starter/assets/css/styles.css create mode 100644 src/starter/assets/images/binserve.webp create mode 100644 src/starter/header.hbs create mode 100755 src/starter/index.html create mode 100644 src/starter/usage.hbs delete mode 100644 src/template.rs delete mode 100644 static/404.html delete mode 100644 static/index.html diff --git a/.cargo/config b/.cargo/config deleted file mode 100644 index 8d0f7d6..0000000 --- a/.cargo/config +++ /dev/null @@ -1,6 +0,0 @@ -[target.x86_64-pc-windows-gnu] -linker = "/usr/bin/x86_64-w64-mingw32-gcc" - -[target.i686-pc-windows-gnu] -linker = "/usr/bin/i686-w64-mingw32-gcc" -rustflags = "-C panic=abort" diff --git a/.github/workflows/CICD.yml b/.github/workflows/build.yml similarity index 99% rename from .github/workflows/CICD.yml rename to .github/workflows/build.yml index 0098788..2d9d215 100644 --- a/.github/workflows/CICD.yml +++ b/.github/workflows/build.yml @@ -9,7 +9,7 @@ name: CICD env: PROJECT_NAME: binserve - PROJECT_DESC: "A blazingly fast static web server in a single binary you can set up with zero code. ⚡ðŸĶ€" + PROJECT_DESC: "A fast static web server with TLS (HTTPS), Routing, Hot Reloading, Caching, Templating, and Security in a single-binary you can set up with zero code." PROJECT_AUTH: "mufeedvh" RUST_MIN_SRV: "1.42.0" ## minimum supported rust version (aka, MinSRV or MSRV) RUST_COV_SRV: "2020-08-01" ## (~v1.47.0) supported rust version for code coverage; (date required/used by 'coverage') @@ -445,4 +445,4 @@ jobs: ## flags: IntegrationTests, UnitTests, ${{ steps.vars.outputs.CODECOV_FLAGS }} flags: ${{ steps.vars.outputs.CODECOV_FLAGS }} name: codecov-umbrella - fail_ci_if_error: false + fail_ci_if_error: false \ No newline at end of file diff --git a/.github/workflows/linux-rust.yml b/.github/workflows/linux-rust.yml deleted file mode 100644 index 74f48c7..0000000 --- a/.github/workflows/linux-rust.yml +++ /dev/null @@ -1,22 +0,0 @@ -name: Linux Ubuntu Rust Build - -on: - push: - branches: [ master ] - pull_request: - branches: [ master ] - -env: - CARGO_TERM_COLOR: always - -jobs: - build: - - runs-on: ubuntu-latest - - steps: - - uses: actions/checkout@v2 - - name: Build - run: cargo build --verbose - - name: Run tests - run: cargo test --verbose diff --git a/.github/workflows/macos-rust.yml b/.github/workflows/macos-rust.yml deleted file mode 100644 index d5dafb1..0000000 --- a/.github/workflows/macos-rust.yml +++ /dev/null @@ -1,22 +0,0 @@ -name: MacOS Rust Build - -on: - push: - branches: [ master ] - pull_request: - branches: [ master ] - -env: - CARGO_TERM_COLOR: always - -jobs: - build: - - runs-on: macos-latest - - steps: - - uses: actions/checkout@v2 - - name: Build - run: cargo build --verbose - - name: Run tests - run: cargo test --verbose diff --git a/.github/workflows/windows-rust.yml b/.github/workflows/windows-rust.yml deleted file mode 100644 index f436aa1..0000000 --- a/.github/workflows/windows-rust.yml +++ /dev/null @@ -1,22 +0,0 @@ -name: Windows Rust Build - -on: - push: - branches: [ master ] - pull_request: - branches: [ master ] - -env: - CARGO_TERM_COLOR: always - -jobs: - build: - - runs-on: windows-latest - - steps: - - uses: actions/checkout@v2 - - name: Build - run: cargo build --verbose - - name: Run tests - run: cargo test --verbose diff --git a/.gitignore b/.gitignore index 369a495..b53d886 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,7 @@ # Generated by Cargo # will have compiled files and executables -/target/ +debug/ +target/ # Remove Cargo.lock from gitignore if creating an executable, leave it for libraries # More information here https://doc.rust-lang.org/cargo/guide/cargo-toml-vs-cargo-lock.html @@ -9,11 +10,19 @@ Cargo.lock # These are backup files generated by rustfmt **/*.rs.bk +# MSVC Windows builds of rustc generate these, which store debugging information +*.pdb +# Generated by Cargo +# will have compiled files and executables +debug/ +target/ -# Added by cargo +# Remove Cargo.lock from gitignore if creating an executable, leave it for libraries +# More information here https://doc.rust-lang.org/cargo/guide/cargo-toml-vs-cargo-lock.html +Cargo.lock -/target +# These are backup files generated by rustfmt +**/*.rs.bk -/rendered_templates -binserve.json -release.py \ No newline at end of file +# MSVC Windows builds of rustc generate these, which store debugging information +*.pdb \ No newline at end of file diff --git a/BENCHMARKS.md b/BENCHMARKS.md new file mode 100644 index 0000000..c10d5f2 --- /dev/null +++ b/BENCHMARKS.md @@ -0,0 +1,113 @@ +# Benchmarks + +Benchmarks are performed with [wrk](https://github.com/wg/wrk). All of the servers tested serve the same static page, benchmark is ran 3 times and the best one of them is chosen. + +> **FUN FACT:** Microbenchmarks like this are not a good measure for "dynamic web apps" which most websites are, in that context, your bottleneck is going to be disk I/O and database queries and a metric like requests/sec doesn't measure anything meaningful. Here, there is a very specific goal -- to serve static files and the bottlenecks can be minimized and there are no external constraints hence why this benchmark exists. :) + +## Tested On: + +> Linux Kernel Version: 5.17.5-76051705-generic +> +> CPU Description: Intel(R) Core(TM) i7-8550U CPU @ 1.80GHz +> +> CPU ID: GenuineIntel,6,142,10 +> +> CPU Architecture: x86_64 +> +> CPUs Available: 8 +> +> Total Memory: 25.1 GB + +## Results + +
+ + +
+
+ +## Binserve + +``` +$ wrk -c 500 -t 12 -d 5s http://127.0.0.1:1337/ +Running 5s test @ http://127.0.0.1:1337/ + 12 threads and 500 connections + Thread Stats Avg Stdev Max +/- Stdev + Latency 2.41ms 372.48us 32.88ms 92.11% + Req/Sec 17.07k 2.14k 38.17k 96.87% + 1030467 requests in 5.10s, 239.79MB read +Requests/sec: 202074.22 +Transfer/sec: 47.02MB +``` + +## NGINX Tuned: + +**Source:** https://github.com/denji/nginx-tuning + +``` +$ wrk -c 500 -t 12 -d 5s http://127.0.0.1:8081/ +Running 5s test @ http://127.0.0.1:8081/ + 12 threads and 500 connections + Thread Stats Avg Stdev Max +/- Stdev + Latency 2.40ms 1.87ms 34.64ms 71.69% + Req/Sec 17.07k 5.49k 38.06k 64.67% + 1023120 requests in 5.08s, 250.76MB read +Requests/sec: 201407.17 +Transfer/sec: 49.36MB +``` + +## NGINX Default: + +``` +$ wrk -c 500 -t 12 -d 5s http://127.0.0.1:8081/ +Running 5s test @ http://127.0.0.1:8081/ + 12 threads and 500 connections + Thread Stats Avg Stdev Max +/- Stdev + Latency 4.24ms 2.55ms 34.47ms 78.72% + Req/Sec 9.85k 1.98k 34.39k 92.87% + 593245 requests in 5.10s, 145.37MB read +Requests/sec: 116415.16 +Transfer/sec: 28.53MB +``` + +## Lighttpd: + +``` +$ wrk -c 500 -t 12 -d 5 http://127.0.0.1:7822/ +Running 5s test @ http://127.0.0.1:7822/ + 12 threads and 500 connections + Thread Stats Avg Stdev Max +/- Stdev + Latency 49.76ms 102.76ms 475.80ms 86.45% + Req/Sec 6.45k 3.15k 42.89k 77.46% + 384892 requests in 5.07s, 83.39MB read +Requests/sec: 75915.69 +Transfer/sec: 16.45MB +``` + +## Caddy: + +``` +$ wrk -c 500 -t 12 -d 5s http://localhost:2015/ +Running 5s test @ http://localhost:2015/ + 12 threads and 500 connections + Thread Stats Avg Stdev Max +/- Stdev + Latency 13.56ms 18.86ms 188.27ms 87.31% + Req/Sec 5.54k 1.25k 13.05k 74.34% + 335088 requests in 5.10s, 73.18MB read +Requests/sec: 65706.52 +Transfer/sec: 14.35MB +``` + +## Apache2: + +``` +$ wrk -c 500 -t 12 -d 5 http://127.0.0.1:80/ +Running 5s test @ http://127.0.0.1:80/ + 12 threads and 500 connections + Thread Stats Avg Stdev Max +/- Stdev + Latency 178.92ms 244.04ms 804.40ms 79.40% + Req/Sec 11.89k 11.40k 36.85k 67.58% + 314836 requests in 5.07s, 71.82MB read +Requests/sec: 62124.60 +Transfer/sec: 14.17MB +``` diff --git a/Cargo.toml b/Cargo.toml index c0dc3d1..3965fc1 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,20 +1,38 @@ [package] name = "binserve" -version = "0.1.0" -authors = ["mufeedvh "] -edition = "2018" +version = "0.2.0" +edition = "2021" [dependencies] -futures = "0.3.5" -env_logger = "0.7.1" - -actix-web = "3" -actix-files = "0.4.0" - -serde = { version = "1.0.116", features = ["derive"] } -serde_json = "1.0.58" - -handlebars = "3.5.0" +actix-files = "0.6.0" +actix-web = { version = "4.0.1", features = ["rustls"] } +actix-web-lab = "0.16.1" +ahash = "0.7.6" +anyhow = "1.0.57" +clap = "3.1.18" +colored = "2.0.0" +compact_str = "0.4.0" +dashmap = "5.3.4" +env_logger = "0.9.0" +etag = { version = "3.0.0", features = ["std"] } +handlebars = "4.3.1" +jwalk = "0.6.0" +minify-html-onepass = "0.8.0" +new_mime_guess = "4.0.1" +notify = "4.0.17" +num_cpus = "1.13.1" +once_cell = { version = "1.12.0", features = ["parking_lot"] } +parking_lot = "0.12.1" +rustls = "0.20.6" +rustls-pemfile = "1.0.0" +serde = { version = "1.0.137", features = ["derive"] } +serde_json = "1.0.81" [profile.release] -lto = true \ No newline at end of file +opt-level = 3 +codegen-units = 1 +panic = 'abort' +lto = "thin" +debug = false +incremental = false +overflow-checks = false diff --git a/README.md b/README.md index 108f22e..03f4d15 100644 --- a/README.md +++ b/README.md @@ -1,169 +1,328 @@ -# `binserve` :zap::crab: +binserve logo -A fast static web server with **routing**, **templating**, and **security** in a single binary you can set up with zero code. :fire: +# `binserve` :rocket::crab: + +A fast static web server with **TLS** (HTTPS), **Routing**, **Hot Reloading**, **Caching**, **Templating**, and **Security** in a single-binary you can set up with zero code. + +Built from the ground up for self-hosters with [performance](#benchmarks), [ease of use](#configuration), and [portability](#portability) in mind. âĪïļ

- version - GitHub license - Twitter + version + gitHub license + twitter share

-> **Update:** [v0.2.0 Draft](https://github.com/mufeedvh/binserve/issues/29) - ## Table of Contents * [Features](#features) -* [Quick Start](#hello-world) -* [Configuration](#%EF%B8%8F-configuration-file) -* [Templates](#-templates) +* [Hello World!](#hello-world) +* [Installation](#installation) * [Build From Source](#build-from-source) +* [Configuration](#configuration) +* [Templates](#templating) +* [Benchmarks](#benchmarks) +* [Contribution](#contribution) * [License](#license) +* [Credits](#credits) ----- +**Example:** [Hosting a website produced by a Static Site Generators like Hugo, Zola, Jekyll, Hexo, etc.](#static-site-generators) -## Features +# Features -- **Single binary:** Single binary with no dependencies and everything built-in. -- **Fast** - It's built on top of [**Actix**](https://actix.rs/), one of the [**fastest web frameworks**](https://www.techempower.com/benchmarks/) out there. -- **Everything in a single config file** - Everything you need to setup is in the `binserve.json`, just change it, run it! -- **[Handlebars](https://github.com/sunng87/handlebars-rust) template engine** - Support for templating with Handlebars. +- **Fast**: Binserve is [designed](#caching) to be performant, this is thanks to [**Actix-Web**](https://github.com/actix/actix-web) - one of the [fastest](https://www.techempower.com/benchmarks/) web frameworks out there and [**DashMap**](https://github.com/xacrimon/dashmap) for handling routes and the cache storage. (See [**Benchmarks**](#benchmarks)) +- **Portability:** Binserve is cross-platform and portable to any major operating system, like it can run on your [Android](#portability) phone! +- **Routing:** Routing is simply matching a URI path to a file or a directory in a JSON file. (See [**Configuration**](#configuration)) +- **Templating:** You can write templates and partials using [Handlebars](https://handlebarsjs.com/guide/). (See [**Templating**](#templating)) +- **Hot Reload:** You can reload your configuration (routes) and static files with no downtime. +- **Caching:** Binserve's performance is achieved due to minimization of Disk I/O operations at runtime (with `fast_mem_cache` enabled) and serving static files from memory. On the client-side, `Cache-Control`, `Etag`, and `Last-Modified` are utilized. +- **Security:** Prevents common attack vectors like [Directory Traversal](https://en.wikipedia.org/wiki/Directory_traversal_attack) and [Symlink Attacks](https://capec.mitre.org/data/definitions/132.html). + +> 👋 **Enterprise?** If you're deplyoing to production or expecting high traffic to your server, get [binserve+](https://mufeedvh.gumroad.com/l/binserveplus) which has **DDoS Protection**, **Rate Limiting**, and **Prometheus Metrics** for monitoring along with all the above features built-in. +> +> Checkout Binserve Plus! +> +> Read [**FAQ**](#faq) for more details. ## Hello World! -Download the binary for your OS from [**Releases**](https://github.com/mufeedvh/binserve/releases), then just run it: +Download the executable for your OS from [**Releases**](https://github.com/mufeedvh/binserve/releases), then just run it: - $ binserve +``` +$ mkdir mywebsite/ +$ binserve +``` -That's it. Done! You should see the following output: +On the first run, it will create the configuration file and a starter boilerplate for you to get started. -``` - _ _ - | |_|_|___ ___ ___ ___ _ _ ___ - | . | | |_ -| -_| _| | | -_| - |___|_|_|_|___|___|_| \_/|___| v0.1.0 - +``` + _ _ +| |_|_|___ ___ ___ ___ _ _ ___ +| . | | |_ -| -_| _| | | -_| +|___|_|_|_|___|___|_| \_/|___| 0.2.0 -Your server is up and running at http://example.com:80/ +[INFO] Build finished in 295 Ξs ⚡ +[SUCCESS] Your server is up and running at 127.0.0.1:1337 🚀 ``` -Here is how the directory structure will look like: +Go to http://127.0.0.0:1337/ and you will be greeted with the index page of Binserve. + +Now all you need to do is to edit the `binserve.json` file. (See [**Configuration**](#configuration)). + +## Installation + +Download the executable from [**Releases**](https://github.com/mufeedvh/binserve/releases) OR Install with `cargo`: + + $ cargo install --git https://github.com/mufeedvh/binserve.git + +[Install Rust/Cargo](https://rust-lang.org/tools/install) + +### Build From Source + +**Prerequisites:** + +* [Git](https://git-scm.org/downloads) +* [Rust](https://rust-lang.org/tools/install) +* Cargo (Automatically installed when installing Rust) +* A C linker (Only for Linux, generally comes pre-installed) ``` -├── binserve -├── binserve.json -├── rendered_templates -│   ├── 404.html -│   └── index.html -└── static - ├── 404.html - ├── assets - │   ├── css - │   ├── images - │   └── js - └── index.html +$ git clone https://github.com/mufeedvh/binserve.git +$ cd binserve/ +$ RUSTFLAGS="-C target-cpu=native" cargo build --release ``` -### ⚙ïļ Configuration File: +The first command clones this repository into your local machine and the last two commands enters the directory and builds the source in release mode. -📄 **File:** `binserve.json` +## Configuration + +The configuration file is a JSON file called `binserve.json` that's generated automatically by the executable. Configuring binserve is pretty straight-forward because the configuration fields are self-explanatory: + +And all of the values here have secure defaults so you don't have to specify the ones you don't need. + +ðŸ’Ą **TIP**: Most probably you wouldn't be needing all of the configuration fields, checkout the [Static Site Generator example](#static-site-generators) on how to serve a single directory. ```json { - "directory_listing": false, - "enable_logging": true, - "error_pages": { - "404": "404.html" - }, - "follow_symlinks": false, - "routes": { - "/": "index.html", - "/example": "example.html" - }, - "server": { - "host": "127.0.0.1", - "port": 1337 - }, - "static_directory": "static", - "template_variables": { - "load_static": "/static/", - "name": "Binserve" - } + "server": { + "host": "127.0.0.1:1337", + "tls": { + "host": "127.0.0.1:443", + "enable": false, + "key": "key.pem", + "cert": "cert.pem" + } + }, + + "routes": { + "/": "public/index.html", + "/usage": "public/usage.hbs", + "/blog": "public/blog/" + }, + + "static": { + "directory": "public/assets", + "served_from": "/assets", + "error_pages": { + "404": "public/404.html" + } + }, + + "template": { + "partials": { + "header": "public/header.hbs" + }, + "variables": { + "app_name": "Binserve" + } + }, + + "config": { + "enable_hot_reload": true, + "fast_mem_cache": true, + "enable_cache_control": true, + "enable_directory_listing": true, + "minify_html": false, + "follow_symlinks": false, + "enable_logging": false + }, + + "insert_headers": { + "x-greetings": "hellooo!" + } +} +``` + +You can override the configuration with command-line arguments as well: + + + +## Templating + +Binserve uses [Handlebars](https://github.com/sunng87/handlebars-rust) for templating as it's simple and the most commonly known templating engine. + +You can register partial templates and template variables like this in the configuration file: + +```json +"template": { + "partials": { + "header": "public/header.hbs" + }, + "variables": { + "app_name": "Binserve" + } } ``` -The whole thing revolves around this configuration file, whatever changes you want to make, just edit the config and run it! +**public/header.hbs**: -### ðŸŽĻ Templates: +```hbs + + + + + {{ app_name }} v0.2.0 Usage + + +``` + +And use it like this: -`binserve` uses [Handlebars](https://github.com/sunng87/handlebars-rust) as the template engine as it perfectly fits our use case. +```hbs + + {{> header}} + Hello World! + +``` -**Here is an example:** +Which would render down to: -```html +```hbs - Example + + + + Binserve v0.2.0 Usage + - -

My name is {{name}}

- + Hello World! ``` -Now add your name in the config file (`binserve.json`) as a `template variable`: +## Static Site Generators + +Every static generator builds your Markdown/Template files into a directory, usually named `public/`, all you have to do is point that directory as the index route: ```json -"template_variables": { - "load_static": "/static/", - "name": "Keanu Reeves" +{ + "server": { + "host": "127.0.0.1:1337", + "tls": { + "host": "127.0.0.1:443", + "enable": false, + "key": "key.pem", + "cert": "cert.pem" + } + }, + "routes": { + "/": "public/" + } } ``` -Now run the server! +That's it! - $ binserve +As mentioned previously, you don't have to specify all the fields, secure defaults will be applied! 🙌 -This would render down to: +Pointing directories as routes is an intentional design so that you can host multiple Static Site Generator outputs easily. Let's say you have a portfolio as the homepage and your blog is made with a different template. You can just do: -```html - - - Example - - -

My name is Keanu Reeves

- - +```json +"routes": { + "/": "my_zola_portfolio/public/", + "/blog": "my_hugo_blog/public/" +} ``` -To load static files such as `images`, `css`, and `javascript`, just use `{{load_static}}`: +## Portability -`load_static` is specified in the `binserve.json` itself. +Binserve is cross-platform which means you can run it on any major operating system / devices. It is low on CPU usage and memory footprint so you can run it on your **Raspberry Pi** or even your **Android** Phone: -```html - - -