#3: Conspiracies, Dissemination, and REST APIs

Welcome to the third post in a series where I share my experience learning Rust. I’m building out a conspiracy theories API to help me get more familiar with Rust and have a little fun. Since I am new to Rust, I welcome all feedback, especially from developers who have been using Rust for quite some time. Leave a comment below or send your feedback via Twitter. Now it’s time to draw the blackout curtains, put on your foil-lined hat on and start serving up conspiracies from a REST API.

Previous posts in the series

In this post, I create HTTP GET routes to fetch a page full of conspiracies, a single conspiracy or tags, more on tags later. I am using the crate actix-web to implement my web service. The actix-web crate offers the ability to handle requests asynchronously. After adding a new binary, the conspiracies_api, to the Cargo.toml file I am ready to get started on creating the REST API. For those of you who haven’t read the previous post, I explain how to add binaries to a project in that post.

While you were away

I’ve renamed the WikiPage struct to Conspiracy since that is what I’m storing in this project. I looked at the categories that I retrieved from Wikipedia, and each page had the same categories so, I’ve removed all the wikipedia categories and replaced categories with the notion of tags. Users can add a single tag or multiple tags to a conspiracy with tags that fit, like UFO, or 9–11. I’ve moved the structs that were in the wiki module into a new models module. Ok, now that you’re caught up let’s start creating the REST API.

Creating a Web Service

I want an API that allows me to access conspiracies, read them and add tags to them. I plan on using the API to power web applications. When I started looking for crates to power the REST API I came across rocket I was intrigued, it seems to be a heavily used crate and allows you to spin up a REST service quickly. However, while doing some looking into Rocket, I saw issue #55 which talks about how the author of Rocket dislikes of middleware. Since one of the driving forces of this post and the previous posts is to allow me to research crates that I may want to use for a project that requires different types of middleware use of auth, logging, and other middleware, I decided to keep looking for a different option. That’s when I found actix-web. actix-web is built with the actix crate, an actor based framework. According to the actix guide the Actor Model “allows applications to be written as groups of independently executing but cooperating “Actors” which communicate via messages.” That sounded interesting to me, and the code samples I saw didn’t seem too daunting, so I decided to use actix-web.

A simple HTTP GET Request

I’m going to get familiar with actix-web by creating a simple HTTP GET request that returns a string. Here’s the code for a simple handler.

Since the index function is pretty straightforward, I’ll walk you through the main function instead, starting with the server::new call. The only parameter server::new takes is a closure function. The closure starts off by creating a new App without passing in any state. The chained resource call maps the / path to another function that implements the core::ops::FnOnce trait. I started writing an overview of what the FnOnce trait is but the documents do a better job of it so if you are curious about what it is, check out the docs.

Adding Middleware

Before I talk about a route, I want to show you how to add middleware to an actix-web service. I’ll add logging middleware that uses the env_logger. At the beginning of the main function, I set an environment variable, RUST_LOG that sets the log level for my middleware which will be used by the logger that is initialized on the next line. Now, I can hookup the logging middleware by adding a middleware(Logger::default()) before defining the routes.

I run the service and make a HTTP GET /tags call and see the following output.

Running `target/debug/conspiracies_api`
INFO 2018-06-06T21:43:40Z: actix_web::server::srv: Starting 8 http workers
INFO 2018-06-06T21:43:40Z: actix_web::server::srv: Starting server on http://127.0.0.1:8088
Started http server: 127.0.0.1:8088
INFO 2018-06-06T21:43:46Z: actix_web::middleware::logger: 127.0.0.1:51337 [06/Jun/2018:17:43:46 -0400] "GET /tags HTTP/1.1" 200 240 "-" "insomnia/5.16.6" 0.001722

My middleware is ready to go, its time to move on to discuss house actix-web uses actors to handle the requests from clients.

Actors & Messages

Before getting into a more realistic handler function, I want to discuss how the route handlers interact with the database. The actix-web crate was built using the Actor model, which is a way to handle concurrent computations or tasks. Each actor can do the following three things in no particular order:

  • Send a message to other actors
  • Do work based on the message it receives
  • Start other actors

For my API I am sending messages to actors, that make queries against the table that is specified by the message sent. Let’s take a look at get_conspiracies_by_id(req: HttpRequest<State>) -> impl Future<Item=HttpResponse, Error=Error> the handler for the /conspiracies/{page_id} route.

The function’s signature has a generic version of the HttpRequest. It expects an instance of the State struct defined in main.rs. I use the State object to pass around the DbExecutor which contains the database connection and is the actor I am using throughout the API service. More on the DbExecutor when we get to the actor code.

The return value of the function uses something new to Rust 1.26 the impl trait return value. What that means that get_conpsiracies_by_id returns something that implements the trait Future where the Item is an HttpResponse and the Error is an actix_web::Error

The first line of the body of the function grabs the page_id value from the URL. The rest of the function deals with sending the message and handling the returned data from the actor. Let’s take a look at the DbExecutor and the GetConspiracy code.

The DbExecutor is the actor, and GetConspiracy is a message that the handler sends to DbExecutor. Thankfully, the names of the traits that the two structs must implement make it clear which role each struct is playing. Each route handler sends a different message to DbExecutor. The DbExecutor implements a Handler trait for each message type DbExecutor wants to support.

The GetConspiracy message has one public field, page_id. The message is how the handler sends the page_id of the conspiracy the user has requested. To use this struct as a message, I have to implement the actix::Message trait, which requires the definition of a Result type. For the GetConspiracy type, I’ve defined the type to be Result<Conspiracy, String> where Conspiracy is the object that contains the record from the database and the String is where an error message would return if an error occurs.

The DbExecutor actor indicates it wants to support the GetConspiracy message by implementing the actix:: Handler<GetConspiracy> trait. The only requirement of the trait is to define the Result type and a handle function.

The handle function takes a reference to the DbExecuter instance that is receiving the message, the GetConspiracy message and a third parameter that I am ignoring. It returns the Result that defined in the trait implementation. Acting on this message is simple, I call the db::get_conspiracy_by_id function passing it a reference to the database connection that is ‘in’ the DbExecory object and the page_id that is a field in the GetConspiracy message. The return value from that call ends up in the route handler, in this case, it’s the get_conspiracy_by_id route handler back in the src/conspiracies_api/main.rs file.

The GET /conspiracies/{page_id} route

Now that I’ve gone over what an actor is and how I’m using them here let’s go over the route that retrieves a single conspiracy. The handler is an asynchronous handler. The updated main function looks like this, the resource definition for the new route is a slightly different call. The first thing you’ll notice is that in the closure, I define the supported method for this route as an HTTP GET and nothing else.

If I try and POST to that route a 405 error, Method Not allowed error, is returned.

That works as you’d expect given the fact that I specifically said HTTP GET is the only supported method. Let’s go back to the ‘/’ route; I haven’t specified what methods are available for the route. I wonder what happens when I do a POST /.

I can post that route without an issue. I probably should fix that. Thankfully it is pretty straightforward. First, I’ll add the r.method(http::Method::GET). I also add a few lines so that if someone tries to do a POST to an HTTP GET route, they should receive a 405 Method Not Allowed error instead of the 404 Not Found error they would receive without the updated code.

The default_resource throws a 405 Error for all non-GET requests. After fixing that oversite and adding the 405 code, I’d like to go over the route handler responsible for retrieving the requested conspiracy from the database.

Meanwhile, Back in the Route Handler

Once the actor has returned the results of its work, I need to determine if the work completed successfully or with an error. There are two method calls made to prep the returned value for processing. The from_err() function reformats any errors returned from the actor into an HttpResponse. The and_then call processes the response from from_err() or the results that were returned from the call to retrieve the requested conspiracy.

The and_then() method takes a FnOnce to allow us to work with the results of the message call. In this case, I’m merely matching on the Ok or Error states. If it found the conspiracy, it returns a JSON representation of the conspiracy. If the call returns an error, a NotFound error. Both responses are sent via the responder() call. Now that we’ve discussed how actors and messaging works, there are a few lines in the main function that I’d like to go over so that you can understand how the actix system gets started.

Starting the actix::System

The first line initializes the actix system. According to the actix::System docs, it creates a new Arbitor associated with the new system. An Arbitor can give you access to the event loop through its API. We won’t be using it in this series, but in the future, if you need access, it’s there.

SyncArbiter::start starts up an actor, the DbExecutor actor to be specific. The function takes two parameters. The first one is the number of threads to start for the actor. The second parameter is a function, here, I use a closure that instantiates DbExecutor actors, creating a connection to the database. The start call returns an which is an address to the actor.

The last step is to tell the system its time to ‘run.’ You do that by calling run() on the returned Addr from the start() call. The service is now ready to handle web requests.

The GET /conspiracies route

Now that you’ve gone through the fundamentals of how I’m using actors there are a few more handler related items I’d like to go over First is how I’m handling paging. The /conspiracies route returns a paged list of no more than 25 conspiracies. The handler sends a Conspiracies message which has a single field, page_num, that tells the actor what page of conspiracies the user wants. The page_num field gets its value from the page query parameter. Here’s how to retrieve the value of a query parameter.

let page_num = req.query()
.get("page")
.unwrap_or("0")
.parse::<i64>().unwrap();

req.state().db.send(Conspiracies{page_num: page_num})

The calls above, get the parameter by name, unwraps the value if the page parameter is present or sets the value of page_num to zero if the page parameter is not is there. The parse call converts the string value into an i64 value. The parsed value is used to set the value of the Conspiracies.page_num field.

Handling HTTP Posts

The last area of the web service I’d like to discuss is how to handle a POST request. I’ll go over how the /tags/new handler parses the body of the request. A quick note on the URL, I wanted to have a POST and a GET /tags route, but it appears as if actix-web does not use the HttpMethod as a differentiator between two routes. What happened was whichever route it encountered first was the one that was recognized. So I went with the /tags/new route to handle adding new tags.

.resource("/tags/new", |r| r.method(http::Method::POST).with(add_tag))

For the post routes, I’m using the with function to assign a handler to the route. The reason for using the with call is it allows me to use extractors to parse the body of the POST request. An extractor is a type-safe way of retrieving data. The handler’s signature looks a little different than the GET routes, here’s the add_tags function signature.

fn add_tag((req, tag): (HttpRequest<State>, Json<models::NewTag>)) -> Box<Future<Item=HttpResponse, Error=Error>>

Since I need both the State object and the request body, I am using a tuple. The first entry in the tuple is the request itself and the second is a struct that supports the deserialization of the JSON into an object. The NewTag struct is a simple struct and is used to parse the body and insert a new tag into the tags table.

#[derive(Insertable, Debug, Serialize, Deserialize)]
#[table_name="tags"]
pub struct NewTag {
pub name: String
}

The last difference between the GET and POST handlers is the way that I set the message field’s value.

req.state().db.send(AddTag{tag: tag.into_inner()})

The tag object returned from the JSON extractor is used to set the value of the AddTag.tag field. To get the NewTag object, I need to call the into_inner() method. If I needed the value of the name field in the NewTag object, I could use tag.name without a problem. However, if I try to set the message’s tag field to the tag object without the into_inner() method I get the following error:

--> src/conspiracies_api/main.rs:34:37
|
34 | req.state().db.send(AddTag{tag: tag})
| ^^^ expected struct `conspiracies::models::NewTag`, found struct `actix_web::Json`

To get the NewTag out of the JSON extractor, I need to make a call to into_inner() which returns the NewTag object. The remaining part of the handler follows the same pattern as the routes we’ve seen earlier. I now have a functioning API! What am I going to do with that?

In this post, I’ve shown how I set up a REST API using the actix-web crate. We took a deep dive into how the /conspiracies/{page_id} route is handled using messages and actors, how to parse path and query parameters, add middleware, as well as start up the actix system.

Reading

Crates of Interest

Me


Originally published at www.myprogrammingadventure.org.