omaha_client_fuchsia/
http_request.rs

1// Copyright 2023 The Fuchsia Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5use fuchsia_async::TimeoutExt;
6use futures::future::BoxFuture;
7use futures::prelude::*;
8use hyper::client::ResponseFuture;
9use hyper::{Body, Client, Request, Response};
10use omaha_client::http_request::{Error, HttpRequest};
11use std::time::Duration;
12
13pub struct FuchsiaHyperHttpRequest {
14    timeout: Duration,
15    client: Client<hyper_rustls::HttpsConnector<fuchsia_hyper::HyperConnector>, Body>,
16}
17
18impl HttpRequest for FuchsiaHyperHttpRequest {
19    fn request(&mut self, req: Request<Body>) -> BoxFuture<'_, Result<Response<Vec<u8>>, Error>> {
20        // create the initial response future
21        let response = self.client.request(req);
22        let timeout = self.timeout;
23
24        collect_from_future(response).on_timeout(timeout, || Err(Error::new_timeout())).boxed()
25    }
26}
27
28// Helper to clarify the types of the futures involved
29async fn collect_from_future(response_future: ResponseFuture) -> Result<Response<Vec<u8>>, Error> {
30    let response = response_future.await.map_err(Error::from)?;
31    let (parts, body) = response.into_parts();
32    let bytes = hyper::body::to_bytes(body).await?;
33    Ok(Response::from_parts(parts, bytes.to_vec()))
34}
35
36impl FuchsiaHyperHttpRequest {
37    /// Construct a new client that uses a default timeout.
38    pub fn new() -> Self {
39        Self::using_timeout(Duration::from_secs(30))
40    }
41
42    /// Construct a new client which always uses the provided duration instead of the default
43    pub fn using_timeout(timeout: Duration) -> Self {
44        FuchsiaHyperHttpRequest { timeout, client: fuchsia_hyper::new_https_client() }
45    }
46}
47
48#[cfg(test)]
49mod tests {
50    use super::*;
51    use fuchsia_hyper_test_support::TestServer;
52    use fuchsia_hyper_test_support::fault_injection::{Hang, HangBody};
53    use fuchsia_hyper_test_support::handler::StaticResponse;
54
55    /// Helper that constructs a Request for a given path on the given test server.
56    fn make_request_for(server: &TestServer, path: &str) -> Request<Body> {
57        Request::builder().uri(server.local_url_for_path(path)).body(Body::empty()).unwrap()
58    }
59
60    /// Test that the HttpRequest implementation works against a simple server and returns
61    /// the expected response body.
62    #[fuchsia_async::run_singlethreaded(test)]
63    async fn test_simple_request() {
64        let server =
65            TestServer::builder().handler(StaticResponse::ok_body("some data")).start().await;
66        let mut client = FuchsiaHyperHttpRequest::using_timeout(Duration::from_secs(5));
67        let response = client.request(make_request_for(&server, "some/path")).await.unwrap();
68        let string = String::from_utf8(response.into_body()).unwrap();
69        assert_eq!(string, "some data");
70    }
71
72    /// Test that the HttpRequest implementation properly times out if the server doesn't return
73    /// a response over the socket after accepting the connection.
74    #[fuchsia_async::run_singlethreaded(test)]
75    async fn test_hang() {
76        let server = TestServer::builder().handler(Hang).start().await;
77        let mut client = FuchsiaHyperHttpRequest::using_timeout(Duration::from_secs(1));
78        let response = client.request(make_request_for(&server, "some/path")).await;
79        assert!(response.unwrap_err().is_timeout());
80    }
81
82    /// Test that the HttpRequest implementation properly times out if the server doesn't return
83    /// a the entire body that's expected (after returning a response header).
84    #[fuchsia_async::run_singlethreaded(test)]
85    async fn test_hang_body() {
86        let server = TestServer::builder().handler(HangBody::content_length(500)).start().await;
87        let mut client = FuchsiaHyperHttpRequest::using_timeout(Duration::from_secs(1));
88        let response = client.request(make_request_for(&server, "some/path")).await;
89        assert!(response.unwrap_err().is_timeout());
90    }
91}