containers_api/conn/
transport.rs1use crate::conn::{Error, Headers, Payload, Result};
4
5use futures_util::{
6 stream::{self, Stream},
7 StreamExt,
8};
9use hyper::{
10 body::Bytes,
11 client::{Client, HttpConnector},
12 header, Body, Method, Request, Response,
13};
14#[cfg(feature = "tls")]
15use hyper_openssl::HttpsConnector;
16#[cfg(unix)]
17use hyperlocal::UnixConnector;
18#[cfg(unix)]
19use hyperlocal::Uri as DomainUri;
20use url::Url;
21
22use std::{iter::IntoIterator, path::PathBuf};
23
24#[derive(Clone, Debug)]
26pub enum Transport {
27 Tcp {
29 client: Client<HttpConnector>,
30 host: Url,
31 },
32 #[cfg(feature = "tls")]
34 #[cfg_attr(docsrs, doc(cfg(feature = "tls")))]
35 EncryptedTcp {
36 client: Client<HttpsConnector<HttpConnector>>,
37 host: Url,
38 },
39 #[cfg(unix)]
41 Unix {
42 client: Client<UnixConnector>,
43 path: PathBuf,
44 },
45}
46
47impl Transport {
48 pub fn remote_addr(&self) -> &str {
49 match &self {
50 Self::Tcp { ref host, .. } => host.as_ref(),
51 #[cfg(feature = "tls")]
52 Self::EncryptedTcp { ref host, .. } => host.as_ref(),
53 #[cfg(unix)]
54 Self::Unix { ref path, .. } => path.to_str().unwrap_or_default(),
55 }
56 }
57
58 pub fn make_uri(&self, ep: &str) -> Result<hyper::Uri> {
59 match self {
60 Transport::Tcp { host, .. } => format!("{host}{ep}").parse().map_err(Error::InvalidUri),
61 #[cfg(feature = "tls")]
62 Transport::EncryptedTcp { host, .. } => {
63 format!("{host}{ep}").parse().map_err(Error::InvalidUri)
64 }
65 #[cfg(unix)]
66 Transport::Unix { path, .. } => Ok(DomainUri::new(path, ep).into()),
67 }
68 }
69
70 pub async fn request(&self, req: Request<Body>) -> Result<Response<Body>> {
72 log::trace!("sending request {} {}", req.method(), req.uri());
73 match self {
74 Transport::Tcp { ref client, .. } => client.request(req),
75 #[cfg(feature = "tls")]
76 Transport::EncryptedTcp { ref client, .. } => client.request(req),
77 #[cfg(unix)]
78 Transport::Unix { ref client, .. } => client.request(req),
79 }
80 .await
81 .map_err(Error::from)
82 }
83
84 pub async fn request_string(&self, req: Request<Body>) -> Result<String> {
85 let body = self.request(req).await.map(|resp| resp.into_body())?;
86 body_to_string(body).await
87 }
88}
89
90pub(crate) async fn body_to_string(body: Body) -> Result<String> {
91 let bytes = hyper::body::to_bytes(body).await?;
92 String::from_utf8(bytes.to_vec()).map_err(Error::from)
93}
94
95pub(crate) fn build_request<B>(
97 method: Method,
98 uri: hyper::Uri,
99 body: Payload<B>,
100 headers: Option<Headers>,
101) -> Result<Request<Body>>
102where
103 B: Into<Body>,
104{
105 let builder = hyper::http::request::Builder::new();
106 let req = builder.method(method).uri(&uri);
107 let mut req = req.header(header::HOST, "");
108
109 if let Some(h) = headers {
110 for (k, v) in h.into_iter() {
111 req = req.header(k, v);
112 }
113 }
114
115 if body.is_none() {
117 return Ok(req.body(Body::empty())?);
118 }
119
120 let mime = body.mime_type();
121 if let Some(c) = mime {
122 req = req.header(header::CONTENT_TYPE, &c.to_string());
123 }
124
125 req.body(body.into_inner().unwrap().into())
127 .map_err(Error::from)
128}
129
130pub(crate) async fn get_response_string(response: Response<Body>) -> Result<String> {
131 body_to_string(response.into_body()).await
132}
133
134pub(crate) fn stream_response(response: Response<Body>) -> impl Stream<Item = Result<Bytes>> {
135 stream_body(response.into_body())
136}
137
138pub(crate) fn stream_json_response(response: Response<Body>) -> impl Stream<Item = Result<Bytes>> {
139 stream_json_body(response.into_body())
140}
141
142fn stream_body(body: Body) -> impl Stream<Item = Result<Bytes>> {
143 async fn unfold(mut body: Body) -> Option<(Result<Bytes>, Body)> {
144 body.next()
145 .await
146 .map(|chunk| (chunk.map_err(Error::from), body))
147 }
148
149 stream::unfold(body, unfold)
150}
151
152static JSON_WHITESPACE: &[u8] = b"\r\n";
153
154fn stream_json_body(body: Body) -> impl Stream<Item = Result<Bytes>> {
155 async fn unfold(mut body: Body) -> Option<(Result<Bytes>, Body)> {
156 let mut chunk = Vec::new();
157 while let Some(chnk) = body.next().await {
158 match chnk {
159 Ok(chnk) => {
160 chunk.extend(chnk.to_vec());
161 if chnk.ends_with(JSON_WHITESPACE) {
162 break;
163 }
164 }
165 Err(e) => {
166 return Some((Err(Error::from(e)), body));
167 }
168 }
169 }
170
171 if chunk.is_empty() {
172 return None;
173 }
174
175 Some((Ok(Bytes::from(chunk)), body))
176 }
177
178 stream::unfold(body, unfold)
179}