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);
}
...
}
Related
In docs,handler gets called if the query deserializes into Info successfully, otherwise a 400 Bad Request error response is returned. How to customize the error response?
use actix_web::{get, web, App, HttpServer};
use serde::Deserialize;
#[derive(Deserialize)]
struct Info {
username: String,
}
// this handler gets called if the query deserializes into `Info` successfully
// otherwise a 400 Bad Request error response is returned
#[get("/")]
async fn index(info: web::Query<Info>) -> String {
format!("Welcome {}!", info.username)
}
Well the key thing is the FromRequest implementation.
You have two options.
Define your own FromRequest
struct MyQuery(Info);
impl FromRequest for MyQuery {
type Error = Error; // <--- Your error type here
type Future = Pin<Box<dyn Future<Output = Result<Self, Error>>>>;
fn from_request(req: &HttpRequest, pl: &mut Payload) -> Self::Future {
let fut = web::Query<Info>::from_request(req, pl);
Box::pin(async move {
if let Some(info) = fut.await? {
....
return Ok(MyQuery(info));
}
Err(Error("my error"))
})
}
#[get("/")]
async fn index(info: MyQuery) -> String {
format!("Welcome {}!", info.0.username)
}
Get the data from Request
#[get("/")]
async fn index(req: Request) -> Result<String, Error> {
let query = Query::<HashMap<String,String>>::from_query(req.query_string()).unwrap();
let username = query.get("username")?;
Ok(format!("Welcome {}!", username))
}
Another variant using QueryConfig.
In my example it renders it in json with status code and description.
It also handles all query deserialization errors from all routes.
// Your custom error struct
#[derive(Debug, Serialize)]
pub struct CustomError {
pub code: u16,
pub description: String,
}
// Handy constructor
impl CustomError {
pub fn new(code: StatusCode, description: &str) -> Self {
CustomError {
code: code.as_u16(),
description: description.into(),
}
}
}
// Important: implement ResponseError to render as actix_web::Error
impl ResponseError for CustomError {
fn status_code(&self) -> StatusCode {
StatusCode::from_u16(self.code)
.unwrap_or(StatusCode::INTERNAL_SERVER_ERROR)
}
fn error_response(&self) -> HttpResponse {
HttpResponseBuilder::new(self.status_code())
.insert_header(header::ContentType(mime::APPLICATION_JSON))
.json(web::Json(self))
}
}
// Customize your error in the handler
fn query_error_handler(
err: QueryPayloadError,
_req: &HttpRequest,
) -> actix_web::Error {
CustomError::new(StatusCode::BAD_REQUEST, err.to_string().as_str()).into()
}
// Attach an error handler to QueryConfig when setting up the application
let server = HttpServer::new(move || {
App::new()
.app_data(
QueryConfig::default()
.error_handler(query_error_handler)
)
...
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(),
))
})
}
}
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
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.
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.