Responses (0)
Introduction#
The Rust language has been gaining tracking for a lot of reasons in the last few years. One of them is the excellent performance and strong high-level constructs provided by Rust's zero-cost abstractions paradigm. Safety and efficiency are traits expected from all Rust frameworks.
You might think that safety and efficiency is something you want from your next webserver application. You might also think Rust language, being lower level than our usual web server go-to languages, might be complicated or convoluted. That's where the developers of Rocket come in. They wanted to marry developer ergonomics to the benefits coming from the language itself so you have no choice but to choose Rocket as the web framework for your next project.
We're starting from scratch, installing rust and rocket and building a server that greets you. The intention here is to introduce the Rocket framework for beginner Rust developers and show the door of web applications in the Rust language.
What You Will Learn#
Setting up your Rust compiler for Rocket
Booting your first Rocket server
Adding a route that takes a parameter and greets you
What You Should Know#
Having familiarity with the Rust language
Knowing the basic concepts of a web server
If you aren't exactly comfortable with the Rust syntax and basic concepts, maybe the official Rust book can help, or maybe even the Fullstack Rust course!
Rust setup#
First thing first: we need to get Rust running. I said we're starting from scratch and I meant it. To get started with our project, we need rustup and cargo. If you already have Rust installed in your system, you can skip this section.
Rustup is the Rust toolchain manager. It's used to install and update the compiler and plug-ins like the official formatter and the official language server. Cargo is the Rust package manager, used to manage projects and dependencies.
To get them both properly installed, follow these official instructions. These installers are very friendly to beginners and you should be done in no time. To verify it's all up and running type rustup --version
and cargo --version
. If you see their versions, we're good to go.
Here's an example of my current outputs.
$ rustup --version
rustup 1.21.1 (7832b2ebe 2019-12-20)
$ cargo --version
cargo 1.42.0 (86334295e 2020-01-31)
Project setup#
Now having rustup and cargo working, we can start our project. Cargo has a handy method to get us started: cargo new --bin rusty-greeter
. This command will create a new folder containing an executable project called rusty-greeter
.
The --bin
parameter for the cargo new
command tells cargo
we want the project to be executable. Conversely, we might have wanted to create a library to be imported by other projects, which would then require the --lib
parameter instead.
Getting inside the project and running cargo run
should execute the project, which will consist just of a Hello World print for now.
$ cargo new --bin rusty-greeter
Created binary (application) `rusty-greeter` package
$ cd rusty-greeter/
$ cargo run
Compiling rusty-greeter v0.1.0 (/home/lsunsi/rusty-greeter)
Finished dev [unoptimized + debuginfo] target(s) in 0.17s
Running `target/debug/rusty-greeter`
Hello, world!
Dependencies#
Next, we're going to install the rocket package. If you're tired of all the setup, bear with me for just one more section. The project should contain an auto-generated Cargo.toml
file. The required packages are declared there under the dependencies bracket.
[dependencies]
rocket = "0.4.4"
That means we want to add the rocket
crate with the particular 0.4.4
version to our project. One more thing though: the rocket
crate requires the nightly compiler to run, so we need to tweak one more option with the rustup override set nightly
command. After that, we can run cargo build
to see if it's all working fine.
$ rustup override set nightly
info: override toolchain for '/home/lsunsi/rusty-greeter'\
set to 'nightly-x86_64-unknown-linux-gnu'
nightly-x86_64-unknown-linux-gnu unchanged -\
rustc 1.44.0-nightly (94d346360 2020-04-09)
$ cargo build
Updating crates.io index
Downloaded ...
Compiling ...
Finished dev [unoptimized + debuginfo] target(s) in 25.37s
I removed the multiple "Downloaded" and "Compiling" lines from my output, but if you saw something like the above output you're ready to go!
You might be curious about the nightly compiler. It is the most up-to-date compiler rust can offer, including all unstable experiments the development team is doing up to last night. To learn more about what it is and what's different about it, check out this section of the official book.
Getting to Hello World#
All right, the setup is done and solid. We want to have an example GET
endpoint that sends from the server a "Hello World" string, just so we know it's all working. Our whole code will be written on the main.rs
file, which should also have been auto-generated by cargo. Open up the file in our favorite editor and replace its contents with this.
#![feature(proc_macro_hygiene, decl_macro)]
use rocket::{get, ignite, routes};
#[get("/greet")]
fn greet() -> &'static str {
"Hello, world!"
}
fn main() {
ignite().mount("/", routes![greet]).launch();
}
Let's break down this code, which is the core of the rocket application.
#![feature(proc_macro_hygiene, decl_macro)]
This line is required by rocket to compile. It's a kind of declaration to the rust compiler what kind of experimental features we want to enable on our project. Don't think about this line too hard, it's just required by rocket currently so we do it and move on.
use rocket::{get, ignite, routes};
This line just imports some functions from rocket to make our code cleaner. The first one, get, is a macro that defines a GET
route. The second one, ignite, is the function that creates the rocket application instance. The last one, routes, is a macro that groups all our routes so they can be bundled into the application. Don't worry about understanding them all now, just pay attention to how they are used in the code.
#[get("/greet")]
fn greet() -> &'static str {
"Hello, world!"
}
We're using the get
macro along with a regular rust function to define, respectively, a route and a handler for it. This bit of code is what the route is and does: the request of a GET
on the /greet
path will return a "Hello, world!" string.
fn main() {
ignite().mount("/", routes![greet]).launch();
}
Finally, the regular Rust main function is populated with the ignite, mount and launch function calls, which are just what rocket needs to start running. The important parts of this code are: the "/"
string, which is the root path of the server; and routes
macro, which should just list all routes in our server.
Testing the Hello World#
We have all we need to see the first response from our server. To run the server with the current code, just run cargo run
. This command builds the project if needed, so don't worry about building it yourself. After a successful compilation, you should see something like this.
$ cargo run
Finished dev [unoptimized + debuginfo] target(s) in 0.05s
Running `target/debug/rusty-greeter`
š§ Configured for development.
=> address: localhost
=> port: 8000
=> log: normal
=> workers: 24
=> secret key: generated
=> limits: forms = 32KiB
=> keep-alive: 5s
=> tls: disabled
š° Mounting /:
=> GET /greet (greet)
š Rocket has launched from http://localhost:8000
That's awesome! You can already see it's all running and with some emojis to make our day better. So how do we try it? You can just open up http://localhost:8000/greet
in your browser or use an http client to try it from terminal. Here's an output from my favourite one.
$ http http://localhost:8000/greet
HTTP/1.1 200 OK
Content-Length: 13
Content-Type: text/plain; charset=utf-8
Date: Sat, 18 Apr 2020 14:26:50 GMT
Server: Rocket
Hello, world!
If you're curious about the log coming from rocket on boot, you can check out the documentation about it. It will be useful when you want to change default configuration.
Getting to Hello You#
We already have the greet
endpoint that greets the world, and all the setup that comes with it. We're half-way there now, we just need to take a parameter so we can greet you instead of the world. Thankfully for us, Rocket makes it easy to do so.
Adding a router parameter to a route in Rocket requires no more imports than what we already have, we just have to pass an extra parameter to the get
macro and take the parameter in the function handler like so.
#[get("/greet/<name>")]
fn greet(name: String) -> String {
format!("Hello, {}!", name)
}
Let's once again break down the differences here.
#[get("/greet/<name>")]
Where we previously had the /greet
string literal, we now have a /greet/<name>
. This just tells Rocket we are expecting something to follow the "greet" on the route path, and we want that something to be named "name".
fn greet(name: String) -> String
Then we take that "name" as a parameter on the handler, limiting to String
s only. The Rocket framework will make sure only Strings pass into the handler. We also replaced the previous &str
return type for a String
one, which just means we're going to return a new entire string instead of a literal one.
format!("Hello, {}!", name)
Lastly, this format macro call constructs the response we want from the parameter. This macro is very handy for simple string manipulation as we're doing right now.
If you got confused about the String vs &str debacle, check out this short blog post about it. Don't worry about it though, it's a regular source of confusion for Rust beginners.
Testing the Hello You#
You are probably getting the hang of it already, but we need to stop running the previously running server (a control+C should suffice) and run it again. Unfortunately, Rocket does not provide us with an automatic code reload. After a new cargo run
, we should see this again.
$ cargo run
Compiling rusty-greeter v0.1.0 (/home/lsunsi/rusty-greeter)
Finished dev [unoptimized + debuginfo] target(s) in 0.78s
Running `target/debug/rusty-greeter`
š§ Configured for development.
=> address: localhost
=> port: 8000
=> log: normal
=> workers: 24
=> secret key: generated
=> limits: forms = 32KiB
=> keep-alive: 5s
=> tls: disabled
š° Mounting /:
=> GET /greet/<name> (greet)
š Rocket has launched from http://localhost:8000
Also again, we can hit it up on the browser at http://localhost:8000/greet/Lucas
or with an http client, which will output something like this.
$ http http://localhost:8000/greet/Lucas
HTTP/1.1 200 OK
Content-Length: 13
Content-Type: text/plain; charset=utf-8
Date: Sat, 18 Apr 2020 15:05:06 GMT
Server: Rocket
Hello, Lucas!
You might be wondering so I'm just going to confirm it. I'm Lucas, that's why I tested with my name. I want to be greeted, just like you.
Greeting Everyone#
If you're curious, you might have tried the previous example, without the /<name>
parameter, only to find out we broke it. On the browser, it will show an error page.
$ http http://localhost:8000/greet
HTTP/1.1 404 Not Found
Content-Length: 496
Content-Type: text/html; charset=utf-8
Date: Sat, 18 Apr 2020 15:09:05 GMT
Server: Rocket
It's not found! This happened because we told Rocket that the name should be a String. Rust is very strict on its typing system so if told it's a String, it won't accept anything else. Since we want to support both the "Hello World" and the "Hello Lucas" paths, what should we do?
It's surprisingly simple. If we want both behaviors to be present, you just have to write them both as two separate routes. That's what you're main.rs
will look like when totally ready.
#![feature(proc_macro_hygiene, decl_macro)]
use rocket::{get, ignite, routes};
#[get("/greet")]
fn greet_world() -> &'static str {
"Hello, World!"
}
#[get("/greet/<name>")]
fn greet(name: String) -> String {
format!("Hello, {}!", name)
}
fn main() {
ignite().mount("/", routes![greet_world, greet]).launch();
}
There's a couple routes now, with both behaviours. They should have different function names, so Rust can tell them apart, and they should both be added to the routes
macro. Re-running the server again with cargo run
we can see they both are working!
$ http http://localhost:8000/greet/Lucas
HTTP/1.1 200 OK
Content-Length: 13
Content-Type: text/plain; charset=utf-8
Date: Sat, 18 Apr 2020 15:45:56 GMT
Server: Rocket
Hello, Lucas!
$ http http://localhost:8000/greet
HTTP/1.1 200 OK
Content-Length: 13
Content-Type: text/plain; charset=utf-8
Date: Sat, 18 Apr 2020 15:45:57 GMT
Server: Rocket
Hello, World!
Conclusion#
We made it, we have an up-and-running Rust server with a greeting route! Reaching here, you learned a couple of things.
You installed Rustup and Cargo, along with the Rust compiler
You created the project and installed rocket on it
You added two greeting routes that work with a dynamic "name" parameter
You saw it all live in your browser!
Of course, this is just an introductory post to servers in Rust. The intention here was to just break the ice and show that Rust can have good developer ergonomics even with higher-level applications, like webservers. Rocket paved the road and we walked it together.
Next Steps#
If you want to keep the learning going, I'd suggest you to start getting more complex with the webserver. Try to answer some of these questions while you keep learning Rocket and Rust.
How would you add a
POST
route to your server?How would you keep an internal state in your server?
How would you send and receive
JSON
on the server?
The Rocket documentation will be an invaluable help on your learning, but just by writing it yourself, you'll get familiar with the concept. I hope I have helped to get you started, thanks for the reading!