Home | Benchmarks | Categories | Atom Feed

Posted on Thu 16 September 2021 under Rust

Rocket: A Web Framework for Rust

This post is a part of a series exploring Rust-based Web Frameworks.

Rocket is a web framework written in Rust. It provides a concise API and is opinionated and feature-rich beyond what you would typically find in a micro-framework.

The project was started by Sergio Benitez in 2016. Sergio used to work at Google and Apple where he worked on Gmail and macOS respectively. In 2014, he helped reduce the system boot-up time of Space X's Rockets while working on their flight software. Sergio still actively maintains Rocket to this day with rarely a business day passing without a few commits being made to its repository on GitHub.

Rocket's online documentation is beautiful and well-written. The 15 examples and 3 mini-applications it ships with do a great job of explaining how to use the framework while keeping the line count down. For instance, the chat application is made up of 47 lines of Rust when excluding its tests, 126 lines of JavaScript and 44 lines of HTML. The forms example is very comprehensive with a date picker, file uploading and validation being demonstrated. The underlying code is made up of 77 lines of Rust for the form handling and 163 lines for unit tests.

On June 9th of this year, Rocket version 0.5.0-rc.1 was released with support for both asynchronous operations and Rust's stable channel.

As of this writing, the project is made up of 32K lines of Rust and recently passed the one million downloads milestone on crates.io. This metric won't include anyone cloning the code from GitHub so the true figure could be much higher. For context, the top 10 most downloaded crates have between 55 and 82 million downloads.

In this post, I'll take a look at some of Rocket's features and characteristics.

Rocket Up & Running

The system used in this blog post is running Ubuntu 20.04 LTS. I'll use Rustup to install Rust version 1.55.0.

$ curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh

I'll then use Git to clone Rocket's master branch off of GitHub.

$ sudo apt update
$ sudo apt install \
    curl \
    git
$ git clone https://github.com/SergioBenitez/Rocket.git

I'll then launch the responders example. By default, Rocket will bind to 127.0.0.1 when you launch it, even when the ROCKET_ADDRESS environment variable isn't presented.

$ cd ~/Rocket/examples/responders
$ ROCKET_ADDRESS=127.0.0.1 cargo run

The rendering code for this blog won't show the syntax highlighting of the console output but if you run the above on your system you'll see it's very well thought-out and makes everything easier to mentally parse.

Each of the example's routes are presented and their mime-type is stated if more than one is supported.

   Compiling responders v0.0.0 (/home/mark/Rocket/examples/responders)
    Finished dev [unoptimized + debuginfo] target(s) in 8.83s
     Running `/home/mark/Rocket/examples/target/debug/responders`
🔧 Configured for debug.
   >> address: 127.0.0.1
   >> port: 8000
   >> workers: 4
   >> ident: Rocket
   >> limits: bytes = 8KiB, data-form = 2MiB, file = 1MiB, form = 32KiB, json = 1MiB, msgpack = 1MiB, string = 8KiB
   >> temp dir: /tmp
   >> keep-alive: 5s
   >> tls: disabled
   >> shutdown: ctrlc = true, force = true, signals = [SIGTERM], grace = 2s, mercy = 3s
   >> log level: normal
   >> cli colors: true
📬 Routes:
   >> (file) GET /file
   >> (upload) POST /file
   >> (delete) DELETE /file
   >> (redir_root) GET /redir
   >> (custom) GET /custom?<kind>
   >> (xml) GET /content text/xml; charset=utf-8
   >> (json) GET /content [2] application/json
   >> (many_his) GET /stream/hi
   >> (redir_login) GET /redir/login
   >> (maybe_redir) GET /redir/<name>
   >> (json_or_msgpack) GET /content/<kind>
   >> (one_hi_per_ms) GET /stream/hi/<n>
🥅 Catchers:
   >> (not_found) 404
📡 Fairings:
   >> Shield (liftoff, response, singleton)
🛡️ Shield:
   >> Permissions-Policy: interest-cohort=()
   >> X-Frame-Options: SAMEORIGIN
   >> X-Content-Type-Options: nosniff
🚀 Rocket has launched from http://127.0.0.1:8000

The following will call an endpoint that will return HTML-formatted content.

$ curl -s 127.0.0.1:8000/content
<payload>I'm here</payload>

I'll then request JSON-formatted content from that same endpoint.

$ curl -s 127.0.0.1:8000/content -H "Accept: application/json"
{ "payload": "I'm here" }

The following will upload Rocket's License file to the instance running.

$ curl --data-binary @/home/mark/Rocket/LICENSE-MIT 127.0.0.1:8000/file
1080 bytes at /tmp/big_file.dat

Database Support

Rocket has built-in support for PostgreSQL, MySQL, SQLite and Memcache and communicates with them via connection pools. There are 4 underlying drivers which provide this support. The memcache driver only works with Memcache, Rusqlite supports SQLite alone, Rust-Postgres supports PostgreSQL and Diesel supports PostgreSQL, MySQL and SQLite while providing an ORM interface that is unavailable with the other drivers.

Diesel is the most high-level library of the four mentioned above and also includes support for migrations. To my delight, these are expressed in SQL rather than JSON or some bespoke DSL.

$ cd ~/Rocket/examples/todo/migrations/20160720150332_create_tasks_table
$ cat down.sql
DROP TABLE tasks
$ cat up.sql
CREATE TABLE tasks (
    id INTEGER PRIMARY KEY AUTOINCREMENT,
    description VARCHAR NOT NULL,
    completed BOOLEAN NOT NULL DEFAULT 0
);

INSERT INTO tasks (description) VALUES ("demo task");
INSERT INTO tasks (description) VALUES ("demo task2");

The Diesel project started in 2015 and is made up of 61K lines of Rust that was contributed by 230 developers. It has one of the best-looking guides I've seen among any of the ORM projects I've come across.

If you're looking for non-ORM-based database connectivity, SQLx is also supported and has a comprehensive example dedicated to it. In addition to PostgreSQL, MySQL and SQLite, it also supports Microsoft's SQL Server. SQLx comes with built-in pooling support as well. The project itself is a little over 2 years old and is made up of 36K lines of Rust.

Rocket has around 345 lines of code dedicated to handling shutdowns gracefully and the code for this has a large number of in-line examples. These include advice on handling run-away I/O, grace and mercy periods, etc... It's worth reviewing as the right configuration can help avoid data loss.

When you launch Rocket the shutdown configuration is presented in the console by default.

...
>> shutdown: ctrlc = true, force = true, signals = [SIGTERM], grace = 2s, mercy = 3s
...

Templating Support

Rocket supports both Tera and Handlebars for templates. When a template is called its file extension, either .tera or .hbs decides which engine will be used for rendering.

When you edit a template and Rocket is running in debug mode, you can refresh your browser to see the changes rather than needing to re-build your entire Rust application.

Tera was heavily inspired by Janja2 and Django templates and might be more approachable if you have prior experience with else of those.

SSL Support

When deploying HTTPS-served applications to the wider world, Let's Encrypt and nginx do a great job of providing and serving SSL certificates. Let's Encrypt provides widely-trusted SSL certificates but for non-public facing web applications, only DNS-based proof-of-ownership is supported at this time.

If you're on a team that isn't authorised to make DNS changes then self-signed certificates might be an option to encrypting your internally-facing web application traffic.

There is limited support for TLS 1.2 and 1.3 so any client will need to be reasonably up to date.

In the example TLS project that ships with Rocket, self-signed RSA SHA-256, ECDSA P-256 and Ed25519 certificates are used to demonstrate HTTPS support. I'll launch the example below.

$ cd ~/Rocket/examples/tls
$ cargo run
...
>> tls: enabled w/mtls
...

I'll compile GoLang-based zgrab2 and use it to parse the TLS response. For readability purposes, I'll exclude some of the longer lines from the output.

$ sudo add-apt-repository ppa:longsleep/golang-backports
$ sudo apt update
$ sudo apt install \
    golang-go \
    jq
$ go get github.com/zmap/zgrab2
$ cd ~/go/src/github.com/zmap/zgrab2
$ make
$ echo '127.0.0.1' \
    | ./zgrab2 tls --port=8000 2>/dev/null \
    | jq \
    | grep -v 'digest\|fingerprint\|modulus\|random\|raw\|session_id\|value'
{
  "ip": "127.0.0.1",
  "data": {
    "tls": {
      "status": "success",
      "protocol": "tls",
      "result": {
        "handshake_log": {
          "server_hello": {
            "version": {
              "name": "TLSv1.2",
            },
            "cipher_suite": {
              "hex": "0xC02F",
              "name": "TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256",
            },
            "compression_method": 0,
            "ocsp_stapling": false,
            "ticket": false,
            "secure_renegotiation": true,
            "heartbeat": false,
            "extended_master_secret": false
          },
          "server_certificates": {
            "certificate": {
              "parsed": {
                "version": 3,
                "serial_number": "301438261598342027628437991669560131935683596135",
                "signature_algorithm": {
                  "name": "SHA256-RSA",
                  "oid": "1.2.840.113549.1.1.11"
                },
                "issuer": {
                  "common_name": [
                    "Rocket Root CA"
                  ],
                  "country": [
                    "US"
                  ],
                  "province": [
                    "CA"
                  ],
                  "organization": [
                    "Rocket CA"
                  ]
                },
                "issuer_dn": "C=US, ST=CA, O=Rocket CA, CN=Rocket Root CA",
                "validity": {
                  "start": "2021-07-09T23:33:33Z",
                  "end": "2031-07-07T23:33:33Z",
                  "length": 315360000
                },
                "subject": {
                  "common_name": [
                    "localhost"
                  ],
                  "country": [
                    "US"
                  ],
                  "province": [
                    "CA"
                  ],
                  "organization": [
                    "Rocket"
                  ]
                },
                "subject_dn": "C=US, ST=CA, O=Rocket, CN=localhost",
                "subject_key_info": {
                  "key_algorithm": {
                    "name": "RSA"
                  },
                  "rsa_public_key": {
                    "exponent": 65537,
                    "length": 4096
                  },
                },
                "extensions": {
                  "subject_alt_name": {
                    "dns_names": [
                      "localhost"
                    ]
                  }
                },
                "signature": {
                  "signature_algorithm": {
                    "name": "SHA256-RSA",
                    "oid": "1.2.840.113549.1.1.11"
                  },
                  "valid": false,
                  "self_signed": false
                },
                "validation_level": "unknown",
                "names": [
                  "localhost"
                ],
                "redacted": false
              }
            },
            "validation": {
              "browser_trusted": false,
              "browser_error": "x509: failed to load system roots and no roots provided"
            }
          },
          "server_key_exchange": {
            "ecdh_params": {
              "curve_id": {
                "name": "secp384r1",
                "id": 24
              },
              "server_public": {
                "x": {
                  "length": 384
                },
                "y": {
                  "length": 384
                }
              }
            },
            "signature": {
              "type": "rsa",
              "valid": true,
              "signature_and_hash_type": {
                "signature_algorithm": "rsa",
                "hash_algorithm": "sha512"
              },
              "tls_version": {
                "name": "TLSv1.2",
              }
            }
          },
          "client_key_exchange": {
            "ecdh_params": {
              "curve_id": {
                "name": "secp384r1",
                "id": 24
              },
              "client_public": {
                "x": {
                  "length": 384
                },
                "y": {
                  "length": 384
                }
              },
              "client_private": {
                "length": 48
              }
            }
          },
          "client_finished": {
            "verify_data": "mVzWiSmT2WgE/Lra"
          },
          "server_finished": {
            "verify_data": "x0ClzuGQDQb2LKRy"
          },
          "key_material": {
            "master_secret": {
              "length": 48
            },
            "pre_master_secret": {
              "length": 48
            }
          }
        }
      },
      "timestamp": "2021-09-03T08:16:07Z"
    }
  }
}

Benchmarking Rocket

Sergio has stated that he won't cater to benchmarks. I still believe it's important to understand what sort of baseline overhead to expect, even if it's from an overly simplistic "Hello, World" example.

I came across Rousan Ali's Rust Web Frameworks Benchmark which was carried out with Rocket 0.4.7 which was prior to async support being added. The benchmark returns "Hello, world!" and quits.

$ git clone https://github.com/rousan/rust-web-frameworks-benchmark.git
$ cat rust-web-frameworks-benchmark/rocket/src/main.rs
#![feature(proc_macro_hygiene, decl_macro)]

#[macro_use]
extern crate rocket;

#[get("/")]
fn index() -> &'static str {
    "Hello, world!"
}

fn main() {
    rocket::ignite().mount("/", routes![index]).launch();
}

The benchmark is run with Will Glozer's wrk HTTP benchmarking tool using 4 threads and 200 open connections for a duration of 8 seconds. The benchmark put Rocket version 0.4.7 5x slower than hyper, Actix and warp and around 3x slower than gotham.

Azzam Syawqi Aziz raised issue #2 in that repository and presented various patches to the benchmark that used a non-specific version of 0.5.0 that was a least a month older than 0.5.0-rc.1's release. His fastest combination of code and settings for 0.5.0 was 2.3x quicker than the version 0.4.7 he tested against.

The async speed up seen in issue #2 should put Rocket much closer to gotham's results and I suspect if the grievances in issue #7 were addressed we might see the results close up even further.

The above is worth revisiting when the final version of Rocket v0.5.0 is released.

Thank you for taking the time to read this post. I offer both consulting and hands-on development services to clients in North America and Europe. If you'd like to discuss how my offerings can help your business please contact me via LinkedIn.

Copyright © 2014 - 2024 Mark Litwintschik. This site's template is based off a template by Giulio Fidente.