]> git.proxmox.com Git - proxmox-backup.git/blame - src/tools/http.rs
tokio 1.0: use ReceiverStream from tokio-stream
[proxmox-backup.git] / src / tools / http.rs
CommitLineData
5eb9dd0c
SR
1use anyhow::{Error, format_err, bail};
2use lazy_static::lazy_static;
3use std::task::{Context, Poll};
4use std::os::unix::io::AsRawFd;
137a6ebc 5use std::collections::HashMap;
5eb9dd0c
SR
6
7use hyper::{Uri, Body};
8use hyper::client::{Client, HttpConnector};
2e201e7d 9use http::{Request, Response};
5eb9dd0c
SR
10use openssl::ssl::{SslConnector, SslMethod};
11use futures::*;
12
13use crate::tools::{
14 async_io::EitherStream,
15 socket::{
16 set_tcp_keepalive,
17 PROXMOX_BACKUP_TCP_KEEPALIVE_TIME,
18 },
19};
20
21lazy_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
30pub 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
54pub 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
60pub 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)]
86pub struct HttpsConnector {
87 http: HttpConnector,
88 ssl_connector: std::sync::Arc<SslConnector>,
89}
90
91impl 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
102type MaybeTlsStream = EitherStream<
103 tokio::net::TcpStream,
104 tokio_openssl::SslStream<tokio::net::TcpStream>,
105>;
106
107impl 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}