Skip to content

Commit b537205

Browse files
authored
Merge pull request #458 from influxdata/crepererum/split-http-mod
refactor: split `http` module
2 parents 149b9bf + 9c8feab commit b537205

5 files changed

Lines changed: 256 additions & 218 deletions

File tree

host/src/http/mod.rs

Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,94 @@
1+
//! Interfaces for HTTP interactions of the guest.
2+
3+
use std::sync::Arc;
4+
5+
use http::HeaderName;
6+
use tokio::runtime::Handle;
7+
use wasmtime_wasi_http::{
8+
DEFAULT_FORBIDDEN_HEADERS,
9+
p2::{
10+
HttpResult, WasiHttpCtxView, WasiHttpHooks, WasiHttpView,
11+
bindings::http::types::ErrorCode as HttpErrorCode,
12+
body::HyperOutgoingBody,
13+
default_send_request_handler,
14+
types::{HostFutureIncomingResponse, OutgoingRequestConfig},
15+
},
16+
};
17+
18+
pub use types::{HttpConnectionMode, HttpMethod, HttpPort};
19+
pub use validator::{
20+
AllowCertainHttpRequests, AllowHttpEndpoint, AllowHttpHost, HttpRequestRejected,
21+
HttpRequestValidator, RejectAllHttpRequests,
22+
};
23+
24+
use crate::state::WasmStateImpl;
25+
26+
mod types;
27+
mod validator;
28+
29+
impl WasiHttpView for WasmStateImpl {
30+
fn http(&mut self) -> WasiHttpCtxView<'_> {
31+
WasiHttpCtxView {
32+
ctx: &mut self.wasi_http_ctx,
33+
table: &mut self.resource_table,
34+
hooks: &mut self.wasi_http_hooks,
35+
}
36+
}
37+
}
38+
39+
/// Implements [`WasiHttpHooks`].
40+
#[derive(Debug)]
41+
pub(crate) struct WasiHttpHooksImpl {
42+
/// HTTP request validator.
43+
pub(crate) http_validator: Arc<dyn HttpRequestValidator>,
44+
45+
/// Handle to tokio I/O runtime.
46+
pub(crate) io_rt: Handle,
47+
}
48+
49+
impl WasiHttpHooks for WasiHttpHooksImpl {
50+
fn send_request(
51+
&mut self,
52+
mut request: hyper::Request<HyperOutgoingBody>,
53+
config: OutgoingRequestConfig,
54+
) -> HttpResult<HostFutureIncomingResponse> {
55+
let _guard = self.io_rt.enter();
56+
57+
// Python `requests` sends this so we allow it but later drop it from the actual request.
58+
request.headers_mut().remove(hyper::header::CONNECTION);
59+
60+
// technically we could return an error straight away, but `urllib3` doesn't handle that super well, so we
61+
// create a future and validate the error in there (before actually starting the request of course)
62+
63+
let validator = Arc::clone(&self.http_validator);
64+
let handle = wasmtime_wasi::runtime::spawn(async move {
65+
// yes, that's another layer of futures. The WASI interface is somewhat nested.
66+
let fut = async {
67+
let mode = HttpConnectionMode::from_use_tls(config.use_tls);
68+
validator
69+
.validate(&request, mode)
70+
.map_err(|_| HttpErrorCode::HttpRequestDenied)?;
71+
72+
log::debug!(
73+
"UDF HTTP request: {} {} ({mode:?})",
74+
request.method().as_str(),
75+
request.uri(),
76+
);
77+
default_send_request_handler(request, config).await
78+
};
79+
80+
Ok(fut.await)
81+
});
82+
83+
Ok(HostFutureIncomingResponse::pending(handle))
84+
}
85+
86+
fn is_forbidden_header(&mut self, name: &HeaderName) -> bool {
87+
// Python `requests` sends this so we allow it but later drop it from the actual request.
88+
if name == hyper::header::CONNECTION {
89+
return false;
90+
}
91+
92+
DEFAULT_FORBIDDEN_HEADERS.contains(name)
93+
}
94+
}

host/src/http/types.rs

Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
//! Common types used for HTTP routines.
2+
use std::{fmt, num::NonZeroU16};
3+
4+
pub use http::Method as HttpMethod;
5+
6+
/// An HTTP port.
7+
///
8+
/// Can be any [`u16`] value except for zero.
9+
#[derive(Clone, Copy, Hash, PartialEq, Eq, PartialOrd, Ord)]
10+
pub struct HttpPort(NonZeroU16);
11+
12+
impl HttpPort {
13+
/// Create new port from [`u16`].
14+
///
15+
/// Returns [`None`] if port is zero.
16+
pub const fn new(p: u16) -> Option<Self> {
17+
// NOTE: `Option::map` isn't const-stable
18+
match NonZeroU16::new(p) {
19+
Some(p) => Some(Self(p)),
20+
None => None,
21+
}
22+
}
23+
24+
/// Get [`u16`] representation of that port.
25+
pub const fn get_u16(&self) -> u16 {
26+
self.0.get()
27+
}
28+
}
29+
30+
impl std::fmt::Debug for HttpPort {
31+
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
32+
self.0.get().fmt(f)
33+
}
34+
}
35+
36+
impl std::fmt::Display for HttpPort {
37+
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
38+
self.0.get().fmt(f)
39+
}
40+
}
41+
42+
impl std::str::FromStr for HttpPort {
43+
type Err = std::num::ParseIntError;
44+
45+
fn from_str(s: &str) -> Result<Self, Self::Err> {
46+
let p: NonZeroU16 = s.parse()?;
47+
Ok(Self(p))
48+
}
49+
}
50+
51+
/// HTTP connection mode.
52+
///
53+
/// Defaults to [`Encrypted`](Self::Encrypted).
54+
#[derive(Debug, Clone, Copy, Hash, PartialEq, Eq, Default)]
55+
pub enum HttpConnectionMode {
56+
/// Encrypted via TLS, i.e. HTTPs.
57+
#[default]
58+
Encrypted,
59+
60+
/// Unencrypted, i.e. plain HTTP.
61+
PlainText,
62+
}
63+
64+
impl HttpConnectionMode {
65+
/// Default port for this connection mode.
66+
pub const fn default_port(&self) -> HttpPort {
67+
match self {
68+
Self::Encrypted => HttpPort::new(443).expect("valid port"),
69+
Self::PlainText => HttpPort::new(80).expect("valid port"),
70+
}
71+
}
72+
73+
/// Derive mode from boolean "use TLS?" flag.
74+
pub(crate) fn from_use_tls(use_tls: bool) -> Self {
75+
if use_tls {
76+
Self::Encrypted
77+
} else {
78+
Self::PlainText
79+
}
80+
}
81+
}

0 commit comments

Comments
 (0)