]>
Commit | Line | Data |
---|---|---|
5eb9dd0c SR |
1 | use anyhow::{Error, format_err, bail}; |
2 | use lazy_static::lazy_static; | |
3 | use std::task::{Context, Poll}; | |
4 | use std::os::unix::io::AsRawFd; | |
137a6ebc | 5 | use std::collections::HashMap; |
5eb9dd0c SR |
6 | |
7 | use hyper::{Uri, Body}; | |
8 | use hyper::client::{Client, HttpConnector}; | |
2e201e7d | 9 | use http::{Request, Response}; |
5eb9dd0c SR |
10 | use openssl::ssl::{SslConnector, SslMethod}; |
11 | use futures::*; | |
12 | ||
13 | use crate::tools::{ | |
14 | async_io::EitherStream, | |
15 | socket::{ | |
16 | set_tcp_keepalive, | |
17 | PROXMOX_BACKUP_TCP_KEEPALIVE_TIME, | |
18 | }, | |
19 | }; | |
20 | ||
21 | lazy_static! { | |
22 | static ref HTTP_CLIENT: Client<HttpsConnector, Body> = { | |
23 | let connector = SslConnector::builder(SslMethod::tls()).unwrap().build(); | |
24 | let httpc = HttpConnector::new(); | |
25 | let https = HttpsConnector::with_connector(httpc, connector); | |
26 | Client::builder().build(https) | |
27 | }; | |
28 | } | |
29 | ||
137a6ebc SR |
30 | pub async fn get_string(uri: &str, extra_headers: Option<&HashMap<String, String>>) -> Result<String, Error> { |
31 | let mut request = Request::builder() | |
32 | .method("GET") | |
33 | .uri(uri) | |
34 | .header("User-Agent", "proxmox-backup-client/1.0"); | |
35 | ||
36 | if let Some(hs) = extra_headers { | |
37 | for (h, v) in hs.iter() { | |
38 | request = request.header(h, v); | |
39 | } | |
40 | } | |
41 | ||
42 | let request = request.body(Body::empty())?; | |
43 | ||
44 | let res = HTTP_CLIENT.request(request).await?; | |
5eb9dd0c SR |
45 | |
46 | let status = res.status(); | |
47 | if !status.is_success() { | |
48 | bail!("Got bad status '{}' from server", status) | |
49 | } | |
50 | ||
2e201e7d TL |
51 | response_body_string(res).await |
52 | } | |
53 | ||
54 | pub async fn response_body_string(res: Response<Body>) -> Result<String, Error> { | |
5eb9dd0c SR |
55 | let buf = hyper::body::to_bytes(res).await?; |
56 | String::from_utf8(buf.to_vec()) | |
57 | .map_err(|err| format_err!("Error converting HTTP result data: {}", err)) | |
58 | } | |
59 | ||
2e201e7d TL |
60 | pub async fn post( |
61 | uri: &str, | |
62 | body: Option<String>, | |
63 | content_type: Option<&str>, | |
64 | ) -> Result<Response<Body>, Error> { | |
65 | let body = if let Some(body) = body { | |
66 | Body::from(body) | |
67 | } else { | |
68 | Body::empty() | |
69 | }; | |
70 | let content_type = content_type.unwrap_or("application/json"); | |
71 | ||
72 | let request = Request::builder() | |
73 | .method("POST") | |
74 | .uri(uri) | |
75 | .header("User-Agent", "proxmox-backup-client/1.0") | |
76 | .header(hyper::header::CONTENT_TYPE, content_type) | |
77 | .body(body)?; | |
78 | ||
79 | ||
80 | HTTP_CLIENT.request(request) | |
81 | .map_err(Error::from) | |
82 | .await | |
83 | } | |
84 | ||
5eb9dd0c SR |
85 | #[derive(Clone)] |
86 | pub struct HttpsConnector { | |
87 | http: HttpConnector, | |
88 | ssl_connector: std::sync::Arc<SslConnector>, | |
89 | } | |
90 | ||
91 | impl HttpsConnector { | |
92 | pub fn with_connector(mut http: HttpConnector, ssl_connector: SslConnector) -> Self { | |
93 | http.enforce_http(false); | |
94 | ||
95 | Self { | |
96 | http, | |
97 | ssl_connector: std::sync::Arc::new(ssl_connector), | |
98 | } | |
99 | } | |
100 | } | |
101 | ||
102 | type MaybeTlsStream = EitherStream< | |
103 | tokio::net::TcpStream, | |
104 | tokio_openssl::SslStream<tokio::net::TcpStream>, | |
105 | >; | |
106 | ||
107 | impl hyper::service::Service<Uri> for HttpsConnector { | |
108 | type Response = MaybeTlsStream; | |
109 | type Error = Error; | |
110 | type Future = std::pin::Pin<Box< | |
111 | dyn Future<Output = Result<Self::Response, Self::Error>> + Send + 'static | |
112 | >>; | |
113 | ||
114 | fn poll_ready(&mut self, _: &mut Context<'_>) -> Poll<Result<(), Self::Error>> { | |
115 | // This connector is always ready, but others might not be. | |
116 | Poll::Ready(Ok(())) | |
117 | } | |
118 | ||
119 | fn call(&mut self, dst: Uri) -> Self::Future { | |
120 | let mut this = self.clone(); | |
121 | async move { | |
122 | let is_https = dst | |
123 | .scheme() | |
124 | .ok_or_else(|| format_err!("missing URL scheme"))? | |
125 | == "https"; | |
126 | let host = dst | |
127 | .host() | |
128 | .ok_or_else(|| format_err!("missing hostname in destination url?"))? | |
129 | .to_string(); | |
130 | ||
131 | let config = this.ssl_connector.configure(); | |
5d08c750 WB |
132 | let dst_str = dst.to_string(); // for error messages |
133 | let conn = this | |
134 | .http | |
135 | .call(dst) | |
136 | .await | |
137 | .map_err(|err| format_err!("error connecting to {} - {}", dst_str, err))?; | |
5eb9dd0c SR |
138 | |
139 | let _ = set_tcp_keepalive(conn.as_raw_fd(), PROXMOX_BACKUP_TCP_KEEPALIVE_TIME); | |
140 | ||
141 | if is_https { | |
142 | let conn = tokio_openssl::connect(config?, &host, conn).await?; | |
143 | Ok(MaybeTlsStream::Right(conn)) | |
144 | } else { | |
145 | Ok(MaybeTlsStream::Left(conn)) | |
146 | } | |
147 | }.boxed() | |
148 | } | |
149 | } |