Skip to content

Commit 693a14c

Browse files
committed
pd-edge: refactor: cleanup http abi impl
1 parent 95d9840 commit 693a14c

25 files changed

Lines changed: 1173 additions & 1391 deletions

pd-edge/src/abi_impl/http/exchange.rs

Lines changed: 124 additions & 324 deletions
Large diffs are not rendered by default.

pd-edge/src/abi_impl/http/fast_path.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -194,7 +194,7 @@ mod tests {
194194
classify_downstream_http1_fast_body, downstream_http1_fast_path_eligible,
195195
downstream_http1_fast_path_expects_continue, outbound_http1_fast_path_eligible,
196196
};
197-
use crate::abi_impl::http::HttpVersionPreference;
197+
use crate::abi_impl::http::version::HttpVersionPreference;
198198

199199
#[test]
200200
fn downstream_http1_fast_path_accepts_chunked_and_fixed_requests() {

pd-edge/src/abi_impl/http/helpers.rs

Lines changed: 90 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -33,33 +33,6 @@ struct CachedHeaderBatch {
3333
static HEADER_BATCH_CACHE: OnceLock<Mutex<HashMap<HeaderBatchCacheKey, CachedHeaderBatch>>> =
3434
OnceLock::new();
3535

36-
pub(super) fn parse_header_name(name: impl AsRef<str>) -> Result<HeaderName, VmError> {
37-
let name = name.as_ref();
38-
HeaderName::from_bytes(name.as_bytes())
39-
.map_err(|_| VmError::HostError(format!("invalid header name '{name}'")))
40-
}
41-
42-
pub(super) fn parse_header(
43-
name: impl AsRef<str>,
44-
value: impl AsRef<str>,
45-
) -> Result<(HeaderName, HeaderValue), VmError> {
46-
let name = name.as_ref();
47-
let value = value.as_ref();
48-
let header_name = HeaderName::from_bytes(name.as_bytes())
49-
.map_err(|_| VmError::HostError(format!("invalid header name '{name}'")))?;
50-
let header_value = HeaderValue::from_str(value)
51-
.map_err(|_| VmError::HostError(format!("invalid header value '{value}'")))?;
52-
Ok((header_name, header_value))
53-
}
54-
55-
pub(super) fn request_path_with_query(path: &str, query: &str) -> String {
56-
if query.is_empty() {
57-
path.to_string()
58-
} else {
59-
format!("{path}?{query}")
60-
}
61-
}
62-
6336
fn header_batch_cache() -> &'static Mutex<HashMap<HeaderBatchCacheKey, CachedHeaderBatch>> {
6437
HEADER_BATCH_CACHE.get_or_init(|| Mutex::new(HashMap::new()))
6538
}
@@ -92,7 +65,7 @@ fn cached_header_batch_matches(source: &HeaderBatchCacheSource, headers: &Value)
9265
}
9366
}
9467

95-
pub(super) fn lookup_cached_header_batch(headers: &Value) -> Option<HeaderMap> {
68+
fn lookup_cached_header_batch(headers: &Value) -> Option<HeaderMap> {
9669
let key = header_batch_cache_key(headers)?;
9770
let mut guard = header_batch_cache().lock();
9871
let cached = guard.get(&key)?;
@@ -103,7 +76,7 @@ pub(super) fn lookup_cached_header_batch(headers: &Value) -> Option<HeaderMap> {
10376
None
10477
}
10578

106-
pub(super) fn store_cached_header_batch(headers: &Value, parsed: &HeaderMap) {
79+
fn store_cached_header_batch(headers: &Value, parsed: &HeaderMap) {
10780
let (Some(key), Some(source)) = (
10881
header_batch_cache_key(headers),
10982
header_batch_cache_source(headers),
@@ -132,6 +105,86 @@ pub(super) fn store_cached_header_batch(headers: &Value, parsed: &HeaderMap) {
132105
);
133106
}
134107

108+
pub(super) fn parse_string_header_batch(
109+
headers: Value,
110+
batch_name: &'static str,
111+
) -> Result<HeaderMap, VmError> {
112+
match headers {
113+
Value::Null => Ok(HeaderMap::new()),
114+
Value::Array(values) => {
115+
if let Some(parsed) = lookup_cached_header_batch(&Value::Array(values.clone())) {
116+
return Ok(parsed);
117+
}
118+
if values.len() % 2 != 0 {
119+
return Err(VmError::HostError(format!(
120+
"{batch_name} arrays must contain alternating name/value string pairs",
121+
)));
122+
}
123+
let mut parsed = HeaderMap::new();
124+
for pair in values.chunks(2) {
125+
let Value::String(name) = &pair[0] else {
126+
return Err(VmError::HostError(format!(
127+
"{batch_name} array keys must be strings",
128+
)));
129+
};
130+
let Value::String(value) = &pair[1] else {
131+
return Err(VmError::HostError(format!(
132+
"{batch_name} array values must be strings",
133+
)));
134+
};
135+
let (header_name, header_value) = parse_header(name.as_str(), value.as_str())?;
136+
parsed.insert(header_name, header_value);
137+
}
138+
store_cached_header_batch(&Value::Array(values.clone()), &parsed);
139+
Ok(parsed)
140+
}
141+
Value::Map(entries) => {
142+
if let Some(parsed) = lookup_cached_header_batch(&Value::Map(entries.clone())) {
143+
return Ok(parsed);
144+
}
145+
let mut parsed = HeaderMap::new();
146+
for (key, value) in entries.as_ref() {
147+
let Value::String(name) = key else {
148+
return Err(VmError::HostError(format!(
149+
"{batch_name} map keys must be strings",
150+
)));
151+
};
152+
let Value::String(value) = value else {
153+
return Err(VmError::HostError(format!(
154+
"{batch_name} map values must be strings",
155+
)));
156+
};
157+
let (header_name, header_value) = parse_header(name.as_str(), value.as_str())?;
158+
parsed.insert(header_name, header_value);
159+
}
160+
store_cached_header_batch(&Value::Map(entries.clone()), &parsed);
161+
Ok(parsed)
162+
}
163+
_ => Err(VmError::HostError(format!(
164+
"{batch_name} must be null, an array of alternating strings, or a string map",
165+
))),
166+
}
167+
}
168+
169+
pub(super) fn parse_header_name(name: impl AsRef<str>) -> Result<HeaderName, VmError> {
170+
let name = name.as_ref();
171+
HeaderName::from_bytes(name.as_bytes())
172+
.map_err(|_| VmError::HostError(format!("invalid header name '{name}'")))
173+
}
174+
175+
pub(super) fn parse_header(
176+
name: impl AsRef<str>,
177+
value: impl AsRef<str>,
178+
) -> Result<(HeaderName, HeaderValue), VmError> {
179+
let name = name.as_ref();
180+
let value = value.as_ref();
181+
let header_name = HeaderName::from_bytes(name.as_bytes())
182+
.map_err(|_| VmError::HostError(format!("invalid header name '{name}'")))?;
183+
let header_value = HeaderValue::from_str(value)
184+
.map_err(|_| VmError::HostError(format!("invalid header value '{value}'")))?;
185+
Ok((header_name, header_value))
186+
}
187+
135188
pub(super) fn headers_to_value_map(headers: &HeaderMap) -> Value {
136189
let mut values = BTreeMap::<String, Vec<String>>::new();
137190
for (name, value) in headers {
@@ -154,6 +207,14 @@ pub(super) fn headers_to_value_map(headers: &HeaderMap) -> Value {
154207
)
155208
}
156209

210+
pub(super) fn request_path_with_query(path: &str, query: &str) -> String {
211+
if query.is_empty() {
212+
path.to_string()
213+
} else {
214+
format!("{path}?{query}")
215+
}
216+
}
217+
157218
pub(super) fn query_to_value_map(query: &str) -> Value {
158219
let mut values = BTreeMap::<String, Vec<String>>::new();
159220
for (name, value) in url::form_urlencoded::parse(query.as_bytes()) {

pd-edge/src/abi_impl/http/mod.rs

Lines changed: 6 additions & 72 deletions
Original file line numberDiff line numberDiff line change
@@ -1,79 +1,13 @@
11
#[cfg(feature = "http")]
2-
use self::helpers::{
3-
headers_to_value_map, is_valid_request_path, lookup_cached_header_batch, parse_header,
4-
parse_header_name, query_to_value_map, request_path_with_query, serialize_query_pairs,
5-
store_cached_header_batch,
6-
};
7-
8-
#[cfg(feature = "http")]
9-
mod exchange;
10-
mod fast_path;
2+
pub(crate) mod exchange;
3+
pub(crate) mod fast_path;
114
mod helpers;
12-
mod outbound_http1;
5+
pub(crate) mod outbound_http1;
136
#[cfg(feature = "http")]
147
mod request;
158
#[cfg(feature = "http")]
16-
mod response;
17-
mod state;
18-
mod version;
9+
pub(crate) mod response;
10+
pub(crate) mod state;
11+
pub(crate) mod version;
1912

20-
#[cfg(feature = "http")]
21-
pub(crate) use exchange::prepare_default_upstream_request;
22-
pub(crate) use fast_path::{
23-
DownstreamHttp1FastBodyKind, MAX_DOWNSTREAM_HTTP1_FAST_BODY_BYTES,
24-
classify_downstream_http1_fast_body_lazy, downstream_http1_fast_path_eligible_lazy,
25-
downstream_http1_fast_path_expects_continue_lazy, outbound_http1_fast_path_eligible,
26-
};
27-
pub(crate) use outbound_http1::new_shared_plain_http1_sender_pool;
28-
pub(crate) use outbound_http1::{OutboundHttp1ForwardBody, OutboundHttp1ForwardResponse};
29-
#[cfg(feature = "http")]
30-
pub(crate) use response::parse_response_header_batch;
31-
#[cfg(feature = "websocket")]
32-
pub(crate) use state::DownstreamWebSocketTunnelPlan;
33-
#[cfg(feature = "websocket")]
34-
pub(crate) use state::HttpOutboundRequestNode;
35-
#[cfg(feature = "tls")]
36-
pub(crate) use state::attach_outbound_exchange_tls_transport;
37-
pub(crate) use state::is_hop_by_hop_header;
38-
#[cfg(feature = "tls")]
39-
pub(crate) use state::upstream_response_available;
40-
pub(crate) use state::{
41-
AttachedHttpTransport, DownstreamConnectTunnelPlan, DownstreamConnectTunnelTarget,
42-
DownstreamConnectionMetadata, DownstreamHttpListenerGoal, DownstreamPostResponsePlan,
43-
Http1DownstreamResolution, HttpPlaneRuntimeServicesConfig, HttpUpstreamScheme,
44-
InlineDownstreamHttpResponse, LazyRequestId, PromotedDownstreamTransport, ProxyStreamRegistry,
45-
ResolvedHttpGraphResponse, ResolvedNativeHttp1DownstreamResponse,
46-
ResolvedNativeLocalHttp1DownstreamResponse, ResolvedSnapshotHttp1DownstreamResponse,
47-
SharedRuntimeServices, allocate_tcp_stream_handle, allocate_udp_socket_handle,
48-
append_outbound_exchange_body, append_outbound_exchange_body_bytes,
49-
append_response_output_body_bytes, attach_outbound_exchange_tcp_transport,
50-
build_downstream_http_request_context, build_downstream_http_request_context_from_components,
51-
consume_request_body_all, default_upstream_exchange_handle, default_upstream_udp_socket_handle,
52-
finish_downstream_response_stream, materialize_downstream_response_body_source,
53-
new_shared_http_plane_runtime_services, outbound_exchange_exists,
54-
outbound_exchange_response_available, outbound_exchange_response_eof,
55-
outbound_exchange_tls_flow, read_downstream_response_trailers,
56-
read_outbound_exchange_response_all, read_outbound_exchange_response_next_chunk,
57-
read_outbound_exchange_response_next_line, read_outbound_exchange_response_trailers,
58-
read_request_body_next_chunk, read_request_body_next_line, read_upstream_response_all,
59-
read_upstream_response_next_chunk, read_upstream_response_next_line, request_body_eof,
60-
resolve_committed_http_graph_response, resolve_http_graph_response,
61-
resolve_http1_downstream_response, schedule_downstream_http_handoff,
62-
start_downstream_response_stream, start_native_default_upstream_http_forward_response,
63-
take_promoted_downstream_transport, tcp_stream_exists, udp_socket_exists,
64-
upstream_response_eof, write_downstream_response_stream_bytes,
65-
};
6613
pub use state::{HttpRequestContext, LazyHttpHeaders, ProxyVmContext, SharedProxyVmContext};
67-
#[cfg(test)]
68-
pub(crate) use state::{RequestPortField, RequestStringField};
69-
#[cfg(any(feature = "http", test))]
70-
pub(crate) use state::{
71-
allocate_outbound_exchange_handle, ensure_outbound_exchange_response_started,
72-
ensure_upstream_response_started, read_request_body_all,
73-
};
74-
#[cfg(feature = "webrtc")]
75-
pub(crate) use state::{
76-
allocate_webrtc_connection_handle, default_upstream_webrtc_connection_handle,
77-
webrtc_connection_exists,
78-
};
79-
pub(crate) use version::HttpVersionPreference;

pd-edge/src/abi_impl/http/outbound_http1.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ use tokio::{
2727
};
2828
use vm::VmError;
2929

30-
use crate::abi_impl::http::LazyHttpHeaders;
30+
use crate::abi_impl::http::state::LazyHttpHeaders;
3131
use crate::abi_impl::transport::HTTP11_ALPN_PROTOCOL;
3232
#[cfg(feature = "tls")]
3333
use crate::abi_impl::transport::{

0 commit comments

Comments
 (0)