Reputation: 30885
I'm trying to accomplish a very simple task here. I don't understand why it has become so complex. I'm attempting to modify headers before they reach the service and return a failure if a certain condition is not met.
Here is my middleware file:
use std::future::{ready, Ready};
use actix_http::header::{HeaderValue, HeaderName};
use actix_web::{
dev::{self, Service, ServiceRequest, ServiceResponse, Transform},
Error,http::Method
};
use futures_util::future::LocalBoxFuture;
//use crate::constants;
pub struct Heartbeat;
impl<S, B> Transform<S, ServiceRequest> for Heartbeat
where
S: Service<ServiceRequest, Response = ServiceResponse<B>, Error = Error>,
S::Future: 'static,
B: 'static,
{
type Response = ServiceResponse<B>;
type Error = Error;
type InitError = ();
type Transform = HeartMiddleware<S>;
type Future = Ready<Result<Self::Transform, Self::InitError>>;
fn new_transform(&self, service: S) -> Self::Future {
ready(Ok(HeartMiddleware { service }))
}
}
pub struct HeartMiddleware<S> {
service: S,
}
impl<S, B> Service<ServiceRequest> for HeartMiddleware<S>
where
S: Service<ServiceRequest, Response = ServiceResponse<B>, Error = Error>,
S::Future: 'static,
B: 'static,
{
type Response = ServiceResponse<B>;
type Error = Error;
type Future = LocalBoxFuture<'static, Result<Self::Response, Self::Error>>;
dev::forward_ready!(service);
fn call(&self, req: ServiceRequest) -> Self::Future {
let fut = self.service.call(req);
Box::pin(async move {
let mut res = fut.await?;
let headers = res.headers_mut();
headers.insert(
HeaderName::from_static("Content-Type"), HeaderValue::from_static("text/plain")
);
if (Method::POST == req.method() ||
Method::GET == req.method() ||
Method::HEAD == req.method()) && req.path() == "/ping" {
Ok(res)
}
Err(actix_web::error::ErrorImATeapot("TeaPot"))
})
}
}
and the main file :
use actix_web::{get,web,http,Result,App,HttpServer,HttpRequest, Responder, HttpResponse};
use serde::{Deserialize,Serialize};
use actix_cors::Cors;
mod heartbeat;
static PORT :u16 = 9091;
#[get("/")]
async fn index()->impl Responder {
HttpResponse::Ok().body("template")
}
#[derive(Serialize)]
pub struct Response {
pub message: String,
}
async fn not_found() ->Result<HttpResponse> {
let response = Response {
message: "Resource not found".to_string(),
};
Ok(HttpResponse::NotFound().json(response))
}
#[actix_web::main]
async fn main()-> std::io::Result<()> {
std::env::set_var("RUST_LOG", "debug");
env_logger::init();
HttpServer::new(|| App::new()
.wrap(
Cors::default()
.allowed_origin("http://*") // Allow all http origins
.allowed_origin("https://*") // Allow all https origins
.allowed_methods(vec!["GET","POST","PUT","DELETE","OPTIONS"])
.allowed_headers(vec![http::header::AUTHORIZATION,http::header::CONTENT_TYPE,
http::header::ACCEPT,http::header::LINK])
.allowed_header("X-CSRF-TOKEN")
.supports_credentials()
.max_age(300)
)
.wrap(heartbeat::Heartbeat)
.service(index)
.service(web::resource("/ping"))
.default_service(web::route().to(not_found)))
.bind(("127.0.0.1",PORT))?
.run()
.await
}
When I compile it, I'm getting:
cargo build
Compiling broker-service v0.1.0 (C:\dev\my\rust\workspace\broker-service)
warning: unused import: `HttpRequest`
--> src\main.rs:1:52
|
1 | use actix_web::{get,web,http,Result,App,HttpServer,HttpRequest, Responder, HttpResponse};
| ^^^^^^^^^^^
|
= note: `#[warn(unused_imports)]` on by default
warning: unused import: `Deserialize`
--> src\main.rs:2:13
|
2 | use serde::{Deserialize,Serialize};
| ^^^^^^^^^^^
error[E0308]: mismatched types
--> src\heartbeat.rs:65:18
|
62 | / if (Method::POST == req.method() ||
63 | | Method::GET == req.method() ||
64 | | Method::HEAD == req.method()) && req.path() == "/ping" {
65 | | Ok(res)
| | ^^^^^^^ expected `()`, found `Result<ServiceResponse<B>, _>`
66 | | }
| |_____________- expected this to be `()`
|
= note: expected unit type `()`
found enum `Result<ServiceResponse<B>, _>`
note: return type inferred to be `()` here
--> src\heartbeat.rs:56:27
|
56 | let mut res = fut.await?;
| ^^^^^^^^^^
For more information about this error, try `rustc --explain E0308`.
warning: `broker-service` (bin "broker-service") generated 2 warnings
error: could not compile `broker-service` (bin "broker-service") due to previous error; 2 warnings emitted
After updating the solution suggested, I am still getting an error:
use std::future::{ready, Ready};
use actix_http::header::{HeaderValue, HeaderName};
use actix_web::{
dev::{self, Service, ServiceRequest, ServiceResponse, Transform},
Error,http::Method
};
use futures_util::future::LocalBoxFuture;
//use crate::constants;
pub struct Heartbeat;
impl<S, B> Transform<S, ServiceRequest> for Heartbeat
where
S: Service<ServiceRequest, Response = ServiceResponse<B>, Error = Error>,
S::Future: 'static,
B: 'static,
{
type Response = ServiceResponse<B>;
type Error = Error;
type InitError = ();
type Transform = HeartMiddleware<S>;
type Future = Ready<Result<Self::Transform, Self::InitError>>;
fn new_transform(&self, service: S) -> Self::Future {
ready(Ok(HeartMiddleware { service }))
}
}
pub struct HeartMiddleware<S> {
service: S,
}
impl<S, B> Service<ServiceRequest> for HeartMiddleware<S>
where
S: Service<ServiceRequest, Response = ServiceResponse<B>, Error = Error>,
S::Future: 'static,
B: 'static,
{
type Response = ServiceResponse<B>;
type Error = Error;
type Future = LocalBoxFuture<'static, Result<Self::Response, Self::Error>>;
dev::forward_ready!(service);
fn call(&self, req: ServiceRequest) -> Self::Future {
let fut = self.service.call(req);
Box::pin(async move {
let mut res = fut.await?;
let headers = res.headers_mut();
headers.insert(
HeaderName::from_static("Content-Type"), HeaderValue::from_static("text/plain")
);
if (Method::POST == req.method() ||
Method::GET == req.method() ||
Method::HEAD == req.method()) && req.path() == "/ping" {
return Ok(res)
}
Err(actix_web::error::ErrorImATeapot("TeaPot"))
})
}
}
cargo build
Compiling broker-service v0.1.0 (C:\dev\my\rust\workspace\broker-service)
warning: unused import: `HttpRequest`
--> src\main.rs:1:52
|
1 | use actix_web::{get,web,http,Result,App,HttpServer,HttpRequest, Responder, HttpResponse};
| ^^^^^^^^^^^
|
= note: `#[warn(unused_imports)]` on by default
warning: unused import: `Deserialize`
--> src\main.rs:2:13
|
2 | use serde::{Deserialize,Serialize};
| ^^^^^^^^^^^
error[E0382]: use of moved value: `req`
--> src\heartbeat.rs:54:18
|
47 | fn call(&self, req: ServiceRequest) -> Self::Future {
| --- move occurs because `req` has type `ServiceRequest`, which does not implement the `Copy` trait
...
50 | let fut = self.service.call(req);
| --- value moved here
...
54 | Box::pin(async move {
| __________________^
55 | |
56 | | let mut res = fut.await?;
57 | | let headers = res.headers_mut();
... |
62 | | if (Method::POST == req.method() ||
| | --- use occurs due to use in generator
... |
68 | | Err(actix_web::error::ErrorImATeapot("TeaPot"))
69 | | })
| |_________^ value used here after move
For more information about this error, try `rustc --explain E0382`.
warning: `broker-service` (bin "broker-service") generated 2 warnings
error: could not compile `broker-service` (bin "broker-service") due to previous error; 2 warnings emitted
Upvotes: 1
Views: 296
Reputation: 70860
Your if
is a statement, not an expression. You're basically telling the compiler "execute this if
and throw away the result, then return Err
". The result must be of type ()
.
You want an explicit return
:
Box::pin(async move {
let mut res = fut.await?;
let headers = res.headers_mut();
headers.insert(
HeaderName::from_static("Content-Type"),
HeaderValue::from_static("text/plain"),
);
if (Method::POST == req.method()
|| Method::GET == req.method()
|| Method::HEAD == req.method())
&& req.path() == "/ping"
{
return Ok(res);
}
Err(actix_web::error::ErrorImATeapot("TeaPot"))
})
Or add an else
, to make the whole thing an expression:
Box::pin(async move {
let mut res = fut.await?;
let headers = res.headers_mut();
headers.insert(
HeaderName::from_static("Content-Type"),
HeaderValue::from_static("text/plain"),
);
if (Method::POST == req.method()
|| Method::GET == req.method()
|| Method::HEAD == req.method())
&& req.path() == "/ping"
{
Ok(res)
} else {
Err(actix_web::error::ErrorImATeapot("TeaPot"))
}
})
Upvotes: 1