How to use an extractor with from_request - rust

I have the following route handler, which sends a 400 BAD REQUEST in case of parse error in FormData.
#[post("/newsletter")]
pub async fn publish_newsletter(
form: web::Form<FormData>,
...
) -> Result<HttpResponse, PublishError> {
...
}
To provide better UX, I want to opt out of this behavior. I'd like to redirect the user to the same page and display the error as a flash message.
But I can't seem to figure out how to extract FormData using Form::from_request
I have tried using HttpRequest and web::dev::Payload extractors:
#[post("/newsletter")]
pub async fn publish_newsletter(
...
req: HttpRequest,
mut payload: dev::Payload,
) -> Result<HttpResponse, PublishError> {
let form: web::Form<FormData> = match web::Form::from_request(&req, &mut payload).await {
Ok(data) => data,
Err(e) => {
// send flash message
// redirect to same page
return Ok(see_other("/newsletter"));
}
};
...
}
But ultimately I'm faced with this error:
the trait bound `actix_web::dev::Payload: FromRequest` is not satisfied

I've solved it in the following way:
#[post("/newsletter")]
pub async fn publish_newsletter(
form: Result<web::Form<FormData>, actix_web::Error>,
...
) -> Result<HttpResponse, PublishError> {
let form = match form {
Ok(form) => form,
Err(e) => return Ok(send_flash_message_and_redirect(e, "/newsletter")),
};
...
}
Shoutout to the helpful folks at Actix Web Discord.

Related

Borrowed value does not live long enough when using async/await

I am trying to make two async api call and eventually get error E0597.
Here is a code:
async fn make_request() -> Result<()> {
.........
.........
.........
let mut result = client.get(uri).await?;
let some_key = result.headers().get("some_key");
let next_url = match some_key {
Some(url) => {
let some_result = client.get(Uri::from_static(url.to_str().unwrap())).await?
}
None => println!("....")
};
Ok(())
}
When I run this code the error "borrowed value does not live long enough argument requires that result is borrowed for `'static"
I have created a compile-able example based on your snipped to reproduce the error in the playground, and if you are able to do something like this in your question (for future reference), it usually helps you get more specific answers.
The Request passed into the function has no lifetime guarantees, so this will fail with the error you mentioned:
use http::{Request, Uri};
async fn make_request(result: &Request<()>) -> std::io::Result<()> {
match result.headers().get("some_key") {
// `url` is a reference to the string in the "some_key" header
Some(url) => {
let some_result = Uri::from_static(url.to_str().unwrap());
}
None => println!("....")
};
Ok(())
}
You can add that lifetime requirement, but that probably isn't what you need, and will likely give you the same error message, just in a different place:
async fn make_request_static(result: &'static Request<()>) -> std::io::Result<()> {
match result.headers().get("some_key") {
// because the request is static, so can be `url`
Some(url) => {
let some_result = Uri::from_static(url.to_str().unwrap());
}
None => println!("....")
};
Ok(())
}
Uri implements the FromStr trait, though, so you would be best off using that. There is no longer a lifetime requirement, so it can work with any string you pass in, even one which is currently borrowed:
// need to import the trait to use its methods
use std::str::FromStr;
async fn make_request_3(result: &Request<()>) -> std::io::Result<()> {
match result.headers().get("some_key") {
// because the request is static, so can be `url`
Some(url) => {
let some_result = Uri::from_str(url.to_str().unwrap());
}
None => println!("....")
};
Ok(())
}

Map domain result into http result

I'm trying to build an HTTP Server using Actix web framework in Rust.
I'm used to segregate Business Model and Business Error from HttpResponse.
For doing that, I've my service, CredentialService, that exposes a method that returns a result Result<String, CredentialServiceError>.
My WebServer exposes an API POST /login that accepts username and password and returns a JWT.
The enum `CredentialServiceError' is the following
use derive_more::{Display, Error};
#[derive(Debug, Display, Error)]
pub enum CredentialServiceError {
NoCredentialFound,
ErrorOnGeneratingJWT,
}
My handler is something like:
async fn login(request_body: web::Json<LoginRequest>, credential_service: web::Data<CredentialService>) -> Result<HttpResponse> {
let request_body: LoginRequest = request_body.0;
let jwt = credential_service.login(request_body.username, request_body.password);
let response: Result<LoginResponse, CredentialServiceError> = jwt.map(|jwt| {
LoginResponse { jwt }
});
response.into()
}
I receive this error:
the trait bound `std::result::Result<actix_web::HttpResponse, actix_web::Error>: std::convert::From<std::result::Result<model::LoginResponse, credential_service::CredentialServiceError>>` is not satisfied
the trait `std::convert::From<std::result::Result<model::LoginResponse, credential_service::CredentialServiceError>>` is not implemented for `std::result::Result<actix_web::HttpResponse, actix_web::Error>`
help: the following implementations were found:
<std::result::Result<(), idna::uts46::Errors> as std::convert::From<idna::uts46::Errors>>
<std::result::Result<(), ring::error::Unspecified> as std::convert::From<ring::bssl::Result>>
<std::result::Result<miniz_oxide::MZStatus, miniz_oxide::MZError> as std::convert::From<&miniz_oxide::StreamResult>>
<std::result::Result<miniz_oxide::MZStatus, miniz_oxide::MZError> as std::convert::From<&miniz_oxide::StreamResult>>
and 2 others
note: required because of the requirements on the impl of `std::convert::Into<std::result::Result<actix_web::HttpResponse, actix_web::Error>>` for `std::result::Result<model::LoginResponse, credential_service::CredentialServiceError>`rustc(E0277)
I've also tried to implement
impl error::ResponseError for CredentialServiceError { ... }
impl Into<HttpResponse> for LoginResponse { ... }
The the error doesn't change.
So, how can I convert Result<String, CredentialServiceError> into Result<HttpResponse> ?
So the problem you are facing is, that you want to convert your result into the Result that actix_web functions expect. I hope I understood that correctly.
I don't see an other way than to map your Result to the expected one. There are different ways you could accomplish that. If you would want to keep the implementation details in your LoginResponse struct you could use the Into trait.
impl Into<HttpResponse> for LoginResponse {
fn into(self) -> HttpResponse {
HttpResponse::Ok().body(self.jwt)
}
}
If you don't really care you can use the map functions provided by Result.
fn map_credential_error(error: CredentialServiceError) -> actix_web::error::Error {
match error {
CredentialServiceError::NoCredentialFound => {
actix_web::error::ErrorUnauthorized("Not authorized")
}
CredentialServiceError::ErrorOnGeneratingJWT => {
actix_web::error::ErrorInternalServerError("Something went wrong generating your jwt")
}
}
}
So in the end your login function would look something like this.
async fn login(
request_body: web::Json<LoginRequest>,
credential_service: web::Data<CredentialService>,
) -> Result<HttpResponse> {
let request_body: LoginRequest = request_body.0;
let jwt = credential_service.login(request_body.username, request_body.password);
let response: Result<HttpResponse, CredentialServiceError> =
jwt.map(|jwt| LoginResponse { jwt }.into());
response.map_err(map_credential_error)
}
Hope I could help and that I understood your question.
In addition to #Julius-Kreutz's answer you may consider impl ResponseError trait Source from Chanda, Abhishek. For example if your CredentialServiceError is an enum like:
#[derive(Debug, Display)]
pub enum CredentialServiceError {
#[display(fmt = "Internal Server Error")]
InternalServerError,
#[display(fmt = "BadRequest: {}", _0)]
BadRequest(String),
#[display(fmt = "JWKSFetchError")]
JWKSFetchError,
}
Then yuo can do something like:
impl ResponseError for CredentialServiceError {
fn error_response(&self) -> HttpResponse {
match self {
CredentialServiceError::InternalServerError => {
HttpResponse::InternalServerError().json("Internal Server Error, Please try later")
}
CredentialServiceError::BadRequest(ref message) => HttpResponse::BadRequest().json(message),
ServiceError::JWKSFetchError => {
HttpResponse::InternalServerError().json("Could not fetch JWKS")
}
}
}
}
To enable the .into()

How to print a response body in actix_web middleware?

I'd like to write a very simple middleware using actix_web framework but it's so far beating me on every front.
I have a skeleton like this:
let result = actix_web::HttpServer::new(move || {
actix_web::App::new()
.wrap_fn(move |req, srv| {
srv.call(req).map(move|res| {
println!("Got response");
// let s = res.unwrap().response().body();
// ???
res
})
})
})
.bind("0.0.0.0:8080")?
.run()
.await;
and I can access ResponseBody type via res.unwrap().response().body() but I don't know what can I do with this.
Any ideas?
This is an example of how I was able to accomplish this with 4.0.0-beta.14:
use std::cell::RefCell;
use std::pin::Pin;
use std::rc::Rc;
use std::collections::HashMap;
use std::str;
use erp_contrib::{actix_http, actix_web, futures, serde_json};
use actix_web::dev::{Service, ServiceRequest, ServiceResponse, Transform};
use actix_web::{HttpMessage, body, http::StatusCode, error::Error ,HttpResponseBuilder};
use actix_http::{h1::Payload, header};
use actix_web::web::{BytesMut};
use futures::future::{ok, Future, Ready};
use futures::task::{Context, Poll};
use futures::StreamExt;
use crate::response::ErrorResponse;
pub struct UnhandledErrorResponse;
impl<S: 'static> Transform<S, ServiceRequest> for UnhandledErrorResponse
where
S: Service<ServiceRequest, Response = ServiceResponse, Error = Error>,
S::Future: 'static,
{
type Response = ServiceResponse;
type Error = Error;
type Transform = UnhandledErrorResponseMiddleware<S>;
type InitError = ();
type Future = Ready<Result<Self::Transform, Self::InitError>>;
fn new_transform(&self, service: S) -> Self::Future {
ok(UnhandledErrorResponseMiddleware { service: Rc::new(RefCell::new(service)), })
}
}
pub struct UnhandledErrorResponseMiddleware<S> {
service: Rc<RefCell<S>>,
}
impl<S> Service<ServiceRequest> for UnhandledErrorResponseMiddleware<S>
where
S: Service<ServiceRequest, Response = ServiceResponse, Error = Error> + 'static,
S::Future: 'static,
{
type Response = ServiceResponse;
type Error = Error;
type Future = Pin<Box<dyn Future<Output = Result<Self::Response, Self::Error>>>>;
fn poll_ready(&self, cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
self.service.poll_ready(cx)
}
fn call(&self, mut req: ServiceRequest) -> Self::Future {
let svc = self.service.clone();
Box::pin(async move {
/* EXTRACT THE BODY OF REQUEST */
let mut request_body = BytesMut::new();
while let Some(chunk) = req.take_payload().next().await {
request_body.extend_from_slice(&chunk?);
}
let mut orig_payload = Payload::empty();
orig_payload.unread_data(request_body.freeze());
req.set_payload(actix_http::Payload::from(orig_payload));
/* now process the response */
let res: ServiceResponse = svc.call(req).await?;
let content_type = match res.headers().get("content-type") {
None => { "unknown"}
Some(header) => {
match header.to_str() {
Ok(value) => {value}
Err(_) => { "unknown"}
}
}
};
return match res.response().error() {
None => {
Ok(res)
}
Some(error) => {
if content_type.to_uppercase().contains("APPLICATION/JSON") {
Ok(res)
} else {
let error = error.to_string();
let new_request = res.request().clone();
/* EXTRACT THE BODY OF RESPONSE */
let _body_data =
match str::from_utf8(&body::to_bytes(res.into_body()).await?){
Ok(str) => {
str
}
Err(_) => {
"Unknown"
}
};
let mut errors = HashMap::new();
errors.insert("general".to_string(), vec![error]);
let new_response = match ErrorResponse::new(&false, errors) {
Ok(response) => {
HttpResponseBuilder::new(StatusCode::BAD_REQUEST)
.insert_header((header::CONTENT_TYPE, "application/json"))
.body(serde_json::to_string(&response).unwrap())
}
Err(_error) => {
HttpResponseBuilder::new(StatusCode::BAD_REQUEST)
.insert_header((header::CONTENT_TYPE, "application/json"))
.body("An unknown error occurred.")
}
};
Ok(ServiceResponse::new(
new_request,
new_response
))
}
}
}
})
}
}
The extraction of the Request Body is straightforward and similar to how Actix example's illustrate. However, with the update to version Beta 14, pulling the bytes directly from AnyBody has changed with the introduction of BoxedBody. Fortunately, I found a utility function body::to_bytes (use actix_web::body::to_bytes) which does a good job. It's current implementation looks like this:
pub async fn to_bytes<B: MessageBody>(body: B) -> Result<Bytes, B::Error> {
let cap = match body.size() {
BodySize::None | BodySize::Sized(0) => return Ok(Bytes::new()),
BodySize::Sized(size) => size as usize,
// good enough first guess for chunk size
BodySize::Stream => 32_768,
};
let mut buf = BytesMut::with_capacity(cap);
pin!(body);
poll_fn(|cx| loop {
let body = body.as_mut();
match ready!(body.poll_next(cx)) {
Some(Ok(bytes)) => buf.extend_from_slice(&*bytes),
None => return Poll::Ready(Ok(())),
Some(Err(err)) => return Poll::Ready(Err(err)),
}
})
.await?;
Ok(buf.freeze())
}
which I believe should be fine to extract the body in this way, as the body is extracted from the body stream by to_bytes().
If someone has a better way, let me know, but it was a little bit of pain, and I only had recently determined how to do it in Beta 13 when it switched to Beta 14.
This particular example intercepts errors and rewrites them to JSON format if they're not already json format. This would be the case, as an example, if an error occurs outside of a handler, such as parsing JSON in the handler function itself _data: web::Json<Request<'a, LoginRequest>> and not in the handler body. Extracting the Request Body and Response Body is not necessary to accomplish the goal, and is just here for illustration.

How to pass many params to rust actix_web route

Is possible to pass more than one parameter into axtic_web route ?
// srv.rs (frag.)
HttpServer::new(|| {
App::new()
.route(
"/api/ext/{name}/set/config/{id}",
web::get().to(api::router::setExtConfig),
)
})
.start();
// router.rs (frag.)
pub fn setExtConfig(
name: web::Path<String>,
id: web::Path<String>,
_req: HttpRequest,
) -> HttpResponse {
println!("{} {}", name, id);
HttpResponse::Ok()
.content_type("text/html")
.body("OK")
}
For routes with one param everything is ok, but for this example
i see only message in the browser: wrong number of parameters: 2 expected 1, and the response stauts code is 404.
I realy need to pass more parameters (from one to three or four)...
That is a perfect fit for tuple:
pub fn setExtConfig(
param: web::Path<(String, String)>,
_req: HttpRequest,
) -> HttpResponse {
println!("{} {}", param.0, param.1);
HttpResponse::Ok().content_type("text/html").body("OK")
}

How to serve a fallback file using Iron's staticfile when the original file is not found?

I'm using Iron to serve a React site. I'm trying to get it to serve index.html if the file or directory does not exist.
fn staticHandler(req: &mut Request) -> IronResult<Response> {
let url = Url::parse("http://localhost:1393").unwrap();
let getFile_result = Static::handle(&Static::new(Path::new("../html")), req);
match getFile_result {
Ok(_) => getFile_result,
Err(err) => {
Static::handle(
// returns 404 error - ../html/index.html returns 500
&Static::new(Path::new("localhost:1393/index.html")),
req,
)
}
}
}
If I go to localhost:1393 I get my index page if I go to localhost:1393/not-a-directory I just get an error.
Is there a way to redirect (without changing the url) or some other solution?
This is not a duplicate of How to change Iron's default 404 behaviour? because I'm trying to handle when the static asset the user requests does not exist, not when the route is not defined.
As discussed on staticfile issue #78 titled "Static with fallback", you can wrap the handler, check for a 404, and serve a file instead:
struct Fallback;
impl AroundMiddleware for Fallback {
fn around(self, handler: Box<Handler>) -> Box<Handler> {
Box::new(FallbackHandler(handler))
}
}
struct FallbackHandler(Box<Handler>);
impl Handler for FallbackHandler {
fn handle(&self, req: &mut Request) -> IronResult<Response> {
let resp = self.0.handle(req);
match resp {
Err(err) => {
match err.response.status {
Some(status::NotFound) => {
let file = File::open("/tmp/example").unwrap();
Ok(Response::with((status::Ok, file)))
}
_ => Err(err),
}
}
other => other
}
}
}

Resources