add tools::http for generic HTTP GET and move HttpsConnector there

...to avoid having the tools:: module depend on api2.

The get_string function is based directly on hyper and thus relatively
simple, not supporting redirects for example.

Signed-off-by: Stefan Reiter <s.reiter@proxmox.com>
This commit is contained in:
Stefan Reiter 2020-10-21 11:41:11 +02:00 committed by Thomas Lamprecht
parent 12bcbf0734
commit 5eb9dd0c8a
3 changed files with 104 additions and 72 deletions

View File

@ -1,8 +1,6 @@
use std::io::Write; use std::io::Write;
use std::task::{Context, Poll};
use std::sync::{Arc, Mutex, RwLock}; use std::sync::{Arc, Mutex, RwLock};
use std::time::Duration; use std::time::Duration;
use std::os::unix::io::AsRawFd;
use anyhow::{bail, format_err, Error}; use anyhow::{bail, format_err, Error};
use futures::*; use futures::*;
@ -19,22 +17,16 @@ use xdg::BaseDirectories;
use proxmox::{ use proxmox::{
api::error::HttpError, api::error::HttpError,
sys::linux::tty, sys::linux::tty,
tools::{ tools::fs::{file_get_json, replace_file, CreateOptions},
fs::{file_get_json, replace_file, CreateOptions},
}
}; };
use super::pipe_to_stream::PipeToSendStream; use super::pipe_to_stream::PipeToSendStream;
use crate::api2::types::Userid; use crate::api2::types::Userid;
use crate::tools::async_io::EitherStream;
use crate::tools::{ use crate::tools::{
self, self,
BroadcastFuture, BroadcastFuture,
DEFAULT_ENCODE_SET, DEFAULT_ENCODE_SET,
socket::{ http::HttpsConnector,
set_tcp_keepalive,
PROXMOX_BACKUP_TCP_KEEPALIVE_TIME,
},
}; };
#[derive(Clone)] #[derive(Clone)]
@ -301,7 +293,7 @@ impl HttpClient {
ssl_connector_builder.set_verify(openssl::ssl::SslVerifyMode::NONE); ssl_connector_builder.set_verify(openssl::ssl::SslVerifyMode::NONE);
} }
let mut httpc = hyper::client::HttpConnector::new(); let mut httpc = HttpConnector::new();
httpc.set_nodelay(true); // important for h2 download performance! httpc.set_nodelay(true); // important for h2 download performance!
httpc.enforce_http(false); // we want https... httpc.enforce_http(false); // we want https...
@ -929,64 +921,3 @@ impl H2Client {
} }
} }
} }
#[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();
let conn = this.http.call(dst).await?;
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()
}
}

View File

@ -37,6 +37,7 @@ pub mod loopdev;
pub mod fuse_loop; pub mod fuse_loop;
pub mod socket; pub mod socket;
pub mod zip; pub mod zip;
pub mod http;
mod parallel_handler; mod parallel_handler;
pub use parallel_handler::*; pub use parallel_handler::*;

100
src/tools/http.rs Normal file
View File

@ -0,0 +1,100 @@
use anyhow::{Error, format_err, bail};
use lazy_static::lazy_static;
use std::task::{Context, Poll};
use std::os::unix::io::AsRawFd;
use hyper::{Uri, Body};
use hyper::client::{Client, HttpConnector};
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)
};
}
pub async fn get_string<U: AsRef<str>>(uri: U) -> Result<String, Error> {
let res = HTTP_CLIENT.get(uri.as_ref().parse()?).await?;
let status = res.status();
if !status.is_success() {
bail!("Got bad status '{}' from server", status)
}
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))
}
#[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();
let conn = this.http.call(dst).await?;
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()
}
}