operator 0.0.2

A web server for static and dynamic content
Documentation

Operator

Operator is an experimental web server for static and dynamic content. You give it a directory and it makes a website.

It serves static files the way you'd expect, but it can also serve dynamic content that is generated by handlebars templates or executables at request time.

⚠️ Operator is extremely alpha! Please don't use it for anything important until there's a 1.0 release.

Installation

Operator is a single self-contained binary. At the moment, the best way to get a binary for your platform is to build one yourself. First install Rust, then:

git clone https://github.com/mkantor/operator.git
cd operator
cargo build --release # This will take a while.
echo '{{#if true}}Hello, Operator!{{/if}}' \
  | ./target/release/operator eval --content-directory=/dev/null

Usage

The CLI has three subcommands:

  1. eval evaluates a handlebars template from STDIN.
  2. get renders content from a content directory.
  3. serve starts an HTTP server.

serve is where the real action is, but the other two come in handy at times.

These commands all require a "content directory", which is just the folder where your website lives. There are a bunch of example content directories in samples/.

To learn more, run operator --help or operator <SUBCOMMAND> --help.

Example

Let's start a server for one of the samples:

operator -vv serve \
  --content-directory=samples/realistic-advanced \
  --index-route=home \
  --error-handler-route=error-handler \
  --bind-to=127.0.0.1:8080

Open http://localhost:8080 in your browser of choice to see the website!

Content

When Operator starts up, it crawls through your content directory to build a representation of your website. The site's routes and configuration are derived from this directory.

There are three different kinds of content files:

  1. Static files are served directly. For example, you can drop a photo into your content directory and it'll be served as-is.
  2. Executables are executed at request time, with standard output piped out as the response body. Any program that your operating system can run will work (think scripting languages, compiled binaries, etc). The executable is invoked with no CLI arguments, no special environment, and with its working directory set to its own parent folder. Operator requires execute permissions on these files, and scripts typically need a shebang so your operating system knows how to interpret them.
  3. Handlebars templates are compiled during server startup and evaluated at request time. The heavy lifting is done by the handlebars Rust library which is largely compatible with the original JavaScript implementation. Operator provides some render data and a custom get helper to make your content composable.

Operator needs to know what media type will be emitted by each content file. This is specified via file extensions. The rules are pretty simple:

  • The first extension always indicates the media type of the file's output (.html is text/html, .js is text/javascript, .png is image/png, and so on).
  • Static files only need one extension (foo.html, bar.mp4, etc).
  • Executables have two extensions (baz.html.py, quux.css.sh, garply.jpg.exe, etc). Operator actually does not care what the second extension is, but you can use it to indicate the file type (so baz.html.py would be a Python script that outputs HTML—for executables the file type is usually not the same as its output type, although if you're feeling feisty then things like wat.js.js are certainly possible). As mentioned previously, make sure you set the executable bit on these files.
  • Handlebars templates also have two extensions, but the second one must be .hbs (plugh.html.hbs, xyzzy.json.hbs, etc).

Hidden files and directories (whose name begins with .) are always completely ignored by Operator.

Disclaimer

Operator is very young and has not been battle-hardened. There are known flaws and obvious missing features that need to be addressed. The major ones are filed as issues. All feedback is greatly appreciated.

This is my first nontrivial Rust project and I'm sure there are places where things are unidiomatic or non-optimal. The main reason I created Operator was to get more experience using the language, so if you notice anything that could be improved (no matter how small), please open an issue to help me learn! ❤️


An old-timey switchboard operator