actix-web: limit upload file size - rust

I successfully uploaded the file referring to this example, but I don't know how to limit the size of the file, for example I can't save the file more than 5M
[dependencies]
actix-web = "4"
actix-multipart = "0.4"
I tried this, but it didn't work.
web::resource("/upload_file")
.app_data(web::PayloadConfig::new(1024 * 5))
.route(web::post().to(views::save_file)),

middleware
It is used in all requests by default, and can be used in a specified route using req.path()
use actix_web::Error;
use std::future::{ready, Ready};
use actix_web::dev::{forward_ready, Service, ServiceRequest, ServiceResponse, Transform};
use futures_util::future::LocalBoxFuture;
pub struct ContentLengthLimit {
pub limit: u64, // byte
}
impl<S, B> Transform<S, ServiceRequest> for ContentLengthLimit
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 = ContentLengthLimitMiddleware<S>;
type Future = Ready<Result<Self::Transform, Self::InitError>>;
fn new_transform(&self, service: S) -> Self::Future {
ready(Ok(ContentLengthLimitMiddleware {
service,
limit: self.limit,
}))
}
}
impl Default for ContentLengthLimit {
fn default() -> Self {
Self {
limit: 1024 * 1024 * 5, /* 5M */
}
}
}
pub struct ContentLengthLimitMiddleware<S> {
service: S,
limit: u64,
}
impl<S, B> ContentLengthLimitMiddleware<S>
where
S: Service<ServiceRequest, Response = ServiceResponse<B>, Error = Error>,
S::Future: 'static,
B: 'static,
{
fn is_big(&self, req: &ServiceRequest) -> Result<bool, ()> {
Ok(req
.headers()
.get("content-length")
.ok_or(())?
.to_str()
.map_err(|_| ())?
.parse::<u64>()
.map_err(|_| ())?
> self.limit)
}
}
impl<S, B> Service<ServiceRequest> for ContentLengthLimitMiddleware<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 {
if let Ok(r) = self.is_big(&req) {
if r {
return Box::pin(async { Err(hje("error").actix()) });
}
}
let fut = self.service.call(req);
Box::pin(async move {
let res = fut.await?;
Ok(res)
})
}
}
use
App::new().wrap(ContentLengthLimit::default())

This posted answer refers to the multipart upload example within Actix. Currently, due to:
https://github.com/actix/actix-web/issues/2695
It's necessary to drain the payload before returning an error, or the connection may hang or the thread may panic. Here an example that will drain the payload first, prior to returning a PayloadTooLarge error. That may not be necessary once the aforementioned is resolved:
lazy_static! {
pub static ref MAX_ALLOWED_CONTENT_LENGTH: u64 =
settings::get_setting("service_max_content_length")
.unwrap_or("20971520".to_owned())
.parse::<u64>()
.unwrap_or(20971520);
}
#[derive(Debug)]
pub struct ContentLengthChecker;
impl<S, B> Transform<S, ServiceRequest> for ContentLengthChecker
where
S: Service<ServiceRequest, Response = ServiceResponse<B>, Error = Error> + 'static,
S::Future: 'static,
B: 'static,
{
type Response = ServiceResponse<EitherBody<B>>;
type Error = Error;
type Transform = ContentLengthCheckerMiddleware<S>;
type InitError = ();
type Future = Ready<Result<Self::Transform, Self::InitError>>;
fn new_transform(&self, service: S) -> Self::Future {
ok(ContentLengthCheckerMiddleware {
service: Rc::new(service),
})
}
}
#[derive(Debug)]
pub struct ContentLengthCheckerMiddleware<S> {
service: Rc<S>,
}
impl<S, B> Service<ServiceRequest> for ContentLengthCheckerMiddleware<S>
where
S: Service<ServiceRequest, Response = ServiceResponse<B>, Error = Error> + 'static,
S::Future: 'static,
B: 'static,
{
type Response = ServiceResponse<EitherBody<B>>;
type Error = Error;
type Future = Pin<Box<dyn Future<Output = Result<Self::Response, Self::Error>>>>;
forward_ready!(service);
fn call(&self, mut req: ServiceRequest) -> Self::Future {
let service = Rc::clone(&self.service);
let content_length = match req.headers().get("content-length") {
None => 0,
Some(content_length) => MiddlewareUtility::get_content_length(Some(content_length)),
};
if content_length <= *MAX_ALLOWED_CONTENT_LENGTH {
return Box::pin(async move {
service
.call(req)
.await
.map(ServiceResponse::map_into_left_body)
});
}
Box::pin(async move {
/* need to drain the body due to
https://github.com/actix/actix-web/issues/2695
*/
let mut payload = req.take_payload();
while let Ok(Some(_)) = payload.try_next().await {}
Ok(req.into_response(
HttpResponse::new(StatusCode::PAYLOAD_TOO_LARGE).map_into_right_body(),
))
})
}
}

Related

actix-web: Add data to request in middleware

I am learning actix-web, I parsed jwt in middleware, I want to pass the data in jwt to the controller that parses to handle this request, but I don't know how to do it
my middleware:
use actix_web::{error::ErrorUnauthorized, Error};
use std::future::{ready, Ready};
use actix_web::dev::{forward_ready, Service, ServiceRequest, ServiceResponse, Transform};
use futures_util::future::LocalBoxFuture;
pub struct JWTAuth;
impl<S, B> Transform<S, ServiceRequest> for JWTAuth
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 = JWTAuthHiMiddleware<S>;
type Future = Ready<Result<Self::Transform, Self::InitError>>;
fn new_transform(&self, service: S) -> Self::Future {
ready(Ok(JWTAuthHiMiddleware {
service,
verification_path: vec!["/api"],
noverification_path: vec!["/api/auth"],
}))
}
}
pub struct JWTAuthHiMiddleware<S> {
service: S,
verification_path: Vec<&'static str>,
noverification_path: Vec<&'static str>,
}
impl<S, B> JWTAuthHiMiddleware<S>
where
S: Service<ServiceRequest, Response = ServiceResponse<B>, Error = Error>,
S::Future: 'static,
B: 'static,
{
fn is_need_verification(&self, path: &str) -> bool {
self.verification_path
.iter()
.any(|&vp| path.starts_with(vp))
&& !self
.noverification_path
.iter()
.any(|&vp| path.starts_with(vp))
}
}
impl<S, B> Service<ServiceRequest> for JWTAuthHiMiddleware<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 {
if self.is_need_verification(req.path()) {
let authorization = req.headers().get("Authorization");
if authorization.is_none() {
return Box::pin(async { Err(ErrorUnauthorized("err")) });
}
let authorization = authorization.unwrap().to_str();
if authorization.is_err() {
return Box::pin(async { Err(ErrorUnauthorized("err")) });
}
let authorization = authorization.unwrap();
let token = &authorization[7..]; // 'Bearer ' + token
let token_data = crate::utils::jwt::Claims::decode(token);
if let Err(err) = token_data {
return Box::pin(async { Err(ErrorUnauthorized(err)) });
}
let token_data = token_data.unwrap();
// I need to pass this user_id to the next Handle
println!("user_id: {}", &token_data.claims.user_id);
}
let fut = self.service.call(req);
Box::pin(async move {
let res = fut.await?;
Ok(res)
})
}
}
I want to get this user_id data in the controller
#[get("/user")]
pub async fn list(req: actix_web::HttpRequest, pool: web::Data<DbPool>) -> HttpResult {
// This is the way I guess, the right way I don't know how to do it
req.user_id;
Ok(HttpResponse::Ok().json(req.user_id))
}
Refer to this example
https://github.com/actix/examples/tree/master/middleware/middleware-ext-mut
let token_data = token_data.unwrap();
req.extensions_mut().insert(token_data.claims);
pub async fn list(claims: Option<web::ReqData<Claims>>, pool: web::Data<DbPool>) -> HttpResult {
if let Some(claims) = claims {
log::info!("user_id: {:?}", claims.user_id);
}
...
}

How can I return an Arc<Vec<u8>> as a Hyper response?

I am writing a server that allocates some compressed data on startup. Now when I serve a hyper response I do not want to copy these bytes, but I cannot figure out a way to do this with hyper.
I have tried implementing HttpBody for my own type, but the lifetime restriction on the trait blocks me from doing this.
Am I missing something? Here is a minimal example of what I am trying to do:
use hyper::{service::Service, Body, Request, Response, Server};
use std::net::SocketAddr;
use std::sync::Arc;
use std::{
future::Future,
pin::Pin,
task::{Context, Poll},
};
fn main() {
let addr = SocketAddr::new("0.0.0.0".parse().unwrap(), 8080);
println!("Server startup...");
let rt = tokio::runtime::Runtime::new().unwrap();
rt.block_on(async {
let app = MakeSvc::new().await;
let ret = app.clone();
let server = Server::bind(&addr).serve(app);
println!("Running on {}", &addr);
server.await.unwrap();
})
}
#[derive(Debug, Clone)]
pub struct WrapperApp {
pub cache: Arc<Vec<u8>>,
}
impl WrapperApp {
//Let's say I allocate some bytes here.
async fn new() -> Self {
Self {
cache: Arc::new(Vec::new()),
}
}
}
impl Service<Request<Body>> for WrapperApp {
type Response = Response<Body>;
type Error = hyper::Error;
type Future = Pin<Box<dyn Future<Output = Result<Self::Response, Self::Error>> + Send>>;
fn poll_ready(&mut self, _: &mut Context) -> Poll<Result<(), Self::Error>> {
Poll::Ready(Ok(()))
}
fn call(&mut self, req: Request<Body>) -> Self::Future {
let a = Arc::clone(&self.cache);
return Box::pin(async { Ok(Response::builder().body(Body::from(a)).unwrap()) });
}
}
#[derive(Debug, Clone)]
pub struct MakeSvc {
app: WrapperApp,
}
impl MakeSvc {
pub async fn new() -> Self {
Self {
app: WrapperApp::new().await,
}
}
}
impl<T> Service<T> for MakeSvc {
type Response = WrapperApp;
type Error = hyper::Error;
type Future = Pin<Box<dyn Future<Output = Result<Self::Response, Self::Error>> + Send>>;
fn poll_ready(&mut self, _: &mut Context) -> Poll<Result<(), Self::Error>> {
Poll::Ready(Ok(()))
}
fn call(&mut self, _: T) -> Self::Future {
let app = self.app.clone();
let fut = async move { Ok(app) };
Box::pin(fut)
}
}
error[E0277]: the trait bound `Body: From<Arc<Vec<u8>>>` is not satisfied
--> src/main.rs:50:61
|
50 | return Box::pin(async { Ok(Response::builder().body(Body::from(a)).unwrap()) });
| ^^^^^^^^^^ the trait `From<Arc<Vec<u8>>>` is not implemented for `Body`
|
= help: the following implementations were found:
<Body as From<&'static [u8]>>
<Body as From<&'static str>>
<Body as From<Box<(dyn futures_core::stream::Stream<Item = std::result::Result<hyper::body::Bytes, Box<(dyn std::error::Error + Send + Sync + 'static)>>> + Send + 'static)>>>
<Body as From<Cow<'static, [u8]>>>
and 4 others
= note: required by `from`
The Cargo.toml that goes with this. The example breaks at the place where I am trying to use the ref:
[package]
name = "hyper-ptr"
version = "0.1.0"
authors = ["Pierre Laas <lanklaas123#gmail.com>"]
edition = "2018"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
hyper={version="0.14", features=["server","http1","tcp"]}
tokio={version="1.0.0", features=["macros","io-util","rt-multi-thread"]}
serde={version="*", features=["derive","rc"]}
serde_json="*"
flate2="*"
openssl="*"
rand="*"
From the documentation, there is no From implementation that doesn't require either 'static references or ownership of the body.
Instead, you can clone the cache. Body::From's implementation requires ownership of the Vec<u8> or static data (if that is useful for your case).
Notice that you need to dereference it first:
use hyper::Body; // 0.14.2
use std::sync::Arc;
const FOO: [u8; 1] = [8u8];
fn main() {
let cache = Arc::new(vec![0u8]);
let body = Body::from((*cache).clone());
let other_body = Body::from(&FOO[..]);
println!("{:?}", cache);
}
Playground

Using a custom transporter for Rust's hyper http crate

ps: the answer below helped but it's not the answer I need, I have a new problem and I edited the question
I'm trying to make a custom transporter for the hyper http crate, so I can transport http packets in my own way.
Hyper's http client can be passed a custom https://docs.rs/hyper/0.14.2/hyper/client/connect/trait.Connect.html here:
pub fn build<C, B>(&self, connector: C) -> Client<C, B> where C: Connect + Clone, B: HttpBody + Send, B::Data: Send,
If we look at
impl<S, T> Connect for S where
S: Service<Uri, Response = T> + Send + 'static,
S::Error: Into<Box<dyn StdError + Send + Sync>>,
S::Future: Unpin + Send,
T: AsyncRead + AsyncWrite + Connection + Unpin + Send + 'static,
the type T, which is the type of the Response, must implement AsyncRead + AsyncWrite, so I've chosen type Response = Cursor<Vec<u8>>.
Here's my custom transporter with a Response of type std::io::Cursor wrapped in CustomResponse so I can implement AsyncWrite and AsyncRead to it:
use hyper::service::Service;
use core::task::{Context, Poll};
use core::future::Future;
use std::pin::Pin;
use std::io::Cursor;
use hyper::client::connect::{Connection, Connected};
use tokio::io::{AsyncRead, AsyncWrite};
#[derive(Clone)]
pub struct CustomTransporter;
unsafe impl Send for CustomTransporter {}
impl CustomTransporter {
pub fn new() -> CustomTransporter {
CustomTransporter{}
}
}
impl Connection for CustomTransporter {
fn connected(&self) -> Connected {
Connected::new()
}
}
pub struct CustomResponse {
//w: Cursor<Vec<u8>>,
v: Vec<u8>,
i: i32
}
unsafe impl Send for CustomResponse {
}
impl Connection for CustomResponse {
fn connected(&self) -> Connected {
println!("connected");
Connected::new()
}
}
impl AsyncRead for CustomResponse {
fn poll_read(
mut self: Pin<&mut Self>,
cx: &mut Context<'_>,
buf: &mut tokio::io::ReadBuf<'_>
) -> Poll<std::io::Result<()>> {
self.i+=1;
if self.i >=3 {
println!("poll_read for buf size {}", buf.capacity());
buf.put_slice(self.v.as_slice());
println!("did poll_read");
Poll::Ready(Ok(()))
} else {
println!("poll read pending, i={}", self.i);
Poll::Pending
}
}
}
impl AsyncWrite for CustomResponse {
fn poll_write(
mut self: Pin<&mut Self>,
cx: &mut Context<'_>,
buf: &[u8]
) -> Poll<Result<usize, std::io::Error>>{
//let v = vec!();
println!("poll_write____");
let s = match std::str::from_utf8(buf) {
Ok(v) => v,
Err(e) => panic!("Invalid UTF-8 sequence: {}", e),
};
println!("result: {}, size: {}, i: {}", s, s.len(), self.i);
if self.i>=0{
//r
Poll::Ready(Ok(s.len()))
}else{
println!("poll_write pending");
Poll::Pending
}
}
fn poll_flush(
mut self: Pin<&mut Self>,
cx: &mut Context<'_>
) -> Poll<Result<(), std::io::Error>> {
println!("poll_flush");
if self.i>=0{
println!("DID poll_flush");
Poll::Ready(Ok(()))
}else{
println!("poll_flush pending");
Poll::Pending
}
}
fn poll_shutdown(
mut self: Pin<&mut Self>,
cx: &mut Context<'_>
) -> Poll<Result<(), std::io::Error>>
{
println!("poll_shutdown");
Poll::Ready(Ok(()))
}
}
impl Service<hyper::Uri> for CustomTransporter {
type Response = CustomResponse;
type Error = hyper::http::Error;
type Future = Pin<Box<dyn Future<Output = Result<Self::Response, Self::Error>> + Send>>;
fn poll_ready(&mut self, cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
println!("poll_ready");
Poll::Ready(Ok(()))
//Poll::Pending
}
fn call(&mut self, req: hyper::Uri) -> Self::Future {
println!("call");
// create the body
let body: Vec<u8> = "HTTP/1.1 200 OK\nDate: Mon, 27 Jul 2009 12:28:53 GMT\nServer: Apache/2.2.14 (Win32)\nLast-Modified: Wed, 22 Jul 2009 19:15:56 GMT\nContent-Length: 88\nContent-Type: text/html\nConnection: Closed<html><body><h1>Hello, World!</h1></body></html>".as_bytes()
.to_owned();
// Create the HTTP response
let resp = CustomResponse{
//w: Cursor::new(body),
v: body,
i: 0
};
// create a response in a future.
let fut = async move{
Ok(resp)
};
println!("gonna return from call");
// Return the response as an immediate future
Box::pin(fut)
}
}
Then I use it like this:
let connector = CustomTransporter::new();
let client: Client<CustomTransporter, hyper::Body> = Client::builder().build(connector);
let mut res = client.get(url).await.unwrap();
However, it gets stuck and hyper never reads my response, but it writes the GET to it.
Here's a complete project for testing: https://github.com/lzunsec/rust_hyper_custom_transporter/blob/39cd036fc929057d975a71969ccbe97312543061/src/custom_req.rs
RUn like this:
cargo run http://google.com
I cannot simply implement Send to Future, and I cannot change Future by a wrapper. What should I do here?
It looks like the problem is your Service::Future is missing the Send constraint. The future being returned in call is already Send so it will work with the simple change:
impl Service<hyper::Uri> for CustomTransporter {
type Response = CustomResponse;
type Error = hyper::http::Error;
type Future = Pin<Box<dyn Future<Output = Result<Self::Response, Self::Error>> + Send>>;
// ^^^^
...
Your code has a few other errors: un-inferred vec!(), self: Pin<...> missing mut, CustomResponse should be pub...
You can specify the B of client by using inference:
let client: Client<CustomTransporter, hyper::Body> = Client::builder().build(connector);
Or by using the turbofish operator on build:
let client = Client::builder().build::<CustomTransporter, hyper::Body>(connector);
I don't know enough about creating custom hyper transports to know if its functional, but these fixes make it compile. Hopefully it helps you make progress.

How to read a request's body in an actix-web 1.0 middleware?

I want to read out the body in a middleware in actix-web 1.0. I'm using the closure-style middleware using wrap_fn.
My basic setup is this:
let mut server = HttpServer::new(move || {
ActixApp::new()
.wrap_fn(|req, srv| {
srv.call(req).map(|res| {
let req_ = res.request();
let body = req_.magical_body_read_function();
dbg!(body);
res
})
})
});
I need that magical_body_read_function() which sadly doesn't exist.
I hacked together something that looks like it could work by reading the examples and using take_payload() but it didn't work, sadly:
let mut server = HttpServer::new(move || {
ActixApp::new()
.wrap_fn(|req, srv| {
srv.call(req).map(|res| {
let req_ = res.request();
req_.take_payload()
.fold(BytesMut::new(), move |mut body, chunk| {
body.extend_from_slice(&chunk);
Ok::<_, PayloadError>(body)
})
.and_then(|bytes| {
info!("request body: {:?}", bytes);
});
res
})
})
});
Gives me
error[E0599]: no method named `fold` found for type `actix_http::payload::Payload<()>` in the current scope --> src/main.rs:209:26
| 209 | .fold(BytesMut::new(), move |mut body, chunk| {
| ^^^^
|
= note: the method `fold` exists but the following trait bounds were not satisfied:
`&mut actix_http::payload::Payload<()> : std::iter::Iterator`
I then tried an approach using the full middleware:
pub struct Logging;
impl<S, B> Transform<S> for Logging
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 = LoggingMiddleware<S>;
type Future = FutureResult<Self::Transform, Self::InitError>;
fn new_transform(&self, service: S) -> Self::Future {
ok(LoggingMiddleware { service })
}
}
pub struct LoggingMiddleware<S> {
service: S,
}
impl<S, B> Service for LoggingMiddleware<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 = Box<dyn Future<Item = Self::Response, Error = Self::Error>>;
fn poll_ready(&mut self) -> Poll<(), Self::Error> {
self.service.poll_ready()
}
fn call(&mut self, req: ServiceRequest) -> Self::Future {
Box::new(self.service.call(req).and_then(|res| {
let req_ = res.request();
req_.take_payload()
.fold(BytesMut::new(), move |mut body, chunk| {
body.extend_from_slice(&chunk);
Ok::<_, PayloadError>(body)
})
.and_then(|bytes| {
info!("request body: {:?}", bytes);
});
Ok(res)
}))
}
}
which sadly also resulted in the very similar looking error:
error[E0599]: no method named `fold` found for type `actix_http::payload::Payload<()>` in the current scope
--> src/main.rs:204:18
|
204 | .fold(BytesMut::new(), move |mut body, chunk| {
| ^^^^
|
= note: the method `fold` exists but the following trait bounds were not satisfied:
`&mut actix_http::payload::Payload<()> : futures::stream::Stream`
`&mut actix_http::payload::Payload<()> : std::iter::Iterator`
`actix_http::payload::Payload<()> : futures::stream::Stream`
With the help of the fine people in the actix-web Gitter channel, I came to this solution which I also made a PR for.
Full solution is:
pub struct Logging;
impl<S: 'static, B> Transform<S> for Logging
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 = LoggingMiddleware<S>;
type Future = FutureResult<Self::Transform, Self::InitError>;
fn new_transform(&self, service: S) -> Self::Future {
ok(LoggingMiddleware {
service: Rc::new(RefCell::new(service)),
})
}
}
pub struct LoggingMiddleware<S> {
// This is special: We need this to avoid lifetime issues.
service: Rc<RefCell<S>>,
}
impl<S, B> Service for LoggingMiddleware<S>
where
S: Service<Request = ServiceRequest, Response = ServiceResponse<B>, Error = Error>
+ 'static,
S::Future: 'static,
B: 'static,
{
type Request = ServiceRequest;
type Response = ServiceResponse<B>;
type Error = Error;
type Future = Box<dyn Future<Item = Self::Response, Error = Self::Error>>;
fn poll_ready(&mut self) -> Poll<(), Self::Error> {
self.service.poll_ready()
}
fn call(&mut self, mut req: ServiceRequest) -> Self::Future {
let mut svc = self.service.clone();
Box::new(
req.take_payload()
.fold(BytesMut::new(), move |mut body, chunk| {
body.extend_from_slice(&chunk);
Ok::<_, PayloadError>(body)
})
.map_err(|e| e.into())
.and_then(move |bytes| {
println!("request body: {:?}", bytes);
svc.call(req).and_then(|res| Ok(res))
}),
)
}
}
Building on svenstaro's solution you can do something like the following to rebuild the request after cloning the stripped bytes.
fn call(&mut self, mut req: ServiceRequest) -> Self::Future {
let mut svc = self.service.clone();
Box::new(
req.take_payload()
.fold(BytesMut::new(), move |mut body, chunk| {
body.extend_from_slice(&chunk);
Ok::<_, PayloadError>(body)
})
.map_err(|e| e.into())
.and_then(move |bytes| {
println!("request body: {:?}", bytes);
let mut payload = actix_http::h1::Payload::empty();
payload.unread_data(bytes.into());
req.set_payload(payload.into());
svc.call(req).and_then(|res| Ok(res))
}),
)
}
Payload implements Stream<Item = Bytes, Error = _>, so there is no reason you cannot use the same trick as for other frameworks:
req_
.take_payload().concat2()
.and_then(|bytes| {
info!("request body: {:?}", bytes);
});
That is, if you had a proper Payload from a POST/PUT request. Since you've used wrap_fn(), you've effectively set up a middleware. Those run across all requests, and do not allow you access to the Payload (partly because you can only take it once).
As such, you're out of luck, I think.

Implementing hyper::client::Connect for testing

I'm trying to test some code that uses a hyper::Client by implementing my own hyper::client::Connect using a static response. I've got the types figured out, but can't figure out a runtime issue where tokio-proto complains saying request / response mismatch. Here's a simplified version of my code that demonstrates the failure:
extern crate futures;
extern crate hyper;
extern crate tokio_core;
extern crate tokio_io;
use futures::{future, Future, Stream};
use std::str::from_utf8;
use std::io::Cursor;
struct Client<'a, C: 'a> {
client: &'a hyper::Client<C>,
url: &'a str,
}
impl<'a, C: hyper::client::Connect> Client<'a, C> {
fn get(&self) -> Box<Future<Item = String, Error = hyper::Error>> {
Box::new(self.client.get(self.url.parse().unwrap()).and_then(|res| {
let body = Vec::new();
res.body()
.fold(body, |mut acc, chunk| {
acc.extend_from_slice(chunk.as_ref());
Ok::<_, hyper::Error>(acc)
})
.and_then(move |value| Ok(String::from(from_utf8(&value).unwrap())))
}))
}
}
struct StaticConnector<'a> {
body: &'a [u8],
}
impl<'a> StaticConnector<'a> {
fn new(body: &'a [u8]) -> StaticConnector {
StaticConnector { body: body }
}
}
impl<'a> hyper::server::Service for StaticConnector<'a> {
type Request = hyper::Uri;
type Response = Cursor<Vec<u8>>;
type Error = std::io::Error;
type Future = Box<Future<Item = Self::Response, Error = Self::Error>>;
fn call(&self, _: Self::Request) -> Self::Future {
Box::new(future::ok(Cursor::new(self.body.to_vec())))
}
}
fn main() {
let mut core = tokio_core::reactor::Core::new().unwrap();
let handle = core.handle();
// My StaticConnector for testing
let hyper_client = hyper::Client::configure()
.connector(StaticConnector::new(
b"\
HTTP/1.1 200 OK\r\n\
Content-Length: 8\r\n\
\r\n\
Maldives\
",
))
.build(&handle);
// Real Connector
/*
let hyper_client = hyper::Client::configure().build(&handle);
*/
let client = Client {
client: &hyper_client,
url: "http://ifconfig.co/country",
};
let result = core.run(client.get()).unwrap();
println!("{}", result);
}
Playground
I'm guessing it's my use of the Cursor for Io that is incomplete in some way, but I'm failing to debug and make progress. One thought is that the writes to this Cursor the hyper::Client presumably makes are not working as expected. Maybe I need a combination of a sink for the writes and the static content for the reads? All ideas I've failed to make progress using!
The reason the original code didn't work was because the reader side provided the response before the client sent the request, so tokio-proto errored out with request / response mismatch. The fix turns out to be non trivial in that first we need to arrange for the reader to block, or more specifically error out with std::io::ErrorKind::WouldBlock to indicate to the event loop that there isn't anything yet, but don't consider it an EOF. Additionally once we get the write which indicates the request has been sent and the tokio-proto machinery is waiting for a response, we use futures::task::current.notify to unblock the read. Here's an updated implementation that works as expected:
extern crate futures;
extern crate hyper;
extern crate tokio_core;
extern crate tokio_io;
use futures::{future, Future, Stream, task, Poll};
use std::str::from_utf8;
use std::io::{self, Cursor, Read, Write};
use tokio_io::{AsyncRead, AsyncWrite};
struct Client<'a, C: 'a> {
client: &'a hyper::Client<C>,
url: &'a str,
}
impl<'a, C: hyper::client::Connect> Client<'a, C> {
fn get(&self) -> Box<Future<Item = String, Error = hyper::Error>> {
Box::new(self.client.get(self.url.parse().unwrap()).and_then(|res| {
let body = Vec::new();
res.body()
.fold(body, |mut acc, chunk| {
acc.extend_from_slice(chunk.as_ref());
Ok::<_, hyper::Error>(acc)
})
.and_then(move |value| Ok(String::from(from_utf8(&value).unwrap())))
}))
}
}
struct StaticStream {
wrote: bool,
body: Cursor<Vec<u8>>,
}
impl Read for StaticStream {
fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
if self.wrote {
self.body.read(buf)
} else {
Err(io::ErrorKind::WouldBlock.into())
}
}
}
impl Write for StaticStream {
fn write<'a>(&mut self, buf: &'a [u8]) -> io::Result<usize> {
self.wrote = true;
task::current().notify();
Ok(buf.len())
}
fn flush(&mut self) -> io::Result<()> {
Ok(())
}
}
impl AsyncRead for StaticStream {}
impl AsyncWrite for StaticStream {
fn shutdown(&mut self) -> Poll<(), io::Error> {
Ok(().into())
}
}
struct StaticConnector<'a> {
body: &'a [u8],
}
impl<'a> StaticConnector<'a> {
fn new(body: &'a [u8]) -> StaticConnector {
StaticConnector { body: body }
}
}
impl<'a> hyper::server::Service for StaticConnector<'a> {
type Request = hyper::Uri;
type Response = StaticStream;
type Error = std::io::Error;
type Future = Box<Future<Item = Self::Response, Error = Self::Error>>;
fn call(&self, _: Self::Request) -> Self::Future {
Box::new(future::ok(StaticStream {
wrote: false,
body: Cursor::new(self.body.to_vec()),
}))
}
}
fn main() {
let mut core = tokio_core::reactor::Core::new().unwrap();
let handle = core.handle();
// My StaticConnector for testing
let hyper_client = hyper::Client::configure()
.connector(StaticConnector::new(
b"\
HTTP/1.1 200 OK\r\n\
Content-Length: 8\r\n\
\r\n\
Maldives\
",
))
.build(&handle);
// Real Connector
/*
let hyper_client = hyper::Client::configure().build(&handle);
*/
let client = Client {
client: &hyper_client,
url: "http://ifconfig.co/country",
};
let result = core.run(client.get()).unwrap();
println!("{}", result);
}
Playground
Note: This implementation works for simple cases, but I haven't tested more complex scenarios. For example one thing I'm unsure of is how large request/responses behave as they may involve more than 1 read/write call.

Resources