├── .gitignore ├── Cargo.toml ├── src └── main.rs ├── README.md ├── README.es.md └── Cargo.lock /.gitignore: -------------------------------------------------------------------------------- 1 | target 2 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "rocket-loadavg-api" 3 | version = "0.1.0" 4 | authors = ["Sebastián Magrí "] 5 | 6 | [dependencies] 7 | libc = "*" 8 | rocket = "0.2.6" 9 | rocket_codegen = "0.2.6" 10 | rocket_contrib = { version = "0.2.6", features = ["json"] } 11 | serde = "0.9" 12 | serde_json = "0.9" 13 | serde_derive = "0.9" -------------------------------------------------------------------------------- /src/main.rs: -------------------------------------------------------------------------------- 1 | #![feature(plugin)] 2 | #![plugin(rocket_codegen)] 3 | 4 | extern crate libc; 5 | extern crate rocket; 6 | #[macro_use] extern crate rocket_contrib; 7 | #[macro_use] extern crate serde_derive; 8 | extern crate serde_json; 9 | 10 | use libc::{c_double, c_int}; 11 | 12 | use rocket_contrib::JSON; 13 | 14 | #[derive(Serialize)] 15 | struct LoadAvg { 16 | last: f64, 17 | last5: f64, 18 | last15: f64 19 | } 20 | 21 | extern { 22 | fn getloadavg(load_avg: *mut c_double, load_avg_len: c_int); 23 | } 24 | 25 | 26 | impl LoadAvg { 27 | fn new() -> LoadAvg { 28 | let load_averages: [f64; 3] = unsafe { 29 | let mut lavgs: [c_double; 3] = [0f64, 0f64, 0f64]; 30 | getloadavg(lavgs.as_mut_ptr(), 3); 31 | lavgs 32 | }; 33 | 34 | LoadAvg { 35 | last: load_averages[0], 36 | last5: load_averages[1], 37 | last15: load_averages[2] 38 | } 39 | } 40 | } 41 | 42 | #[get("/loadavg")] 43 | fn loadavg() -> JSON { 44 | JSON(LoadAvg::new()) 45 | } 46 | 47 | fn main() { 48 | rocket::ignite() 49 | .mount("/", routes![loadavg]) 50 | .launch(); 51 | } 52 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Building high performance REST APIs with Rust and Rocket 2 | 3 | This project is done as part of a workshop to show how to build APIs using [Rocket](https://rocket.rs). It was originally written in Spanish for the RustMX meetup as you can find it in `README.es.md`. 4 | 5 | The goal of this project is to show the fundamental concepts needed to implement a REST API using Rust and Rocket, highlighting some specific details over the way. 6 | 7 | The slidedeck for this workshop is [available in Spanish](http://slides.com/sebasmagri/construyendo-servicios-web-de-alto-rendimiento-con-rust-y-rocket). However, this document describes the implementation in more detail. 8 | 9 | ## Objectives 10 | 11 | The goal of this API will be to allow clients to query the load average of a host through a single endpoint. 12 | 13 | Load average is an abstraction of how busy a host has been in the last minute, the last 5 minutes and the last 15 minutes. The values for each timeframe are a relation between the system's capacity to process tasks and the amount of tasks to be processed. 14 | 15 | Clients will query the load average issuing a `GET` request to a `/loadavg` endpoint, and they will get a JSON answer as follows: 16 | 17 | { 18 | 'last': 0.7, 19 | 'last5': 1.1, 20 | 'last15': 0.8 21 | } 22 | 23 | ## Preparing the environment 24 | 25 | Rocket *still* requires Rust *Nightly* because of some features that have not yet landed in a stable release of the compiler. Fortunately, [rustup](https://rustup.rs/) makes it really easy to install and manage multiple Rust compiler releases. To install Rust Nightly, we can run the official rustup script: 26 | 27 | $ curl https://sh.rustup.rs -sSf | sh 28 | 29 | This method _just works_ for UNIX environments. If you're working on Windows you should follow [other installation methods](https://github.com/rust-lang-nursery/rustup.rs/#other-installation-methods). 30 | 31 | By default, rustup installs the stable toolchain. Then, we need to install the nightly toolchain with: 32 | 33 | $ rustup install nightly-2017-05-18 34 | 35 | If your Rocket application stops working after an update, you should update your toolchain as well: 36 | 37 | $ rustup update 38 | 39 | ## Creating the new project 40 | 41 | In the Rust world, the project, dependencies and build management is done using *Cargo*. Cargo automates a lot of tasks and you will be definitely using it really often while working with Rust. 42 | 43 | To generate the initial files structure of our application we can run: 44 | 45 | $ cargo new loadavg-api --bin 46 | $ cd loadavg-api/ 47 | 48 | Now, we must set the project to use the nightly toolchain: 49 | 50 | $ rustup override set nightly-2017-01-25 51 | 52 | ## Rocket installation 53 | 54 | Now that we have our project in place, lets add *Rocket* to its dependencies. 55 | 56 | `cargo` tracks dependencies in a `Cargo.toml` file found the project root. We must use the `[dependencies]` section on this file to define which *crates* are going to be used by our project. By default, those crates are fetched from the central community repo at [crates.io](https://crates.io/). Thus, we add `rocket` and `rocket_codegen` to our dependencies. The latter includes code generation tools and it makes it a lot easier to implement APIs. 57 | 58 | [dependencies] 59 | rocket = "0.1.6" 60 | rocket_codegen = "0.1.6" 61 | 62 | The next time we run `cargo build` or `cargo run`, cargo will automatically find, fetch and build all of the dependencies. 63 | 64 | ## Building the API 65 | 66 | ### Initial modelling 67 | 68 | As a first step, lets do a model of the data that will be handled by our API. Having a strong functional programming influence, Rust uses *data types* for this. 69 | 70 | #### Data Types 71 | 72 | Rust allows the definition of new data types by using `struct`s. Then, would we need an abstraction of the load average, we could implement it as follows: 73 | 74 | #[derive(Debug)] 75 | struct LoadAvg { 76 | last: f64, // last minute load average 77 | last5: f64, // last 5 minutes load average 78 | last15: f64 // last 15 minutes load average 79 | } 80 | 81 | Here we are creating a `LoadAvg` `struct` with 3 *fields*, each one of those has a `f64` data type, the Rust primitive data type for 64 bits floating point numbers. This struct is by itself a new data type which abstracts the concept of load average. If we look closely at the JSON response that clients should be getting, we will find `LoadAvg` to be pretty similar. 82 | 83 | Above the definition of our `LoadAvg` struct, we can find `#[derive(Debug)]`. This is a way in which Rust _implements_ a `trait`. The `trait` describes certain specific behaviours of a data type. In this specific case, to aid with debugging, we are adding `LoadAvg` the necessary behaviour to be able to print an instance of it to the standard output by using the `{:?}` format specifier. This way we can get a detailed representation of the data type: 84 | 85 | println!("{:?}", load_avg); 86 | ... 87 | LoadAvg { last: 0.9, last5: 1.5, last15: 1.8 } 88 | 89 | We can now add our new data type to the `src/main.rs` file and go on. 90 | 91 | #### Data type behaviour 92 | 93 | Rust `struct`s are not static structures. Rust actually allows is to model a data type behaviour by using *methods*, in a similar way to the object oriented programming classes. For example, to add a _constructor_ to our `LoadAvg` data type, we can use an `impl` block: 94 | 95 | impl LoadAvg { 96 | fn new() -> LoadAvg { 97 | // Placeholder 98 | LoadAvg { 99 | last: 0.9, 100 | last5: 1.5, 101 | last15: 1.8 102 | } 103 | } 104 | } 105 | 106 | We will be able to use the `new` method onwards to create *instances* of this data type. For example, in our `main` function in `src/main.rs`, we could use: 107 | 108 | fn main() { 109 | let load_avg = LoadAvg::new(); 110 | println!("{:?}", load_avg); 111 | } 112 | 113 | ##### Getting real load average data 114 | 115 | This particular section is not implemented in the workshop because of time constraints, but it is documented in detail here to show how to integrate C standard library functions in Rust. 116 | 117 | Until now, we have been using placeholder values for the fields of `LoadAvg`. However, one would like `LoadAvg::new()` to return an instance with the current load average values. 118 | 119 | The recommended way to get the system's load average is using the `getloadavg` function from `libc`, the C standard library. However, this function is implemented in *C*, and C do not give us the safeguards that Rust offers. Even so, it's quite simple to integrate it in our Rust code. We must indicate it's an _external_ function, and it's _unsafe_. 120 | 121 | First of all, lets add a reference to `libc` in our project's `[dependencies]` in the `Cargo.toml` file: 122 | 123 | libc = "*" 124 | 125 | Then, we can reference this _crate_ in our source code, at the top of `src/main.rs`: 126 | 127 | extern crate libc; 128 | 129 | This allow us to use any of the functions defined in the [libc](https://doc.rust-lang.org/libc/x86_64-unknown-linux-gnu/libc/) crate in our project. 130 | 131 | Now, if we look at the [getloadavg function signature in C](https://linux.die.net/man/3/getloadavg), we will see that the first parameter is a pointer to an array of `double` values, and the second one is an `int`: 132 | 133 | # This is C code 134 | int getloadavg(double loadavg[], int nelem); 135 | 136 | However, neither `double` nor `int` are present among the primitive Rust data types, and we need to find an implementation of those data types for Rust. Fortunately, we can find it as `c_double` and `c_int` in the `libc` crate, so we _use_ them in our code: 137 | 138 | use libc::{c_double, c_int}; 139 | 140 | Then, we are able to add a reference to this function in our Rust code: 141 | 142 | extern { 143 | fn getloadavg(load_avg: *mut c_double, load_avg_len: c_int); 144 | } 145 | 146 | As we can see, this function will take a _mutable_ `c_double` pointer to the first element of the output array, and a `c_int` for the count of elements. 147 | 148 | Now we're able to call `getloadavg`: 149 | 150 | let load_averages: [f64; 3] = unsafe { 151 | let mut lavgs: [c_double; 3] = [0f64, 0f64, 0f64]; 152 | getloadavg(lavgs.as_mut_ptr(), 3); 153 | lavgs 154 | }; 155 | 156 | This way, our `LoadAvg::new` _constructor_ can be: 157 | 158 | fn new() -> LoadAvg { 159 | let load_averages: [f64; 3] = unsafe { 160 | let mut lavgs: [c_double; 3] = [0f64, 0f64, 0f64]; 161 | getloadavg(lavgs.as_mut_ptr(), 3); 162 | lavgs 163 | }; 164 | 165 | LoadAvg { 166 | last: load_averages[0], 167 | last5: load_averages[1], 168 | last15: load_averages[2] 169 | } 170 | } 171 | 172 | ### API implementation 173 | 174 | #### The /loadavg endpoint 175 | 176 | According to the initial requirement, we need a `/loadavg` _endpoint_ that will handle `GET` requests and will respond with the load average in JSON. 177 | 178 | To accomplish this, Rocket maps a _route_ and a set of validation conditions to a function that will _handle_ the input data and generate a response. The validations are concisely expressed through *attributes* in the functions. This attribute is used to define the request _method_, parameters and constraints of a specific endpoint. 179 | 180 | With this in mind, the responsibility of our _handler_ will be to create a new instance of `LoadAvg` y return its value in JSON. 181 | 182 | First, we add the necessary references to our `src/main.rs` for the Rocket tools. At the top of the main file, we must add some directives to tell the compiler that we'll be using some custom features: 183 | 184 | #![feature(plugin)] 185 | #![plugin(rocket_codegen)] 186 | 187 | extern crate rocket; 188 | 189 | Next, lets implement an initial handler for our endpoint: 190 | 191 | #[get("/loadavg")] 192 | fn loadavg() -> String { 193 | format!("{:?}", LoadAvg::new()) 194 | } 195 | 196 | The handler definition starts with an *attribute* in which we define the request method, the route and the parameters of an endpoint. `#[get("/loadavg")]` indicates that the following function will respond only to `GET` requests to the `/loadavg` path, and will not take any parameter. 197 | 198 | After the _attribute_, a function is defined to handle the matching requests. The function's return data type must implement the `Responder` trait, that defines how a data type is transformed into a HTTP response. 199 | 200 | We have not had to implement `Responder` anywhere by ourselves because Rocket already implements it for a bunch of standard data types. 201 | 202 | #### Mounting the /loadavg endpoint 203 | 204 | Our handler is not yet available for clients. It must be _mounted_ first when the application starts. Then, the Rocket's Web server must be started in the `main` function of our project. It's a funny launch sequence. After the engine _ignition_, the `mount` function allows to provide a set of _routes_ by using the `routes!` macro. After all routes has been mounted, you can then _launch_ the _rocket_: 205 | 206 | fn main() { 207 | rocket::ignite() 208 | .mount("/", routes![loadavg]) 209 | .launch(); 210 | } 211 | 212 | Now we can run our application using `cargo run`: 213 | 214 | 🚀 Rocket has launched from http://localhost:8000... 215 | 216 | However, if we query the endpoint at `http://localhost:8000/loadavg`, we'll realize the content of the response is not in JSON yet. But this is going to change pretty soon. 217 | 218 | #### Serializing the response as JSON 219 | 220 | Ultimately, we need to make sure the response is properly formatted according to the JSON initial specification, and that the adequate headers are set for clients to be able to process the response properly. This may sound complicated, but Rocket provides tools to handle JSON easily in its `contrib` crate. The `rocket_contrib::JSON` data type allows us to wrap a _serializable_ data type and make it the handler output type, so it will handle all the specific details automatically. 221 | 222 | The `JSON` data type requires some additional dependencies to work. More specifically, it uses the `serde` crate, which is probably the most used for _serialization_ and _deserialization_ in Rust. Lets add the needed bits to our `[dependencies]` section so it looks as follows: 223 | 224 | [dependencies] 225 | libc = "*" 226 | rocket = "0.1.6" 227 | rocket_codegen = "0.1.6" 228 | rocket_contrib = { version = "0.1.6", features = ["json"] } 229 | serde = "0.8" 230 | serde_json = "0.8" 231 | serde_derive = "0.8" 232 | 233 | Then, we need to add the crates references at the top of our `src/main.rs` file: 234 | 235 | extern crate serde_json; 236 | #[macro_use] extern crate rocket_contrib; 237 | #[macro_use] extern crate serde_derive; 238 | 239 | use rocket_contrib::JSON; 240 | 241 | At this point, we only need to make sure that our response data type can be correctly serialized as JSON. Given that `LoadAvg` is pretty simple, and all of its fields can be easily translated to its JSON counterparts, we can use `#[derive()]` to automatically implement `serde`'s `Serialize` trait: 242 | 243 | #[derive(Serialize)] 244 | struct LoadAvg { 245 | last: f64, 246 | last5: f64, 247 | last15: f64 248 | } 249 | 250 | We've removed the `Debug` trait as well since we wont be using it anymore. 251 | 252 | By giving our data type the super power to be transformed to `JSON`, we can refactor our handler to return `rocket_contrib::JSON`: 253 | 254 | #[get("/loadavg")] 255 | fn loadavg() -> JSON { 256 | JSON(LoadAvg::new()) 257 | } 258 | 259 | Finally, we can run the application again using `cargo run` and check how the response for the `/loadavg` endpoint is correctly formatted. 260 | 261 | ## Final references 262 | 263 | * https://doc.rust-lang.org/stable/book/ 264 | * https://rocket.rs/guide/requests/#json 265 | * https://github.com/SergioBenitez/Rocket/tree/v0.1.6/examples/json 266 | -------------------------------------------------------------------------------- /README.es.md: -------------------------------------------------------------------------------- 1 | # Construyendo APIs de alto rendimiento con Rust y Rocket 2 | 3 | La intención de este proyecto es mostrar los fundamentos para la implementación de un API REST en el lenguaje de programación Rust, haciendo uso del Framework Web Rocket, así como resaltar algunos detalles en el camino. 4 | 5 | Este proyecto es ofrecido como taller asociado a una [introducción al desarrollo de APIs](http://slides.com/sebasmagri/construyendo-servicios-web-de-alto-rendimiento-con-rust-y-rocket). Sin embargo, en este documento se describe a mayor detalle su implementación. 6 | 7 | ## Objetivos 8 | 9 | El objetivo de este API será consultar la carga promedio de un sistema a través de un único endpoint. 10 | 11 | La carga promedio expresa qué tan ocupado ha estado un sistema *procesando* tareas, y se expresa generalmente en forma de 3 valores; para el último minuto, para los últimos 5 minutos y para los últimos 15 minutos. La magnitud de cada valor es una aproximación a la relación entre la capacidad de procesar tareas y la cantidad de tareas en procesamiento durante ese tiempo. 12 | 13 | Los clientes consultarán la carga del sistema con una solicitud `GET` a un endpoint `/loadavg`, y recibirán una respuesta en `JSON` con la siguiente forma: 14 | 15 | { 16 | 'last': 0.7, 17 | 'last5': 1.1, 18 | 'last15': 0.8 19 | } 20 | 21 | ## Preparación del ambiente de trabajo 22 | 23 | Rocket *aún* requiere el uso de la versión *nightly* o de desarrollo del compilador debido a que hace uso de algunas características del lenguaje que aún no están disponibles en las versiones estables. Afortunadamente, [rustup](https://rustup.rs/) hace que sea muy fácil instalar y manejar cualquier versión de Rust en nuestros ambientes de desarrollo. Para instalar Rust, ejecutamos el script oficial: 24 | 25 | $ curl https://sh.rustup.rs -sSf | sh 26 | 27 | Este método funciona para ambientes UNIX. Si estás trabajando en Windows puedes usar [otros métodos de instalación](https://github.com/rust-lang-nursery/rustup.rs/#other-installation-methods). 28 | 29 | `rustup` instala por defecto el toolchain estable de Rust. Por esta razón debemos instalar luego el toolchain *Nightly* con: 30 | 31 | $ rustup install nightly-2017-01-25 32 | 33 | Si tu aplicación en Rocket deja de funcionar después de actualizar las dependencias, es muy probable que necesites actualizar también el toolchain: 34 | 35 | $ rustup update 36 | 37 | ## Generación del nuevo proyecto 38 | 39 | En Rust, la herramienta utilizada para gestionar proyectos, dependencias y compilaciones se llama *Cargo*. Cargo es una herramienta que automatiza gran cantidad de tareas y es la que vas a estar utilizando más a menudo cuando estés trabajando con Rust. 40 | 41 | Para generar la estructura inicial de nuestra aplicación ejecutamos: 42 | 43 | $ cargo new loadavg-api --bin 44 | $ cd loadavg-api/ 45 | 46 | Ahora nos aseguramos de utilizar la versión nightly del compilador en nuestro proyecto 47 | 48 | $ rustup override set nightly-2017-01-25 49 | 50 | ## Instalación de Rocket 51 | 52 | Ahora que tenemos la estructura inicial de nuestro proyecto, añadimos a *Rocket* a las dependencias del mismo. Como se mencionó anteriormente, `cargo` es utilizado para gestionar las dependencias, y esto lo hace a través del archivo `Cargo.toml` que se encuentra en la raíz del proyecto. 53 | 54 | Dentro del archivo `Cargo.toml`, usamos la sección `[dependencies]` para definir qué *crates* utilizará nuestro proyecto. Por defecto, estos crates son descargados desde el repositorio central comunitario en [crates.io](https://crates.io/). Así, añadimos `rocket` y `rocket_codegen`. Este último incluye herramientas de generación automática de código que nos va a ahorrar una gran cantidad de trabajo al implementar nuestra API. 55 | 56 | [dependencies] 57 | rocket = "0.1.6" 58 | rocket_codegen = "0.1.6" 59 | 60 | La próxima vez que se ejecute `cargo build` o `cargo run`, él mismo se encargará de encontrar, descargar y construir las dependencias del proyecto. 61 | 62 | ## Implementación del API 63 | 64 | Ya con todo en sitio, podemos comenzar a implementar nuestra API. 65 | 66 | ### Modelado inicial 67 | 68 | Como paso inicial, vamos a modelar datos que nuestra aplicación manejará. Teniendo una fuerte base en la programación funcional, Rust hace uso de *tipos de datos* para este fin. 69 | 70 | #### Tipos de datos 71 | 72 | Rust permite definir datos tipados con características arbitrarias a través de `struct`s. De manera que, si queremos tener una abstracción de la carga promedio del sistema, o `Load Average`, podríamos modelarlo de la siguiente manera: 73 | 74 | #[derive(Debug)] 75 | struct LoadAvg { 76 | last: f64, // last minute load average 77 | last5: f64, // last 5 minutes load average 78 | last15: f64 // last 15 minutes load average 79 | } 80 | 81 | Estamos creando una estructura `LoadAvg` con 3 *campos*, cada uno de los cuales tiene tipo `f64`, que maneja números flotantes de 64 bits. Esta estructura es en si un nuevo tipo de datos que abstrae la carga promedio del sistema. Si observamos la especificación de la respuesta que esperan nuestros clientes, podemos darnos cuenta de que el tipo de datos `LoadAvg` es muy similar. 82 | 83 | Antes de la definición de nuestro `LoadAvg`, podemos encontrar `#[derive(Debug)]`. Ésta es una manera como Rust implementa un `trait`, que describe ciertos comportamientos de un tipo de datos. En este caso específico, y con fines de depuración, solo estamos indicando que queremos que nuestro tipo de datos se pueda imprimir usando el indicador de formato `{:?}`, que genera una representación del dato con detalles de sus campos. Así podemos hacer: 84 | 85 | println!("{:?}", load_avg); 86 | 87 | Y obtener algo así en la salida estándar: 88 | 89 | LoadAvg { last: 0.9, last5: 1.5, last15: 1.8 } 90 | 91 | Añadimos este nuevo tipo de datos al código de nuestra aplicación en `src/main.rs`, y continuamos. 92 | 93 | #### Comportamiento de un tipo de datos 94 | 95 | Las `struct`s en Rust no son, necesariamente, estructuras estáticas. Al contrario, estas permiten modelar el comportamiento de un dato a través de *métodos*, muy al estilo de las clases en los lenguajes de programación orientados a objetos. Para añadir métodos a un tipo de datos, utilizamos la palabra clave `impl`. 96 | 97 | Si queremos implementar un constructor para nuestro tipo `LoadAvg`, podemos hacerlo de la siguiente manera: 98 | 99 | impl LoadAvg { 100 | fn new() -> LoadAvg { 101 | // Placeholder 102 | LoadAvg { 103 | last: 0.9, 104 | last5: 1.5, 105 | last15: 1.8 106 | } 107 | } 108 | } 109 | 110 | En adelante, podemos utilizar este nuevo método para generar *instancias* de este tipo de datos. Podemos tener entonces en nuestra función `main` en `src/main.rs`: 111 | 112 | fn main() { 113 | let load_avg = LoadAvg::new(); 114 | println!("{:?}", load_avg); 115 | } 116 | 117 | ##### Obtención de la carga del sistema real 118 | 119 | Esta sección en particular no se implementa a detalle en el taller por limitaciones de tiempo, pero muestra como integrar funciones definidas en la librería estándar de C en nuestras aplicaciones. 120 | 121 | Hasta ahora, hemos utilizado valores fijos para los campos de nuestro tipo `LoadAvg`. Sin embargo, en condiciones reales, uno quisiera que `LoadAvg::new()` devolviera un valor real, con la carga del sistema al momento. 122 | 123 | La manera recomendada de obtener la carga del sistema es usando la función `getloadavg`, presente en la librería estándar de *C*, `libc`. Sin embargo, esta función está implementada en *C*, que no nos ofrece las garantías que nos ofrece Rust. Aún así, es muy sencillo integrarla en nuestro código Rust, señalando de manera explícita que es una función externa, e insegura. 124 | 125 | Antes que nada, debemos añadir una referencia a `libc` en nuestro proyecto. En el archivo `Cargo.toml` añadimos a la sección `[dependencies]`: 126 | 127 | libc = "*" 128 | 129 | Después de tener `libc` en las dependencias del proyecto, podemos hacer referencia a este *crate* en nuestro código fuente, al inicio de `src/main.rs`: 130 | 131 | extern crate libc; 132 | 133 | Esto nos permite utilizar cualquiera de las funciones definidas en el crate [libc](https://doc.rust-lang.org/libc/x86_64-unknown-linux-gnu/libc/) en nuestros proyectos. 134 | 135 | Si observamos la [firma de esta función en C](https://linux.die.net/man/3/getloadavg), podemos darnos cuenta de que el primer parámetro es un puntero a un arreglo de valores `double`, donde se almacenarán los valores de carga, y el segundo un valor `int`, para la longitud del arreglo anterior: 136 | 137 | # Esto es código C 138 | int getloadavg(double loadavg[], int nelem); 139 | 140 | Sin embargo, ni el `double` ni el `int` de C están presentes entre los tipos de datos primitivos de Rust, por lo cual tenemos que usar los tipos de datos definidos dentro de `libc` importándolos en nuestro código: 141 | 142 | use libc::{c_double, c_int}; 143 | 144 | Con todo en sitio, podemos hacer referencia a la función `getloadavg`: 145 | 146 | extern { 147 | fn getloadavg(load_avg: *mut c_double, load_avg_len: c_int); 148 | } 149 | 150 | Como podemos observar en la firma de la función, la misma toma como primer parámetro un puntero a un valor mutable de tipo `c_double`, que sería el primer elemento del arreglo requerido por la función en C, así como el indicador del número de elementos presente igualmente en la firma de la función original. 151 | 152 | Ahora podemos utilizar `getloadavg` para obtener los indicadores de carga promedio del sistema de la siguiente manera: 153 | 154 | let load_averages: [f64; 3] = unsafe { 155 | let mut lavgs: [c_double; 3] = [0f64, 0f64, 0f64]; 156 | getloadavg(lavgs.as_mut_ptr(), 3); 157 | lavgs 158 | }; 159 | 160 | De esta manera, nuestro método `LoadAvg::new` queda: 161 | 162 | fn new() -> LoadAvg { 163 | let load_averages: [f64; 3] = unsafe { 164 | let mut lavgs: [c_double; 3] = [0f64, 0f64, 0f64]; 165 | getloadavg(lavgs.as_mut_ptr(), 3); 166 | lavgs 167 | }; 168 | 169 | LoadAvg { 170 | last: load_averages[0], 171 | last5: load_averages[1], 172 | last15: load_averages[2] 173 | } 174 | } 175 | 176 | ### Implementación del API 177 | 178 | Hasta este punto, no hemos utilizado nada que tenga que ver con Rocket. Pero espera solo un poco, eso está a punto de cambiar. 179 | 180 | #### /loadavg 181 | 182 | De acuerdo con la especificación inicial, necesitamos un endpoint `/loadavg` que atenderá solicitudes `GET` y devolverá los promedios de carga en forma de JSON. 183 | 184 | Para este fin, Rocket asocia una ruta y un conjunto de condiciones de validación con una función que manejará los datos de entrada y generará una respuesta, o *handler*. Las validaciones se expresan a través de un *atributo* de la función que indica qué método, parámetros y restricciones tiene un endpoint específico. 185 | 186 | Teniendo esto en cuenta, el deber principal de nuestro *handler* para el endpoint `/loadavg` será crear una nueva instancia de `LoadAvg` y devolver su valor como JSON. 187 | 188 | En primer lugar, añadimos las referencias necesarias a nuestro archivo `src/main.rs` para utilizar las herramientas de Rocket. Al comienzo del archivo, añadimos algunas directivas para indicarle al compilador que utilice las características de generación de código así como la referencia al *crate* de Rocket. 189 | 190 | #![feature(plugin)] 191 | #![plugin(rocket_codegen)] 192 | 193 | extern crate rocket; 194 | 195 | A continuación, vamos a implementar el *handler* para el endpoint `/loadavg`. 196 | 197 | #[get("/loadavg")] 198 | fn loadavg() -> String { 199 | format!("{:?}", LoadAvg::new()) 200 | } 201 | 202 | La definición del handler contempla entonces un *atributo* que define el método, ruta y parámetros de un endpoint. En este caso `#[get("/loadavg")]` indica que el endpoint `/loadavg` responderá a solicitudes `GET` y que no toma ningún parámetro. 203 | 204 | Seguido, se define la *función* que manejará las solicitudes que coincidan con las condiciones definidas por el atributo. Esta función también tiene un tipo de datos de retorno, el cual debe implementar el _trait_ *Responder*, que no es más que una manera de indicar que el tipo de datos puede ser transformado en una respuesta HTTP. 205 | 206 | En este caso, se utiliza inicialmente el tipo de datos `String`. Rocket implementa el trait `Responder` por defecto para una buena cantidad de tipos de datos estándar de Rust, por lo que no es necesario que implementemos nada adicional. 207 | 208 | #### Montaje del endpoint /loadavg 209 | 210 | Para que el endpoint esté disponible para los clientes, el mismo debe _montarse_ al momento de iniciar la aplicación. Para este fin, el servidor Web de Rocket debe arrancar en la función `main` de nuestro proyecto. Esta es una secuencia divertida. Después de _encender_, la función `mount` nos permite pasar un conjunto de rutas a _montar_ con un prefijo específico generadas por la macro `routes`. Una vez se han montado las rutas, es posible _lanzar_ el _cohete_. 211 | 212 | fn main() { 213 | rocket::ignite() 214 | .mount("/", routes![loadavg]) 215 | .launch(); 216 | } 217 | 218 | En este punto ya podemos correr nuestra API usando `cargo run`: 219 | 220 | 🚀 Rocket has launched from http://localhost:8000... 221 | 222 | Sin embargo, al consultar el endpoint en `http://localhost:8000/loadavg`, podremos observar que la respuesta aún no está en JSON, sino como una representación del tipo `LoadAvg` como cadena de caracteres. Esto es debido al tipo de retorno de nuestro handler, y está a punto de cambiar. 223 | 224 | #### Serialización de la respuesta como JSON 225 | 226 | Por último, necesitamos formatear el cuerpo de la respuesta como JSON, y establecer las entradas adecuadas para indicarle a los clientes sobre este formato en las cabeceras de la misma. Aunque parezca algo complicado, Rocket ofrece herramientas para que esta tarea sea sumamente sencilla en su módulo _contrib_. Específicamente, el tipo de datos `rocket_contrib::JSON` nos permite _envolver_ un tipo de datos serializable y hacerlo directamente el valor de retorno del handler, manejando todos los detalles de conversión e información adicional en la respuesta HTTP. 227 | 228 | Como el tipo `JSON` en Rocket hace su trabajo sobre la base del _crate_ `serde`, posiblemente el más usado para fines de _serialización_ y _deserialización_ en Rust, primero debemos añadir algunas nuevas dependencias a nuestro `Cargo.toml` de manera que la sección `[dependencies]` quede de la siguiente forma: 229 | 230 | [dependencies] 231 | libc = "*" 232 | rocket = "0.1.6" 233 | rocket_codegen = "0.1.6" 234 | rocket_contrib = { version = "0.1.6", features = ["json"] } 235 | serde = "0.8" 236 | serde_json = "0.8" 237 | serde_derive = "0.8" 238 | 239 | Igualmente, debemos añadir las referencias a estos nuevos _crates_ en nuestro `src/main.rs`: 240 | 241 | extern crate serde_json; 242 | #[macro_use] extern crate rocket_contrib; 243 | #[macro_use] extern crate serde_derive; 244 | 245 | use rocket_contrib::JSON; 246 | 247 | En este punto, solo debemos asegurarnos de que nuestro tipo de datos de respuesta pueda ser correctamente serializado como `JSON`. Dado que `LoadAvg` es un tipo de datos simple, y que todos sus campos pueden ser convertidos fácilmente a su representación en `JSON`, podemos hacer uso del atributo `[derive()]` para implementar automáticamente el _trait_ o interfaz `Serialize` proveniente de `serde`. De tal manera que nuestro tipo de datos queda así: 248 | 249 | #[derive(Serialize)] 250 | struct LoadAvg { 251 | last: f64, 252 | last5: f64, 253 | last15: f64 254 | } 255 | 256 | Como se puede observar, se ha removido también el trait `Debug`, debido a que ya no se utilizará. 257 | 258 | Al garantizar que nuestro tipo de datos se puede expresar correctamente como `JSON`, podemos refactorizar el handler `loadavg` para utilizar el tipo de datos `rocket_contrib::JSON`, quedando de la siguiente manera: 259 | 260 | #[get("/loadavg")] 261 | fn loadavg() -> JSON { 262 | JSON(LoadAvg::new()) 263 | } 264 | 265 | Finalmente, podemos correr la aplicación de nuevo con `cargo run` y verificar que la respuesta del endpoint `/loadavg` está formateada de la manera esperada. 266 | 267 | ## Referencias finales 268 | 269 | * https://doc.rust-lang.org/stable/book/ 270 | * https://rocket.rs/guide/requests/#json 271 | * https://github.com/SergioBenitez/Rocket/tree/v0.1.6/examples/json 272 | -------------------------------------------------------------------------------- /Cargo.lock: -------------------------------------------------------------------------------- 1 | [root] 2 | name = "rocket-loadavg-api" 3 | version = "0.1.0" 4 | dependencies = [ 5 | "libc 0.2.23 (registry+https://github.com/rust-lang/crates.io-index)", 6 | "rocket 0.2.6 (registry+https://github.com/rust-lang/crates.io-index)", 7 | "rocket_codegen 0.2.6 (registry+https://github.com/rust-lang/crates.io-index)", 8 | "rocket_contrib 0.2.6 (registry+https://github.com/rust-lang/crates.io-index)", 9 | "serde 0.9.15 (registry+https://github.com/rust-lang/crates.io-index)", 10 | "serde_derive 0.9.15 (registry+https://github.com/rust-lang/crates.io-index)", 11 | "serde_json 0.9.10 (registry+https://github.com/rust-lang/crates.io-index)", 12 | ] 13 | 14 | [[package]] 15 | name = "ansi_term" 16 | version = "0.9.0" 17 | source = "registry+https://github.com/rust-lang/crates.io-index" 18 | 19 | [[package]] 20 | name = "base64" 21 | version = "0.5.2" 22 | source = "registry+https://github.com/rust-lang/crates.io-index" 23 | dependencies = [ 24 | "byteorder 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)", 25 | ] 26 | 27 | [[package]] 28 | name = "byteorder" 29 | version = "1.0.0" 30 | source = "registry+https://github.com/rust-lang/crates.io-index" 31 | 32 | [[package]] 33 | name = "cookie" 34 | version = "0.6.2" 35 | source = "registry+https://github.com/rust-lang/crates.io-index" 36 | dependencies = [ 37 | "time 0.1.37 (registry+https://github.com/rust-lang/crates.io-index)", 38 | "url 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)", 39 | ] 40 | 41 | [[package]] 42 | name = "dtoa" 43 | version = "0.4.1" 44 | source = "registry+https://github.com/rust-lang/crates.io-index" 45 | 46 | [[package]] 47 | name = "httparse" 48 | version = "1.2.2" 49 | source = "registry+https://github.com/rust-lang/crates.io-index" 50 | 51 | [[package]] 52 | name = "hyper" 53 | version = "0.10.10" 54 | source = "registry+https://github.com/rust-lang/crates.io-index" 55 | dependencies = [ 56 | "base64 0.5.2 (registry+https://github.com/rust-lang/crates.io-index)", 57 | "httparse 1.2.2 (registry+https://github.com/rust-lang/crates.io-index)", 58 | "language-tags 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)", 59 | "log 0.3.7 (registry+https://github.com/rust-lang/crates.io-index)", 60 | "mime 0.2.4 (registry+https://github.com/rust-lang/crates.io-index)", 61 | "num_cpus 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)", 62 | "rustc_version 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)", 63 | "time 0.1.37 (registry+https://github.com/rust-lang/crates.io-index)", 64 | "traitobject 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", 65 | "typeable 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)", 66 | "unicase 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)", 67 | "url 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)", 68 | ] 69 | 70 | [[package]] 71 | name = "idna" 72 | version = "0.1.1" 73 | source = "registry+https://github.com/rust-lang/crates.io-index" 74 | dependencies = [ 75 | "matches 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)", 76 | "unicode-bidi 0.2.6 (registry+https://github.com/rust-lang/crates.io-index)", 77 | "unicode-normalization 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)", 78 | ] 79 | 80 | [[package]] 81 | name = "itoa" 82 | version = "0.3.1" 83 | source = "registry+https://github.com/rust-lang/crates.io-index" 84 | 85 | [[package]] 86 | name = "kernel32-sys" 87 | version = "0.2.2" 88 | source = "registry+https://github.com/rust-lang/crates.io-index" 89 | dependencies = [ 90 | "winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)", 91 | "winapi-build 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", 92 | ] 93 | 94 | [[package]] 95 | name = "language-tags" 96 | version = "0.2.2" 97 | source = "registry+https://github.com/rust-lang/crates.io-index" 98 | 99 | [[package]] 100 | name = "libc" 101 | version = "0.2.23" 102 | source = "registry+https://github.com/rust-lang/crates.io-index" 103 | 104 | [[package]] 105 | name = "log" 106 | version = "0.3.7" 107 | source = "registry+https://github.com/rust-lang/crates.io-index" 108 | 109 | [[package]] 110 | name = "matches" 111 | version = "0.1.4" 112 | source = "registry+https://github.com/rust-lang/crates.io-index" 113 | 114 | [[package]] 115 | name = "memchr" 116 | version = "1.0.1" 117 | source = "registry+https://github.com/rust-lang/crates.io-index" 118 | dependencies = [ 119 | "libc 0.2.23 (registry+https://github.com/rust-lang/crates.io-index)", 120 | ] 121 | 122 | [[package]] 123 | name = "mime" 124 | version = "0.2.4" 125 | source = "registry+https://github.com/rust-lang/crates.io-index" 126 | dependencies = [ 127 | "log 0.3.7 (registry+https://github.com/rust-lang/crates.io-index)", 128 | ] 129 | 130 | [[package]] 131 | name = "num-traits" 132 | version = "0.1.37" 133 | source = "registry+https://github.com/rust-lang/crates.io-index" 134 | 135 | [[package]] 136 | name = "num_cpus" 137 | version = "1.4.0" 138 | source = "registry+https://github.com/rust-lang/crates.io-index" 139 | dependencies = [ 140 | "libc 0.2.23 (registry+https://github.com/rust-lang/crates.io-index)", 141 | ] 142 | 143 | [[package]] 144 | name = "quote" 145 | version = "0.3.15" 146 | source = "registry+https://github.com/rust-lang/crates.io-index" 147 | 148 | [[package]] 149 | name = "redox_syscall" 150 | version = "0.1.17" 151 | source = "registry+https://github.com/rust-lang/crates.io-index" 152 | 153 | [[package]] 154 | name = "rocket" 155 | version = "0.2.6" 156 | source = "registry+https://github.com/rust-lang/crates.io-index" 157 | dependencies = [ 158 | "ansi_term 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)", 159 | "cookie 0.6.2 (registry+https://github.com/rust-lang/crates.io-index)", 160 | "hyper 0.10.10 (registry+https://github.com/rust-lang/crates.io-index)", 161 | "log 0.3.7 (registry+https://github.com/rust-lang/crates.io-index)", 162 | "memchr 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)", 163 | "num_cpus 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)", 164 | "state 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)", 165 | "term-painter 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)", 166 | "time 0.1.37 (registry+https://github.com/rust-lang/crates.io-index)", 167 | "toml 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)", 168 | "url 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)", 169 | "version_check 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", 170 | ] 171 | 172 | [[package]] 173 | name = "rocket_codegen" 174 | version = "0.2.6" 175 | source = "registry+https://github.com/rust-lang/crates.io-index" 176 | dependencies = [ 177 | "ansi_term 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)", 178 | "log 0.3.7 (registry+https://github.com/rust-lang/crates.io-index)", 179 | "rocket 0.2.6 (registry+https://github.com/rust-lang/crates.io-index)", 180 | "version_check 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", 181 | ] 182 | 183 | [[package]] 184 | name = "rocket_contrib" 185 | version = "0.2.6" 186 | source = "registry+https://github.com/rust-lang/crates.io-index" 187 | dependencies = [ 188 | "log 0.3.7 (registry+https://github.com/rust-lang/crates.io-index)", 189 | "rocket 0.2.6 (registry+https://github.com/rust-lang/crates.io-index)", 190 | "serde 0.9.15 (registry+https://github.com/rust-lang/crates.io-index)", 191 | "serde_json 0.9.10 (registry+https://github.com/rust-lang/crates.io-index)", 192 | ] 193 | 194 | [[package]] 195 | name = "rustc_version" 196 | version = "0.1.7" 197 | source = "registry+https://github.com/rust-lang/crates.io-index" 198 | dependencies = [ 199 | "semver 0.1.20 (registry+https://github.com/rust-lang/crates.io-index)", 200 | ] 201 | 202 | [[package]] 203 | name = "semver" 204 | version = "0.1.20" 205 | source = "registry+https://github.com/rust-lang/crates.io-index" 206 | 207 | [[package]] 208 | name = "serde" 209 | version = "0.9.15" 210 | source = "registry+https://github.com/rust-lang/crates.io-index" 211 | 212 | [[package]] 213 | name = "serde_codegen_internals" 214 | version = "0.14.2" 215 | source = "registry+https://github.com/rust-lang/crates.io-index" 216 | dependencies = [ 217 | "syn 0.11.11 (registry+https://github.com/rust-lang/crates.io-index)", 218 | ] 219 | 220 | [[package]] 221 | name = "serde_derive" 222 | version = "0.9.15" 223 | source = "registry+https://github.com/rust-lang/crates.io-index" 224 | dependencies = [ 225 | "quote 0.3.15 (registry+https://github.com/rust-lang/crates.io-index)", 226 | "serde_codegen_internals 0.14.2 (registry+https://github.com/rust-lang/crates.io-index)", 227 | "syn 0.11.11 (registry+https://github.com/rust-lang/crates.io-index)", 228 | ] 229 | 230 | [[package]] 231 | name = "serde_json" 232 | version = "0.9.10" 233 | source = "registry+https://github.com/rust-lang/crates.io-index" 234 | dependencies = [ 235 | "dtoa 0.4.1 (registry+https://github.com/rust-lang/crates.io-index)", 236 | "itoa 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", 237 | "num-traits 0.1.37 (registry+https://github.com/rust-lang/crates.io-index)", 238 | "serde 0.9.15 (registry+https://github.com/rust-lang/crates.io-index)", 239 | ] 240 | 241 | [[package]] 242 | name = "state" 243 | version = "0.2.1" 244 | source = "registry+https://github.com/rust-lang/crates.io-index" 245 | 246 | [[package]] 247 | name = "syn" 248 | version = "0.11.11" 249 | source = "registry+https://github.com/rust-lang/crates.io-index" 250 | dependencies = [ 251 | "quote 0.3.15 (registry+https://github.com/rust-lang/crates.io-index)", 252 | "synom 0.11.3 (registry+https://github.com/rust-lang/crates.io-index)", 253 | "unicode-xid 0.0.4 (registry+https://github.com/rust-lang/crates.io-index)", 254 | ] 255 | 256 | [[package]] 257 | name = "synom" 258 | version = "0.11.3" 259 | source = "registry+https://github.com/rust-lang/crates.io-index" 260 | dependencies = [ 261 | "unicode-xid 0.0.4 (registry+https://github.com/rust-lang/crates.io-index)", 262 | ] 263 | 264 | [[package]] 265 | name = "term" 266 | version = "0.4.5" 267 | source = "registry+https://github.com/rust-lang/crates.io-index" 268 | dependencies = [ 269 | "kernel32-sys 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)", 270 | "winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)", 271 | ] 272 | 273 | [[package]] 274 | name = "term-painter" 275 | version = "0.2.3" 276 | source = "registry+https://github.com/rust-lang/crates.io-index" 277 | dependencies = [ 278 | "term 0.4.5 (registry+https://github.com/rust-lang/crates.io-index)", 279 | ] 280 | 281 | [[package]] 282 | name = "time" 283 | version = "0.1.37" 284 | source = "registry+https://github.com/rust-lang/crates.io-index" 285 | dependencies = [ 286 | "kernel32-sys 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)", 287 | "libc 0.2.23 (registry+https://github.com/rust-lang/crates.io-index)", 288 | "redox_syscall 0.1.17 (registry+https://github.com/rust-lang/crates.io-index)", 289 | "winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)", 290 | ] 291 | 292 | [[package]] 293 | name = "toml" 294 | version = "0.2.1" 295 | source = "registry+https://github.com/rust-lang/crates.io-index" 296 | 297 | [[package]] 298 | name = "traitobject" 299 | version = "0.1.0" 300 | source = "registry+https://github.com/rust-lang/crates.io-index" 301 | 302 | [[package]] 303 | name = "typeable" 304 | version = "0.1.2" 305 | source = "registry+https://github.com/rust-lang/crates.io-index" 306 | 307 | [[package]] 308 | name = "unicase" 309 | version = "1.4.0" 310 | source = "registry+https://github.com/rust-lang/crates.io-index" 311 | dependencies = [ 312 | "rustc_version 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)", 313 | ] 314 | 315 | [[package]] 316 | name = "unicode-bidi" 317 | version = "0.2.6" 318 | source = "registry+https://github.com/rust-lang/crates.io-index" 319 | dependencies = [ 320 | "matches 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)", 321 | ] 322 | 323 | [[package]] 324 | name = "unicode-normalization" 325 | version = "0.1.4" 326 | source = "registry+https://github.com/rust-lang/crates.io-index" 327 | 328 | [[package]] 329 | name = "unicode-xid" 330 | version = "0.0.4" 331 | source = "registry+https://github.com/rust-lang/crates.io-index" 332 | 333 | [[package]] 334 | name = "url" 335 | version = "1.4.0" 336 | source = "registry+https://github.com/rust-lang/crates.io-index" 337 | dependencies = [ 338 | "idna 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", 339 | "matches 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)", 340 | ] 341 | 342 | [[package]] 343 | name = "version_check" 344 | version = "0.1.0" 345 | source = "registry+https://github.com/rust-lang/crates.io-index" 346 | 347 | [[package]] 348 | name = "winapi" 349 | version = "0.2.8" 350 | source = "registry+https://github.com/rust-lang/crates.io-index" 351 | 352 | [[package]] 353 | name = "winapi-build" 354 | version = "0.1.1" 355 | source = "registry+https://github.com/rust-lang/crates.io-index" 356 | 357 | [metadata] 358 | "checksum ansi_term 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)" = "23ac7c30002a5accbf7e8987d0632fa6de155b7c3d39d0067317a391e00a2ef6" 359 | "checksum base64 0.5.2 (registry+https://github.com/rust-lang/crates.io-index)" = "30e93c03064e7590d0466209155251b90c22e37fab1daf2771582598b5827557" 360 | "checksum byteorder 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "c40977b0ee6b9885c9013cd41d9feffdd22deb3bb4dc3a71d901cc7a77de18c8" 361 | "checksum cookie 0.6.2 (registry+https://github.com/rust-lang/crates.io-index)" = "30b3493e12a550c2f96be785088d1da8d93189e7237c8a8d0d871bc9070334c3" 362 | "checksum dtoa 0.4.1 (registry+https://github.com/rust-lang/crates.io-index)" = "80c8b71fd71146990a9742fc06dcbbde19161a267e0ad4e572c35162f4578c90" 363 | "checksum httparse 1.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "77f756bed9ee3a83ce98774f4155b42a31b787029013f3a7d83eca714e500e21" 364 | "checksum hyper 0.10.10 (registry+https://github.com/rust-lang/crates.io-index)" = "36e108e0b1fa2d17491cbaac4bc460dc0956029d10ccf83c913dd0e5db3e7f07" 365 | "checksum idna 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "6ac85ec3f80c8e4e99d9325521337e14ec7555c458a14e377d189659a427f375" 366 | "checksum itoa 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "eb2f404fbc66fd9aac13e998248505e7ecb2ad8e44ab6388684c5fb11c6c251c" 367 | "checksum kernel32-sys 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "7507624b29483431c0ba2d82aece8ca6cdba9382bff4ddd0f7490560c056098d" 368 | "checksum language-tags 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "a91d884b6667cd606bb5a69aa0c99ba811a115fc68915e7056ec08a46e93199a" 369 | "checksum libc 0.2.23 (registry+https://github.com/rust-lang/crates.io-index)" = "e7eb6b826bfc1fdea7935d46556250d1799b7fe2d9f7951071f4291710665e3e" 370 | "checksum log 0.3.7 (registry+https://github.com/rust-lang/crates.io-index)" = "5141eca02775a762cc6cd564d8d2c50f67c0ea3a372cbf1c51592b3e029e10ad" 371 | "checksum matches 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)" = "efd7622e3022e1a6eaa602c4cea8912254e5582c9c692e9167714182244801b1" 372 | "checksum memchr 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)" = "1dbccc0e46f1ea47b9f17e6d67c5a96bd27030519c519c9c91327e31275a47b4" 373 | "checksum mime 0.2.4 (registry+https://github.com/rust-lang/crates.io-index)" = "9d69889cdc6336ed56b174514ce876c4c3dc564cc23dd872e7bca589bb2a36c8" 374 | "checksum num-traits 0.1.37 (registry+https://github.com/rust-lang/crates.io-index)" = "e1cbfa3781f3fe73dc05321bed52a06d2d491eaa764c52335cf4399f046ece99" 375 | "checksum num_cpus 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ca313f1862c7ec3e0dfe8ace9fa91b1d9cb5c84ace3d00f5ec4216238e93c167" 376 | "checksum quote 0.3.15 (registry+https://github.com/rust-lang/crates.io-index)" = "7a6e920b65c65f10b2ae65c831a81a073a89edd28c7cce89475bff467ab4167a" 377 | "checksum redox_syscall 0.1.17 (registry+https://github.com/rust-lang/crates.io-index)" = "29dbdfd4b9df8ab31dec47c6087b7b13cbf4a776f335e4de8efba8288dda075b" 378 | "checksum rocket 0.2.6 (registry+https://github.com/rust-lang/crates.io-index)" = "ee95097db80eac85e071dd44f5a842f8e40ef40bef83bd72d320710e3efea9a2" 379 | "checksum rocket_codegen 0.2.6 (registry+https://github.com/rust-lang/crates.io-index)" = "060ea4d2c0af966c73420c3a64b5b8814a16247dc7e16e815e6ff5b4dfb01ba2" 380 | "checksum rocket_contrib 0.2.6 (registry+https://github.com/rust-lang/crates.io-index)" = "6cf1c3677525e17abc1e7564e294dee079f2725a6e5886c807bd4e9732b4b316" 381 | "checksum rustc_version 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)" = "c5f5376ea5e30ce23c03eb77cbe4962b988deead10910c372b226388b594c084" 382 | "checksum semver 0.1.20 (registry+https://github.com/rust-lang/crates.io-index)" = "d4f410fedcf71af0345d7607d246e7ad15faaadd49d240ee3b24e5dc21a820ac" 383 | "checksum serde 0.9.15 (registry+https://github.com/rust-lang/crates.io-index)" = "34b623917345a631dc9608d5194cc206b3fe6c3554cd1c75b937e55e285254af" 384 | "checksum serde_codegen_internals 0.14.2 (registry+https://github.com/rust-lang/crates.io-index)" = "bc888bd283bd2420b16ad0d860e35ad8acb21941180a83a189bb2046f9d00400" 385 | "checksum serde_derive 0.9.15 (registry+https://github.com/rust-lang/crates.io-index)" = "978fd866f4d4872084a81ccc35e275158351d3b9fe620074e7d7504b816b74ba" 386 | "checksum serde_json 0.9.10 (registry+https://github.com/rust-lang/crates.io-index)" = "ad8bcf487be7d2e15d3d543f04312de991d631cfe1b43ea0ade69e6a8a5b16a1" 387 | "checksum state 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "70c8d8cb4ad5c5e81546ccc23bf48f4c8100568a9756f1a5f0ea7d1641576a0a" 388 | "checksum syn 0.11.11 (registry+https://github.com/rust-lang/crates.io-index)" = "d3b891b9015c88c576343b9b3e41c2c11a51c219ef067b264bd9c8aa9b441dad" 389 | "checksum synom 0.11.3 (registry+https://github.com/rust-lang/crates.io-index)" = "a393066ed9010ebaed60b9eafa373d4b1baac186dd7e008555b0f702b51945b6" 390 | "checksum term 0.4.5 (registry+https://github.com/rust-lang/crates.io-index)" = "d168af3930b369cfe245132550579d47dfd873d69470755a19c2c6568dbbd989" 391 | "checksum term-painter 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)" = "ab900bf2f05175932b13d4fc12f8ff09ef777715b04998791ab2c930841e496b" 392 | "checksum time 0.1.37 (registry+https://github.com/rust-lang/crates.io-index)" = "ffd7ccbf969a892bf83f1e441126968a07a3941c24ff522a26af9f9f4585d1a3" 393 | "checksum toml 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "736b60249cb25337bc196faa43ee12c705e426f3d55c214d73a4e7be06f92cb4" 394 | "checksum traitobject 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "efd1f82c56340fdf16f2a953d7bda4f8fdffba13d93b00844c25572110b26079" 395 | "checksum typeable 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "1410f6f91f21d1612654e7cc69193b0334f909dcf2c790c4826254fbb86f8887" 396 | "checksum unicase 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "13a5906ca2b98c799f4b1ab4557b76367ebd6ae5ef14930ec841c74aed5f3764" 397 | "checksum unicode-bidi 0.2.6 (registry+https://github.com/rust-lang/crates.io-index)" = "149319afc0ec718611d4a9208c0308e3b1b62dcfbd982e5e723f6ec35b909b92" 398 | "checksum unicode-normalization 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)" = "e28fa37426fceeb5cf8f41ee273faa7c82c47dc8fba5853402841e665fcd86ff" 399 | "checksum unicode-xid 0.0.4 (registry+https://github.com/rust-lang/crates.io-index)" = "8c1f860d7d29cf02cb2f3f359fd35991af3d30bac52c57d265a3c461074cb4dc" 400 | "checksum url 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "f5ba8a749fb4479b043733416c244fa9d1d3af3d7c23804944651c8a448cb87e" 401 | "checksum version_check 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "f2edadb5308f31d97771a678c33df28f18d04f61de3fe21b9452f37854eb6c08" 402 | "checksum winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)" = "167dc9d6949a9b857f3451275e911c3f44255842c1f7a76f33c55103a909087a" 403 | "checksum winapi-build 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "2d315eee3b34aca4797b2da6b13ed88266e6d612562a0c46390af8299fc699bc" 404 | --------------------------------------------------------------------------------