call an async function in actix-web middleware - rust

I implemented a actix-web middleware but i'm having issues trying to retrieve a client object from a database pool because that call is an async function:
impl <S, B> Service<ServiceRequest> for SomeMiddleware<S>
...
fn call(&self, request: ServiceRequest) -> Self::Future {
if let Some(pool) = request.app_data::web::Data<Pool>>() {
// this call will not work
let client = pool.get().await.unwrap();
}
}
How do I get a client object from the pool?
I am using actix-web 4.x and deadpool-postgres.

Related

How to return Error Response to a user from a middleware

I am new to Rust and the Actix web framework. I have been trying to create a simple server in actix web. I have implemented the auth system but I am trying to create a middleware for verifying the JWT token and extract the claims from the jwt to be used in an handler.
impl<S, B> Service<ServiceRequest> for JwtVerifierMiddleware<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>>;
forward_ready!(service);
fn call(&self, req: ServiceRequest) -> Self::Future {
let authorization = req.headers().get("Authorization");
let mut extensions = req.extensions_mut();
let fut = self.service.call(req);
let token = if let Some(authorization) = authorization {
authorization.to_str().unwrap()
} else {
return Box::pin(async {
let res = fut.await?;
Ok(res)
});
};
let claims = verify(token).unwrap();
extensions.insert(claims);
Box::pin(async move {
let res = fut.await?;
Ok(res)
})
}
}
I have the code above where I am trying to extract the token from the request and return an error response if the token is a None variant.The issue I have currently is that self.service.call() wants to move the req but it has already been borrowed here req.headers().get("Authorization"). I am not sure how to approach this.
I have tried to clone the req but it seems ServiceRequest does not implement the clone trait. I have also tried to pass a reference to the ServiceRequest as argument to the call function but I get an error that method call has an incompatible type for trait Service

py03 sharing Juniper GraphQL request module (sharing state between dependencies)

Thanks in advance to #brunorijsman for his excellent self answered example of callbacks between rust and python using py03 How to pass a Rust function as a callback to Python using pyo3, which my code is heavily influenced by.
I'm trying to interrupt a GraphQL request using callbacks to a python file which is importing from rust using py03.
I've been able to use a Mutex to store shared state, but i'm stuck at how to store or share with Juniper a callback. I'm planning to eventually await the callbacks on both sides with async and futures, but at the moment I can't find a way to make the callback in rust callable from a Juniper resolver once its been registered...
GQLwrapper rust module lib.rs:
#[macro_use]
extern crate lazy_static;
use actix_cors::Cors;
use actix_web::{
middleware, route,
web::{self, Data},
App, HttpResponse, HttpServer, Responder,
};
use juniper::http::{GraphQLRequest};
use juniper::{EmptySubscription, FieldResult, RootNode, EmptyMutation};
use pyo3::prelude::*;
use std::thread;
use std::{io, sync::Arc};
mod schema;
use crate::schema::{Model, Params};
pub struct QueryRoot;
#[juniper::graphql_object]
impl QueryRoot {
fn model<'mdl>(&self, _params: Params) -> FieldResult<Model> {
// I WANT TO BE ABLE TO CALL THE CALLBACK HERE WITH MY GRAPHQLDATA!
Ok(Model {
prompt: _params.prompt.to_owned(),
})
}
}
type Schema = RootNode<'static, QueryRoot, EmptyMutation, EmptySubscription>;
fn create_schema() -> Schema {
Schema::new(QueryRoot {}, EmptyMutation::new(), EmptySubscription::new())
}
/// GraphQL endpoint
#[route("/graphql", method = "GET", method = "POST")]
async fn graphql(st: web::Data<Schema>, data: web::Json<GraphQLRequest>) -> impl Responder {
let user = data.execute(&st, &()).await;
HttpResponse::Ok().json(user)
}
#[actix_web::main]
async fn main() -> io::Result<()> {
std::env::set_var("RUST_LOG", "actix_web=info");
env_logger::init();
let schema = Arc::new(create_schema());
HttpServer::new(move || {
#[pyfunction]
fn init() -> PyResult<String> {
thread::spawn(move || main());
Ok("GQL server started...".to_string())
}
#[pyclass]
struct Callback {
#[allow(dead_code)]
callback_function: Box<dyn Fn(&PyAny) -> PyResult<()> + Send>,
}
#[pymethods]
impl Callback {
fn __call__(&self, python_api: &PyAny) -> PyResult<()> {
(self.callback_function)(python_api)
}
}
#[pyfunction]
fn rust_register_callback(python_api: &PyAny) -> PyResult<()> {
// this registers a python callback which will in turn be used to send rust_callback
// along with a message when a request is recieved.
Python::with_gil(|py| {
// THIS IS THE CALLBACK I WANT TO BE ABLE TO CALL FROM THE RESOLVER...
let callback = Box::new(Callback {
callback_function: Box::new(move |python_api| {
rust_callback(python_api)
}),
});
println!("Rust: register callback");
python_api
.getattr("set_response_callback")?
.call1((callback.into_py(py),"data piped from request".to_string()))?;
Ok(())
})
}
fn rust_callback(message: &PyAny) -> PyResult<()> {
// This callback will return a message from python and add to the GraphQL response.
// I need to be able to ultimately await this and use it to set state that will be passed back
println!("Rust: rust_callback");
println!("Rust: Message={}", message);
Ok(())
}
#[pymodule]
#[pyo3(name = "GQLwrapper")]
fn GQLwrapper(_py: Python, m: &PyModule) -> PyResult<()> {
m.add_function(wrap_pyfunction!(init, m)?)?;
m.add_function(wrap_pyfunction!(rust_register_callback, m)?)?;
m.add_class::<Callback>()?;
Ok(())
}
App::new()
.app_data(Data::from(schema.clone()))
.service(graphql)
.wrap(Cors::permissive())
.wrap(middleware::Logger::default())
})
.workers(2)
.bind("127.0.0.1:8080")?
.run()
.await
}
Python main.py:
from lib.wrapper import graphql_wrapper
import time
def runner():
print("Python: doing some work")
graphql_wrapper.call_response_callback()
graphql_wrapper.set_fn_to_call(runner)
time.sleep(5000)
python wrapper.py:
import GQLwrapper
class PythonApi:
def __init__(self):
self.response_callback = None
self.python_callback = None
GQLwrapper.init()
def set_response_callback(self, callback, data):
self.response_callback = callback
print(f'Python: Message={data}')
self.python_callback()
def call_response_callback(self):
assert self.response_callback is not None
self.response_callback("data to add back into response")
def set_fn_to_call(self, callback):
self.python_callback = callback
GQLwrapper.rust_register_callback(self)
graphql_wrapper = PythonApi()

How to modify request data in actix-web middleware? [duplicate]

This question already has an answer here:
How can I pass structs from an Actix middleware to the handler?
(1 answer)
Closed 5 months ago.
Is there a recommended way of modifying the request received on actix-web? I am looking for way to add data to the request object and have it available for processing by downstream middlewares and handlers.
The Middleware documentation says:
"Actix-web’s middleware system allows us to add additional behavior to request/response processing. Middleware can hook into an incoming request process, enabling us to modify requests as well as halt request processing to return a response early."
The page doesn't have an example on how to modify the Request.
Let take the code below (obtained from the documentation above), what would be the code to somehow add data to the request?
use std::pin::Pin;
use std::task::{Context, Poll};
use actix_service::{Service, Transform};
use actix_web::{dev::ServiceRequest, dev::ServiceResponse, Error};
use futures::future::{ok, Ready};
use futures::Future;
// There are two steps in middleware processing.
// 1. Middleware initialization, middleware factory gets called with
// next service in chain as parameter.
// 2. Middleware's call method gets called with normal request.
pub struct SayHi;
// Middleware factory is `Transform` trait from actix-service crate
// `S` - type of the next service
// `B` - type of response's body
impl<S, B> Transform<S> for SayHi
where
S: Service<Request = ServiceRequest, Response = ServiceResponse<B>, Error = Error>,
S::Future: 'static,
B: 'static,
{
type Request = ServiceRequest;
type Response = ServiceResponse<B>;
type Error = Error;
type InitError = ();
type Transform = SayHiMiddleware<S>;
type Future = Ready<Result<Self::Transform, Self::InitError>>;
fn new_transform(&self, service: S) -> Self::Future {
ok(SayHiMiddleware { service })
}
}
pub struct SayHiMiddleware<S> {
service: S,
}
impl<S, B> Service for SayHiMiddleware<S>
where
S: Service<Request = ServiceRequest, Response = ServiceResponse<B>, Error = Error>,
S::Future: 'static,
B: 'static,
{
type Request = ServiceRequest;
type Response = ServiceResponse<B>;
type Error = Error;
type Future = Pin<Box<dyn Future<Output = Result<Self::Response, Self::Error>>>>;
fn poll_ready(&mut self, cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
self.service.poll_ready(cx)
}
fn call(&mut self, req: ServiceRequest) -> Self::Future {
println!("Hi from start. You requested: {}", req.path());
let fut = self.service.call(req);
Box::pin(async move {
let res = fut.await?;
println!("Hi from response");
Ok(res)
})
}
}
I was just looking at how to do this as well, disappointing that they talk about it in their docs but do not show any examples.
I found that you can either edit the headers or extensions as follows, I'm not sure how to edit it otherwise.
Headers
Grab the headers from the Service request, let headers = req.headers_mut()
Take the HeaderMap and you can from there either insert, remove, clear, etc
Insert example: headers.insert(HeaderName::from_lowercase(b"content-length").unwrap(), HeaderValue::from_static("hello"));
Extensions
Get the extensions for the request let extensions = req.extensions_mut()
Add to the extension extensions.insert("foo".to_string());
One liner: req.extensions_mut().insert("foo".to_string());
Get that extension later: req.extensions().get::<String>()
It's certainly possible to modify a request and the associated response from middleware. Here is a brief example(this works with Actix v4):
use x_contrib::{actix_http, actix_web, body, futures, log, HttpMessage, HttpResponseBuilder};
use std::cell::RefCell;
use log::Level;
use std::pin::Pin;
use actix_web::dev::{Service, ServiceRequest, ServiceResponse, Transform};
use actix_web::Error;
use futures::future::{ok, Ready};
use futures::task::{Context, Poll};
use std::future::Future;
use std::rc::Rc;
use std::str;
use crate::middleware::utility::{ApiMiddlewareUtility, MiddlewareUtility};
use crate::request;
use x_common::prelude::*;
use x_common::settings;
use x_contrib::actix_http::h1::Payload;
use x_contrib::body::{EitherBody, MessageBody};
use x_contrib::futures::future::err;
use x_contrib::futures::StreamExt;
use x_contrib::web::{Buf, BytesMut};
pub struct LogEvents;
impl<S: 'static, B> Transform<S, ServiceRequest> for LogEvents
where
S: Service<ServiceRequest, Response = ServiceResponse<B>, Error = Error>,
S::Future: 'static,
B: MessageBody + 'static,
{
type Response = ServiceResponse<EitherBody<B>>;
type Error = Error;
type Transform = LogEventsMiddleware<S>;
type InitError = ();
type Future = Ready<Result<Self::Transform, Self::InitError>>;
fn new_transform(&self, service: S) -> Self::Future {
ok(LogEventsMiddleware {
service: Rc::new(RefCell::new(service)),
})
}
}
pub struct LogEventsMiddleware<S> {
service: Rc<RefCell<S>>,
}
impl<S: 'static, B> Service<ServiceRequest> for LogEventsMiddleware<S>
where
S: Service<ServiceRequest, Response = ServiceResponse<B>, Error = Error>,
S::Future: 'static,
B: MessageBody + 'static,
{
type Response = ServiceResponse<EitherBody<B>>;
type Error = Error;
type Future = Pin<Box<dyn Future<Output = Result<Self::Response, Self::Error>>>>;
actix_web::dev::forward_ready!(service);
fn call(&self, mut req: ServiceRequest) -> Self::Future {
let svc = self.service.clone();
let log_level = settings::get_setting("log_level").unwrap_or("info".to_owned());
Box::pin(async move {
match log_level.as_str() {
"debug" | "trace" | "info" => {
let route = req.path().to_owned();
/* we only process requests that are json */
if !MiddlewareUtility::is_json_request(&req) {
let res: ServiceResponse = svc.call(req).await?.map_into_boxed_body();
return Ok(res.map_into_right_body());
}
/* extract and log the request */
let mut request_body = BytesMut::new();
while let Some(chunk) = req.take_payload().next().await {
request_body.extend_from_slice(&chunk?);
}
match str::from_utf8(&request_body.to_vec().as_slice()) {
Ok(str) => {
/* identify routes that we will redact the body from,
these are items that contain sensitive information we do not want to log
*/
match route.as_str() {
"/x/protected_endpoint" => {
tracing::info!({ body = "Redacted" }, "HTTP Request");
}
_ => {
tracing::info!({body = %str}, "HTTP Request");
}
}
}
Err(_) => {}
};
let (payload_sender, mut orig_payload) = Payload::create(true);
orig_payload.unread_data(request_body.freeze());
req.set_payload(actix_http::Payload::from(orig_payload));
/* extract and log the response */
let res: ServiceResponse = svc.call(req).await?.map_into_boxed_body();
if !MiddlewareUtility::is_json_response(&res) {
return Ok(res.map_into_right_body());
}
let res_status = res.status().clone();
let res_headers = res.headers().clone();
let new_request = res.request().clone();
let body_bytes = body::to_bytes(res.into_body()).await?;
match str::from_utf8(&body_bytes) {
Ok(str) => {
tracing::info!({body = %str}, "HTTP Response");
str
}
Err(_) => "Unknown",
};
/* build an identical response */
let mut new_response = HttpResponseBuilder::new(res_status);
for (header_name, header_value) in res_headers {
new_response.insert_header((header_name.as_str(), header_value));
}
let new_response = new_response.body(body_bytes.to_vec());
Ok(ServiceResponse::new(
new_request,
new_response.map_into_right_body(),
))
}
_ => {
let res: ServiceResponse = svc.call(req).await?.map_into_boxed_body();
Ok(res.map_into_right_body())
}
}
})
}
}
This particular example integrates with tracing_actix_web, a fantastic telemetry tool and logs the request/response to jaeger if the request/response is json.
One thing to note, as far as I know, once you read out the request, you have to reassemble it, same with the response. Hence what the example is doing.

How do I pass App data to service route handler function in actix-web when using function decorations?

I found in the docs an example of how to create global state, protected by Mutex, shared among processing threads that is made available to all your route handlers. Perfect! However, I prefer to use attributes attached to my functions to wire up my route handlers. I do not know the syntax (if permitted) to use attributed functions and also pass in the global state.
Here is the example from the actix-web docs, from https://docs.rs/actix-web/1.0.2/actix_web/web/struct.Data.html
use std::sync::Mutex;
use actix_web::{web, App};
struct MyData {
counter: usize,
}
/// Use `Data<T>` extractor to access data in handler.
fn index(data: web::Data<Mutex<MyData>>) {
let mut data = data.lock().unwrap();
data.counter += 1;
}
fn main() {
let data = web::Data::new(Mutex::new(MyData{ counter: 0 }));
let app = App::new()
// Store `MyData` in application storage.
.register_data(data.clone())
.service(
web::resource("/index.html").route(
web::get().to(index)));
}
Notice how the route handler named index is being passed the web::Data.
Now here are some snippets of my code.
use actix_web::{get, App, HttpResponse, HttpServer, Responder};
pub mod request;
pub mod routes;
const SERVICE_NAME : &str = "Shy Rules Engine";
const SERVICE_VERSION : &str = "0.1";
#[get("/")]
fn index() -> impl Responder {
HttpResponse::Ok().body(format!("{} version {}", SERVICE_NAME, SERVICE_VERSION))
}
mod expression_execute {
#[post("/expression/execute")]
fn route(req: web::Json<ExpressionExecuteRequest>) -> HttpResponse {
// ... lots of code omitted ...
if response.has_error() {
HttpResponse::Ok().json(response)
}
else {
HttpResponse::BadRequest().json(response)
}
}
}
pub fn shy_service(ip : &str, port : &str) {
HttpServer::new(|| {
App::new()
.service(index)
.service(expression_execute::route)
})
.bind(format!("{}:{}", ip, port))
.unwrap()
.run()
.unwrap();
}
Notice how I am calling method App::service to wire up my route handlers.
Also notice how my route handler does not receive global state (because I have not yet added it to my app). If I used a similar pattern as the docs using register_data to create global App data, what changes do I make to my method signature, the get and post attributes and anything else so that I can pass that global state to the handler?
Or is it not possible using get and post attributes to gain access to global state?
The two cases you listed really don't have much difference:
//# actix-web = "1.0.8"
use actix_web::{get, web, App, HttpResponse, HttpServer, Responder};
use std::sync::Mutex;
const SERVICE_NAME : &str = "Shy Rules Engine";
const SERVICE_VERSION : &str = "0.1";
struct MyData {
counter: usize,
}
#[get("/")]
fn index(data: web::Data<Mutex<MyData>>) -> impl Responder {
let mut data = data.lock().unwrap();
data.counter += 1;
println!("Endpoint visited: {}", data.counter);
HttpResponse::Ok().body(format!("{} version {}", SERVICE_NAME, SERVICE_VERSION))
}
pub fn shy_service(ip : &str, port : &str) {
let data = web::Data::new(Mutex::new(MyData{ counter: 0 }));
HttpServer::new(move || {
App::new()
.register_data(data.clone())
.service(index)
})
.bind(format!("{}:{}", ip, port))
.unwrap()
.run()
.unwrap();
}
fn main() {
shy_service("127.0.0.1", "8080");
}
You can verify that it works by simply curl the http endpoint. For multiple extractors, you'll have to use tuple:
#[post("/expression/execute")]
fn route((req, data): (web::Json<ExpressionExecuteRequest>, web::Data<Mutex<MyData>>)) -> HttpResponse {
unimplemented!()
}

Copy body and headers from hyper HTTP request to a new request while inspecting the body

I would like to create a small Rust HTTP proxy using hyper which accepts requests, forwards them and dumps the request + body.
Based on this example, the proxy part works fine.
However, I can't simply copy & print the request body. My main problem is that the request body can't be simply copied into something like an Vec<u8>. I cannot deconstruct the request to read the body and then create it later since the deconstructed headers can't be added to a new request.
The following code shows my minimal HTTP proxy example:
extern crate futures;
extern crate hyper;
extern crate tokio_core;
use futures::{Future, Stream};
use hyper::{Body, Client, StatusCode};
use hyper::client::HttpConnector;
use hyper::header::{ContentLength, ContentType};
use hyper::server::{Http, Request, Response, Service};
use tokio_core::reactor::Core;
type HTTPClient = Client<HttpConnector, Body>;
struct Server {
client: HTTPClient,
}
impl Server {
pub fn new(client: HTTPClient) -> Server {
Server { client: client }
}
}
impl Service for Server {
type Request = Request;
type Response = Response;
type Error = hyper::Error;
type Future = Box<Future<Item = Self::Response, Error = Self::Error>>;
fn call(&self, mut req: Request) -> Self::Future {
let req_uri_str = {
let uri = req.uri();
format!(
"http://localhost{}?{}",
uri.path(),
uri.query().unwrap_or_default()
)
};
req.set_uri(req_uri_str.parse().unwrap());
// Try to create a copy of the new request
/*
let (method, uri, version, headers, body) = req.deconstruct();
let mut req_copy: Request<hyper::Body> = Request::new(method, uri);
// Main problem: How can the request body be copied?
// >>> let body_bytes: Vec<u8> = ...
req_copy.set_body(body);
req_copy.set_version(version);
// Try to copy the headers
for header in headers.iter() {
req_copy.headers_mut().set(header.value().unwrap());
}
*/
// This works if the request is not deconstructed
let work = self.client
.request(req)
.and_then(|res| futures::future::ok(res))
.or_else(|err| {
let body = format!("{}\n", err);
futures::future::ok(
Response::new()
.with_status(StatusCode::BadRequest)
.with_header(ContentType::plaintext())
.with_header(ContentLength(body.len() as u64))
.with_body(body),
)
});
Box::new(work)
}
}
fn main() {
// Create HTTP client core + handles
let mut core = Core::new().unwrap();
let handle = core.handle();
let handle_clone = handle.clone();
// Create HTTP server
let server_addr = "127.0.0.1:9999".parse().unwrap();
let server = Http::new()
.serve_addr_handle(&server_addr, &handle, move || {
Ok(Server::new(Client::new(&handle_clone)))
})
.unwrap();
// Connect HTTP client with server
let handle_clone2 = handle.clone();
handle.spawn(
server
.for_each(move |conn| {
handle_clone2.spawn(conn.map(|_| ()).map_err(|err| println!("Error: {:?}", err)));
Ok(())
})
.map_err(|_| ()),
);
core.run(futures::future::empty::<(), ()>()).unwrap();
}
Running this works fine, if you have any HTTP service running on Port 80, connecting with a browser to port 9999 will forward any responses and requests perfectly.
However, if you re-enable the lines regarding the construction of a new, copied request, my approach fails since I don't understand how to copy the headers. (Furthermore, this doesn't really help me when it comes to copying the request body)
I'm aware that there are similar questions here, but none of them match my requirement to re-use the request body after looking at it (or don't have answers at all).
the request body can't be simply copied into something like an Vec<u8>
Sure it can. In the Rust standard library, it's worth memorizing the capabilities of the Iterator trait. When dealing with futures, you should also memorize the capabilities of Future and Stream.
For example, hyper's Body implements Stream. This means you can use the Stream::concat2 method:
Concatenate all results of a stream into a single extendable destination, returning a future representing the end result.
This creates one large Chunk which can be converted to a Vec:
extern crate hyper; // 0.11.22
extern crate futures; // 0.1.18
use futures::{Future, Stream};
fn example(req: hyper::Request) {
req.body().concat2().map(|chunk| {
let body = chunk.to_vec();
println!("{:?}", body);
()
});
// Use this future somehow!
}
Likewise, a Vec<u8> can be converted back into a Body.
since the deconstructed headers can't be added to a new request.
req_copy.headers_mut().extend(headers.iter());
All together:
fn create_localhost_request(req: Request) -> (Request, Body) {
let (method, uri, version, headers, body) = req.deconstruct();
let req_uri_str = {
format!(
"http://localhost{}?{}",
uri.path(),
uri.query().unwrap_or_default()
)
};
let uri = req_uri_str.parse().unwrap();
let mut req_copy = Request::new(method, uri);
req_copy.set_version(version);
req_copy.headers_mut().extend(headers.iter());
(req_copy, body)
}
fn perform_proxy_request(
client: HttpClient,
req: Request,
) -> Box<Future<Item = Response, Error = hyper::Error>> {
Box::new(client.request(req).or_else(|err| {
let body = format!("{}\n", err);
Ok(Response::new()
.with_status(StatusCode::BadRequest)
.with_header(ContentType::plaintext())
.with_header(ContentLength(body.len() as u64))
.with_body(body))
}))
}
impl Service for Server {
type Request = Request;
type Response = Response;
type Error = hyper::Error;
type Future = Box<Future<Item = Self::Response, Error = Self::Error>>;
fn call(&self, req: Request) -> Self::Future {
let (mut req, body) = create_localhost_request(req);
let client = self.client.clone();
let work = body
.concat2()
.map(|chunk| chunk.to_vec())
// Do whatever we need with the body here, but be careful
// about doing any synchronous work.
.map(move |body| {
req.set_body(body);
req
})
.and_then(|req| perform_proxy_request(client, req));
Box::new(work)
}
}

Resources