blob: 9d9e93670e756e40ccd108efa465da5ceb9d6ace [file] [log] [blame]
// Copyright 2019 The Fuchsia Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
use {
fuchsia_async::TimeoutExt,
futures::{future::BoxFuture, prelude::*},
hyper::{client::ResponseFuture, Body, Client, Request, Response},
omaha_client::http_request::{Error, HttpRequest},
std::time::Duration,
};
pub struct FuchsiaHyperHttpRequest {
timeout: Duration,
client: Client<hyper_rustls::HttpsConnector<fuchsia_hyper::HyperConnector>, Body>,
}
impl HttpRequest for FuchsiaHyperHttpRequest {
fn request(&mut self, req: Request<Body>) -> BoxFuture<'_, Result<Response<Vec<u8>>, Error>> {
// create the initial response future
let response = self.client.request(req);
collect_from_future(response).on_timeout(self.timeout, || Err(Error::new_timeout())).boxed()
}
}
// Helper to clarify the types of the futures involved
async fn collect_from_future(response_future: ResponseFuture) -> Result<Response<Vec<u8>>, Error> {
let response = response_future.await.map_err(|e| Error::from(e))?;
let (parts, body) = response.into_parts();
let bytes = hyper::body::to_bytes(body).await?;
Ok(Response::from_parts(parts, bytes.to_vec()))
}
impl FuchsiaHyperHttpRequest {
/// Construct a new client that uses a default timeout.
pub fn new() -> Self {
Self::using_timeout(Duration::from_secs(30))
}
/// Construct a new client which always uses the provided duration instead of the default
pub fn using_timeout(timeout: Duration) -> Self {
FuchsiaHyperHttpRequest { timeout, client: fuchsia_hyper::new_https_client() }
}
}
#[cfg(test)]
mod tests {
use super::*;
use fuchsia_hyper_test_support::{
fault_injection::{Hang, HangBody},
handler::StaticResponse,
TestServer,
};
/// Helper that constructs a Request for a given path on the given test server.
fn make_request_for(server: &TestServer, path: &str) -> Request<Body> {
Request::builder().uri(server.local_url_for_path(path)).body(Body::empty()).unwrap()
}
/// Test that the HttpRequest implementation works against a simple server and returns
/// the expected response body.
#[fuchsia_async::run_singlethreaded(test)]
async fn test_simple_request() {
let server = TestServer::builder().handler(StaticResponse::ok_body("some data")).start();
let mut client = FuchsiaHyperHttpRequest::using_timeout(Duration::from_secs(5));
let response = client.request(make_request_for(&server, "some/path")).await.unwrap();
let string = String::from_utf8(response.into_body()).unwrap();
assert_eq!(string, "some data");
}
/// Test that the HttpRequest implementation properly times out if the server doesn't return
/// a response over the socket after accepting the connection.
#[fuchsia_async::run_singlethreaded(test)]
async fn test_hang() {
let server = TestServer::builder().handler(Hang).start();
let mut client = FuchsiaHyperHttpRequest::using_timeout(Duration::from_secs(1));
let response = client.request(make_request_for(&server, "some/path")).await;
assert!(response.unwrap_err().is_timeout());
}
/// Test that the HttpRequest implementation properly times out if the server doesn't return
/// a the entire body that's expected (after returning a response header).
#[fuchsia_async::run_singlethreaded(test)]
async fn test_hang_body() {
let server = TestServer::builder().handler(HangBody::content_length(500)).start();
let mut client = FuchsiaHyperHttpRequest::using_timeout(Duration::from_secs(1));
let response = client.request(make_request_for(&server, "some/path")).await;
assert!(response.unwrap_err().is_timeout());
}
}