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