Handler implementation

Every handler in Gotham has to return the HandlerFuture implementation of a tuple that can be converted to HandlerFuture. Also, a handler has to accept a State parameter:

fn register_user_agent(state: State) -> Box<HandlerFuture> {
// Implementation
}

If you remember, we need to extract the User-Agent header from a request. We can do this using a State value, because we can borrow HeaderMap from a State with the borrow_from method call. It returns a map that we can use to get the User-Agent HTTP header by using the USER_AGENT key imported from the hyper crate:

let user_agent = HeaderMap::borrow_from(&state)
.get(USER_AGENT)
.map(|value| value.to_str().unwrap().to_string())
.unwrap_or_else(|| "<undefined>".into());

HeaderMap returns HeaderValue as a value of header and we have to get the string value using the to_str method and convert it to an owned string with the to_string method. If the header was not provided, we use the "<undefined>" value.

Now we can borrow the ConnState value from State and add a new record to the database:

let conn = ConnState::borrow_from(&state);
let client_1 = conn.client.clone();
let client_2 = conn.client.clone();

let res = future::ok(())
.and_then(move |_| {
let mut client = client_1.lock().unwrap();
client.prepare("INSERT INTO agents (agent) VALUES ($1)
RETURNING agent")
})
.and_then(move |statement| {
let mut client = client_2.lock().unwrap();
client.query(&statement, &[&user_agent]).collect().map(|rows| {
rows[0].get::<_, String>(0)
})
})
.then(|res| {
let mut builder = Response::builder();
let body = {
match res {
Ok(value) => {
let value = format!("User-Agent: {}", value);
builder.status(StatusCode::OK);
value.into()
}
Err(err) => {
builder.status(StatusCode::INTERNAL_SERVER_ERROR);
err.to_string().into()
}
}
};
let response = builder.body(body).unwrap();
Ok((state, response))
});
Box::new(res)

We need two references to a Client, because we have to resolve two futures: one is to prepare a query, the second to execute that query. To prepare a query, we will use the prepare method, which expects a string with a SQL statement. The method call returns a Future instance that returns a Statement instance, but we can't create that Future directly in the function's body, because we have to lock Mutex to get access to the Client and it will be blocked after the Future statement is resolved.

To use Client twice, we need two references to the Client and use them in separate closures in a chain of futures. We start creating a futures chain with the future::ok method call, which returns a successful Future. We use the and_then method to add the first step: statement preparation. We lock the Mutex to get a mutable reference to a Client. Then, we call the prepare method to create a Future that returns a Statement.

Beyond that, we can add the next step to the futures chain to fill a Statement with values. We lock the second Mutex clone to the call query method of a Client. The method expects a statement as a first parameter and a reference to an array with references to values. Since we know that the statement we're using inserts a new record and returns exactly one row, we extract a String value from the first position of the first row.

At the end of the chain, we then use the method to convert a Result of the query execution into a Response. We create a new Builder for a Response. If the query returns a successful result, we return it to a client. If the query fails, we print an error with the 500 status code. The closure returns a tuple with a pair: the State and Response instances. Gotham uses this result to return the response to the client.

The implementation is finished and now we can check it with a database instance.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.141.2.157