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.
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.
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.
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.
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 {}
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.
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.
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
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:
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` . . . .
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
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