📜 ⬆️ ⬇️

Introducing Iron: Melt Ore on Rust


Iron is a high-level web framework written in the Rust programming language and built on the basis of another well-known hyper library. Iron is designed to take advantage of all the benefits that Rust gives us.


Philosophy


Iron is built on the principle of extensibility as much as possible.
He introduces concepts for expanding his own functional:



With the basic part of the modifiers and intermediate types you will get acquainted in the course of the article.


Iron Official Website


Project creation


First, create a project using Cargo, using the command:


cargo new rust-iron-tutorial --bin 

Next, add the dependence iron = "0.4.0" to the [dependencies] section of the Cargo.toml file.


Writing the first program using Iron


Let's write the first simple program on Rust using Iron, which will respond to any requests on port 3000 with the text "Hello habrahabr!".


 extern crate iron; use iron::prelude::*; use iron::status; fn main() { Iron::new(|_: &mut Request| { Ok(Response::with((status::Ok, "Hello habrahabr!\n"))) }).http("localhost:3000").unwrap(); } 

Run the code using the cargo run command and after the compilation is complete and the program starts, test the service, for example, using curl:


 [loomaclin@loomaclin ~]$ curl localhost:3000 Hello habrahabr! 

Let's analyze the program to understand what is happening here. The first line of the program imports the iron package.
In the second line, a prelude module was connected, containing a set of the most important types, such as Request ,
Response , IronRequest , IronResult , IronError and Iron . The third line connects the status module, which contains lists of codes for answering requests. Iron::new creates a new Iron::new instance, which, in turn, is the base object of your server. It takes a parameter object that implements the type of Handler . In our case, we pass a closure whose argument is a mutable reference to the transmitted request.


Specify mime-type in the response header


Most often, when building web services (SOAP, REST), it is required to send answers indicating the type of content they contain. For this, Iron provides special tools.


Perform the following.


We connect the corresponding structure:


 use iron::mime::Mime; 

Bind the name content_type , which will store the parsed value of the following type using the connected Mime type:


 let content_type = "application/json".parse::<Mime>().unwrap(); 

Modify the query response line as follows:


 Ok(Response::with((content_type, status::Ok, "{}"))) 

Run the program and check the performance:


 [loomaclin@loomaclin ~]$ curl -v localhost:3000 * Rebuilt URL to: localhost:3000/ * Trying ::1... * Connected to localhost (::1) port 3000 (#0) > GET / HTTP/1.1 > Host: localhost:3000 > User-Agent: curl/7.49.1 > Accept: */* > < HTTP/1.1 200 OK < Content-Type: application/json < Date: Tue, 12 Jul 2016 19:53:21 GMT < Content-Length: 2 < * Connection #0 to host localhost left intact {} 

Manage response status codes


In the StatusCode enumeration, located in the status module, various status codes are located. Let's use this and return the "client" error 404 - NotFound , changing the line to form the answer to the request:


 Ok(Response::with((content_type, status::NotFound))) 

Check:


 [loomaclin@loomaclin ~]$ curl -v localhost:3000 * Rebuilt URL to: localhost:3000/ * Trying ::1... * Connected to localhost (::1) port 3000 (#0) > GET / HTTP/1.1 > Host: localhost:3000 > User-Agent: curl/7.49.1 > Accept: */* > < HTTP/1.1 404 Not Found < Content-Length: 2 < Content-Type: application/json < Date: Tue, 12 Jul 2016 20:55:40 GMT < * Connection #0 to host localhost left intact 

Note: in fact, the entire status module is a wrapper for the corresponding enumerations in the hyper library on which iron is based.


Redirect requests


For redirect to iron , the Redirect structure from the modifiers module is used (not to be confused with modifier ). It consists of a target URL, where it will be necessary to redirect.
Let's try to apply it by making the following changes:


Connect the Redirect structure:


 use iron::modifiers::Redirect; 

Add the Url module connection to the status module connection:


 use iron::{Url, status}; 

Bind the name of the url , which will store the parsed value of the redirect address:


 let url = Url::parse("https://habrahabr.ru/").unwrap(); 

Change the initialization block Iron as follows:


  Iron::new(move |_: &mut Request | { Ok(Response::with((status::Found, Redirect(url.clone())))) }).http("localhost:3000").unwrap(); 

Check the result:


 [loomaclin@loomaclin ~]$ curl -v localhost:3000 * Rebuilt URL to: localhost:3000/ * Trying ::1... * Connected to localhost (::1) port 3000 (#0) > GET / HTTP/1.1 > Host: localhost:3000 > User-Agent: curl/7.49.1 > Accept: */* > < HTTP/1.1 302 Found < Location: https://habrahabr.ru/ < Date: Tue, 12 Jul 2016 21:39:24 GMT < Content-Length: 0 < * Connection #0 to host localhost left intact 

You can also use another RedirectRaw structure from the modifiers module, which requires only a string to be constructed.


Work with type http-request


The Request structure has a method field that allows you to determine the type of the incoming HTTP request.
We will write a service that will save the data transmitted in the request body with the type Put ,
read data from a file and transmit it in response to a request of type Get .


We annotate the imported iron container with the macro_use attribute in order to use the iexpect and itry to handle error situations in the future:


 #[macro_use] extern crate iron; 

We connect modules for working with the file system and input / output from the standard library:


 use std::io; use std::fs; 

We connect the method module, which contains the list of http request types:


 use iron::method; 

We change the initialization block Iron in such a way as to associate the received request with the name req :


 Iron::new(|req: &mut Request| { ... ... ... }.http("localhost:3000").unwrap(); 

We add to the processing of the request a pattern matching the method field for two types of Get and Put requests, and for the rest we will use the answer in the form of the BadRequest status code:


  Ok(match req.method { method::Get => { let f = iexpect!(fs::File::open("foo.txt").ok(), (status::Ok, "")); Response::with((status::Ok, f)) }, method::Put => { let mut f = itry!(fs::File::create("foo.txt")); itry!(io::copy(&mut req.body, &mut f)); Response::with(status::Created) }, _ => Response::with(status::BadRequest) } 

In Iron iexcept macro iexcept used to expand an Option object passed to it, and if Option contains None macro returns Ok(Response::new()) with the default modifier status::BadRequest .
The itry macro itry used to wrap an error in IronError .


We try to run and test performance.


PUT:


 [loomaclin@loomaclin ~]$ curl -X PUT -d my_file_content localhost:3000 [loomaclin@loomaclin ~]$ cat ~/IdeaProjects/cycle/foo.txt my_file_content 

GET:


 [loomaclin@loomaclin ~]$ curl localhost:3000 my_file_content 

POST:


 [loomaclin@loomaclin ~]$ curl -X POST -v localhost:3000 * Rebuilt URL to: localhost:3000/ * Trying ::1... * Connected to localhost (::1) port 3000 (#0) > POST / HTTP/1.1 > Host: localhost:3000 > User-Agent: curl/7.49.1 > Accept: */* > < HTTP/1.1 400 Bad Request < Content-Length: 0 < Date: Tue, 12 Jul 2016 22:29:58 GMT < * Connection #0 to host localhost left intact 

Implementing end-to-end functionality using pre- and post-processing


In iron there are also types of BeforeMiddleware , AfterMiddleware and AroundMiddleware for AroundMiddleware -through functionality,
allowing to implement the logic for processing the request before it started in the main handler, and after it ended there.


Let's write an example of using AOP'o similar specified types:


Sample code
 extern crate iron; use iron::prelude::*; use iron::{BeforeMiddleware, AfterMiddleware, AroundMiddleware, Handler}; struct SampleStruct; struct SampleStructAroundHandler<H:Handler> { logger: SampleStruct, handler: H} impl BeforeMiddleware for SampleStruct { fn before(&self, req: &mut Request) -> IronResult<()> { println!("  ."); Ok(()) } } impl AfterMiddleware for SampleStruct { fn after(&self, req: &mut Request, res: Response) -> IronResult<Response> { println!("  ."); Ok(res) } } impl<H: Handler> Handler for SampleStructAroundHandler<H> { fn handle(&self, req: &mut Request) -> IronResult<Response> { println!("     ."); let res = self.handler.handle(req); res } } impl AroundMiddleware for SampleStruct { fn around(self, handler: Box<Handler>) -> Box<Handler> { Box::new(SampleStructAroundHandler { logger: self, handler: handler }) as Box<Handler> } } fn sample_of_middlewares(_:&mut Request) -> IronResult<Response> { println!("   ."); Ok(Response::with((iron::status::Ok, ",    !"))) } fn main() { let mut chain = Chain::new(sample_of_middlewares); chain.link_before(SampleStruct); chain.link_after(SampleStruct); chain.link_around(SampleStruct); Iron::new(chain).http("localhost:3000").unwrap(); } 

This example introduces the SampleStruct, structure for which the BeforeMiddleware types with the before function and AfterMiddleware with the after function are implemented. With their help, all end-to-end logic can be implemented. The AroundMiddleware type AroundMiddleware used in conjunction with the Handler type to add an additional handler. Add all implemented
handlers in the request processing life cycle is performed using a special type of Chain , which allows you to form a chain of calls to pre and post handlers.


Test the program.
In the console:


 [loomaclin@loomaclin ~]$ curl localhost:3000 ,    ! 

In the output of the program:


  Running `target/debug/cycle`   .      .    .   . 

Routing


What server API can do without routing? Add it =) Modify our basic example from the beginning of the article as follows.


We connect the collection from the standard library:


 use std::collections:HashMap; 

Let's declare the structure for storing the collection “path-handler” and describe for this structure the constructor, which will initialize this collection, and the function to add new routes to the collection with their handlers:


 struct Router { routes: HashMap<String, Box<Handler>> } impl Router { fn new() -> Self { Router { routes: HashMap::new() } } fn add_route<H>(&mut self, path: String, handler: H) where H: Handler { self.routes.insert(path, Box::new(handler)); } } 

To use our structure in conjunction with Iron you need to implement for it the type Handler with the function handle :


 impl Handler for Router { fn handle(&self, req: &mut Request) -> IronResult<Response> { match self.routes.get(&req.url.path().join("/")) { Some(handler) => handler.handle(req), None => Ok(Response::with(status::NotFound)) } } } 

In the handle function, by the path passed in the request, we find the corresponding handler in the collection and call the handler of this path with the transfer of the request to it. If the path passed in the request is not "registered" in the collection, the response with the NotFound error code is NotFound .


The last thing to do is to initialize our router and register in it the paths we need with their handlers:


 fn main() { let mut router = Router::new(); router.add_route("hello_habrahabr".to_string(), |_: &mut Request| { Ok(Response::with((status::Ok, "Hello Loo Maclin!\n"))) }); router.add_route("hello_habrahabr/again".to_string(), |_: &mut Request| { Ok(Response::with((status::Ok, " !\n"))) }); router.add_route("error".to_string(), |_: &mut Request| { Ok(Response::with(status::BadRequest)) }); ... 

Adding new paths occurs by calling the function implemented above.
Initialize the Iron instance using our router:


 Iron::new(router).http("localhost:3000").unwrap(); 

Testing:


 [loomaclin@loomaclin ~]$ curl localhost:3000/hello_habrahabr Hello Loo Maclin! [loomaclin@loomaclin ~]$ curl localhost:3000/hello_habrahabr/again  ! [loomaclin@loomaclin ~]$ curl -v localhost:3000/error * Trying ::1... * Connected to localhost (::1) port 3000 (#0) > GET /error HTTP/1.1 > Host: localhost:3000 > User-Agent: curl/7.49.1 > Accept: */* > < HTTP/1.1 400 Bad Request < Date: Wed, 13 Jul 2016 21:29:20 GMT < Content-Length: 0 < * Connection #0 to host localhost left intact 

Conclusion


This article comes to an end.
In addition to the considered functionality, Iron takes out most of the typical web framework functionality for the base extensions:



The article is intended for basic acquaintance with Iron, and I would like to hope that it copes with this goal.


Thanks for attention!


')

Source: https://habr.com/ru/post/305714/


All Articles