2020-10-21 09:41:11 +00:00
|
|
|
use anyhow::{Error, format_err, bail};
|
|
|
|
use lazy_static::lazy_static;
|
|
|
|
use std::task::{Context, Poll};
|
|
|
|
use std::os::unix::io::AsRawFd;
|
2020-11-09 09:35:28 +00:00
|
|
|
use std::collections::HashMap;
|
2020-10-21 09:41:11 +00:00
|
|
|
|
|
|
|
use hyper::{Uri, Body};
|
|
|
|
use hyper::client::{Client, HttpConnector};
|
2020-10-27 08:52:45 +00:00
|
|
|
use http::{Request, Response};
|
2020-10-21 09:41:11 +00:00
|
|
|
use openssl::ssl::{SslConnector, SslMethod};
|
|
|
|
use futures::*;
|
|
|
|
|
|
|
|
use crate::tools::{
|
|
|
|
async_io::EitherStream,
|
|
|
|
socket::{
|
|
|
|
set_tcp_keepalive,
|
|
|
|
PROXMOX_BACKUP_TCP_KEEPALIVE_TIME,
|
|
|
|
},
|
|
|
|
};
|
|
|
|
|
|
|
|
lazy_static! {
|
|
|
|
static ref HTTP_CLIENT: Client<HttpsConnector, Body> = {
|
|
|
|
let connector = SslConnector::builder(SslMethod::tls()).unwrap().build();
|
|
|
|
let httpc = HttpConnector::new();
|
|
|
|
let https = HttpsConnector::with_connector(httpc, connector);
|
|
|
|
Client::builder().build(https)
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
2020-11-09 09:35:28 +00:00
|
|
|
pub async fn get_string(uri: &str, extra_headers: Option<&HashMap<String, String>>) -> Result<String, Error> {
|
|
|
|
let mut request = Request::builder()
|
|
|
|
.method("GET")
|
|
|
|
.uri(uri)
|
|
|
|
.header("User-Agent", "proxmox-backup-client/1.0");
|
|
|
|
|
|
|
|
if let Some(hs) = extra_headers {
|
|
|
|
for (h, v) in hs.iter() {
|
|
|
|
request = request.header(h, v);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
let request = request.body(Body::empty())?;
|
|
|
|
|
|
|
|
let res = HTTP_CLIENT.request(request).await?;
|
2020-10-21 09:41:11 +00:00
|
|
|
|
|
|
|
let status = res.status();
|
|
|
|
if !status.is_success() {
|
|
|
|
bail!("Got bad status '{}' from server", status)
|
|
|
|
}
|
|
|
|
|
2020-10-27 08:52:45 +00:00
|
|
|
response_body_string(res).await
|
|
|
|
}
|
|
|
|
|
|
|
|
pub async fn response_body_string(res: Response<Body>) -> Result<String, Error> {
|
2020-10-21 09:41:11 +00:00
|
|
|
let buf = hyper::body::to_bytes(res).await?;
|
|
|
|
String::from_utf8(buf.to_vec())
|
|
|
|
.map_err(|err| format_err!("Error converting HTTP result data: {}", err))
|
|
|
|
}
|
|
|
|
|
2020-10-27 08:52:45 +00:00
|
|
|
pub async fn post(
|
|
|
|
uri: &str,
|
|
|
|
body: Option<String>,
|
|
|
|
content_type: Option<&str>,
|
|
|
|
) -> Result<Response<Body>, Error> {
|
|
|
|
let body = if let Some(body) = body {
|
|
|
|
Body::from(body)
|
|
|
|
} else {
|
|
|
|
Body::empty()
|
|
|
|
};
|
|
|
|
let content_type = content_type.unwrap_or("application/json");
|
|
|
|
|
|
|
|
let request = Request::builder()
|
|
|
|
.method("POST")
|
|
|
|
.uri(uri)
|
|
|
|
.header("User-Agent", "proxmox-backup-client/1.0")
|
|
|
|
.header(hyper::header::CONTENT_TYPE, content_type)
|
|
|
|
.body(body)?;
|
|
|
|
|
|
|
|
|
|
|
|
HTTP_CLIENT.request(request)
|
|
|
|
.map_err(Error::from)
|
|
|
|
.await
|
|
|
|
}
|
|
|
|
|
2020-10-21 09:41:11 +00:00
|
|
|
#[derive(Clone)]
|
|
|
|
pub struct HttpsConnector {
|
|
|
|
http: HttpConnector,
|
|
|
|
ssl_connector: std::sync::Arc<SslConnector>,
|
|
|
|
}
|
|
|
|
|
|
|
|
impl HttpsConnector {
|
|
|
|
pub fn with_connector(mut http: HttpConnector, ssl_connector: SslConnector) -> Self {
|
|
|
|
http.enforce_http(false);
|
|
|
|
|
|
|
|
Self {
|
|
|
|
http,
|
|
|
|
ssl_connector: std::sync::Arc::new(ssl_connector),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
type MaybeTlsStream = EitherStream<
|
|
|
|
tokio::net::TcpStream,
|
|
|
|
tokio_openssl::SslStream<tokio::net::TcpStream>,
|
|
|
|
>;
|
|
|
|
|
|
|
|
impl hyper::service::Service<Uri> for HttpsConnector {
|
|
|
|
type Response = MaybeTlsStream;
|
|
|
|
type Error = Error;
|
|
|
|
type Future = std::pin::Pin<Box<
|
|
|
|
dyn Future<Output = Result<Self::Response, Self::Error>> + Send + 'static
|
|
|
|
>>;
|
|
|
|
|
|
|
|
fn poll_ready(&mut self, _: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
|
|
|
|
// This connector is always ready, but others might not be.
|
|
|
|
Poll::Ready(Ok(()))
|
|
|
|
}
|
|
|
|
|
|
|
|
fn call(&mut self, dst: Uri) -> Self::Future {
|
|
|
|
let mut this = self.clone();
|
|
|
|
async move {
|
|
|
|
let is_https = dst
|
|
|
|
.scheme()
|
|
|
|
.ok_or_else(|| format_err!("missing URL scheme"))?
|
|
|
|
== "https";
|
|
|
|
let host = dst
|
|
|
|
.host()
|
|
|
|
.ok_or_else(|| format_err!("missing hostname in destination url?"))?
|
|
|
|
.to_string();
|
|
|
|
|
|
|
|
let config = this.ssl_connector.configure();
|
2020-11-10 10:55:53 +00:00
|
|
|
let dst_str = dst.to_string(); // for error messages
|
|
|
|
let conn = this
|
|
|
|
.http
|
|
|
|
.call(dst)
|
|
|
|
.await
|
|
|
|
.map_err(|err| format_err!("error connecting to {} - {}", dst_str, err))?;
|
2020-10-21 09:41:11 +00:00
|
|
|
|
|
|
|
let _ = set_tcp_keepalive(conn.as_raw_fd(), PROXMOX_BACKUP_TCP_KEEPALIVE_TIME);
|
|
|
|
|
|
|
|
if is_https {
|
|
|
|
let conn = tokio_openssl::connect(config?, &host, conn).await?;
|
|
|
|
Ok(MaybeTlsStream::Right(conn))
|
|
|
|
} else {
|
|
|
|
Ok(MaybeTlsStream::Left(conn))
|
|
|
|
}
|
|
|
|
}.boxed()
|
|
|
|
}
|
|
|
|
}
|