summaryrefslogtreecommitdiff
path: root/ext/http/request_properties.rs
diff options
context:
space:
mode:
authorMatt Mastracci <matthew@mastracci.com>2023-04-22 11:48:21 -0600
committerGitHub <noreply@github.com>2023-04-22 11:48:21 -0600
commitbdffcb409fd1e257db280ab73e07cc319711256c (patch)
tree9aca1c1e73f0249bba8b66781b79c358a7a00798 /ext/http/request_properties.rs
parentd137501a639cb315772866f6775fcd9f43e28f5b (diff)
feat(ext/http): Rework Deno.serve using hyper 1.0-rc3 (#18619)
This is a rewrite of the `Deno.serve` API to live on top of hyper 1.0-rc3. The code should be more maintainable long-term, and avoids some of the slower mpsc patterns that made the older code less efficient than it could have been. Missing features: - `upgradeHttp` and `upgradeHttpRaw` (`upgradeWebSocket` is available, however). - Automatic compression is unavailable on responses.
Diffstat (limited to 'ext/http/request_properties.rs')
-rw-r--r--ext/http/request_properties.rs249
1 files changed, 249 insertions, 0 deletions
diff --git a/ext/http/request_properties.rs b/ext/http/request_properties.rs
new file mode 100644
index 000000000..7a7f5219c
--- /dev/null
+++ b/ext/http/request_properties.rs
@@ -0,0 +1,249 @@
+use deno_core::error::AnyError;
+use deno_core::OpState;
+use deno_core::ResourceId;
+use deno_net::raw::NetworkStream;
+// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license.
+use deno_net::raw::take_network_stream_listener_resource;
+use deno_net::raw::take_network_stream_resource;
+use deno_net::raw::NetworkStreamAddress;
+use deno_net::raw::NetworkStreamListener;
+use deno_net::raw::NetworkStreamType;
+use hyper::HeaderMap;
+use hyper::Uri;
+use hyper1::header::HOST;
+use std::borrow::Cow;
+use std::rc::Rc;
+
+// TODO(mmastrac): I don't like that we have to clone this, but it's one-time setup
+#[derive(Clone)]
+pub struct HttpListenProperties {
+ pub stream_type: NetworkStreamType,
+ pub scheme: &'static str,
+ pub fallback_host: String,
+ pub local_port: Option<u16>,
+}
+
+#[derive(Clone)]
+pub struct HttpConnectionProperties {
+ pub stream_type: NetworkStreamType,
+ pub peer_address: Rc<str>,
+ pub peer_port: Option<u16>,
+ pub local_port: Option<u16>,
+}
+
+pub struct HttpRequestProperties {
+ pub authority: Option<String>,
+}
+
+/// Pluggable trait to determine listen, connection and request properties
+/// for embedders that wish to provide alternative routes for incoming HTTP.
+pub trait HttpPropertyExtractor {
+ /// Given a listener [`ResourceId`], returns the [`NetworkStreamListener`].
+ fn get_network_stream_listener_for_rid(
+ state: &mut OpState,
+ listener_rid: ResourceId,
+ ) -> Result<NetworkStreamListener, AnyError>;
+
+ /// Given a connection [`ResourceId`], returns the [`NetworkStream`].
+ fn get_network_stream_for_rid(
+ state: &mut OpState,
+ rid: ResourceId,
+ ) -> Result<NetworkStream, AnyError>;
+
+ /// Determines the listener properties.
+ fn listen_properties(
+ stream_type: NetworkStreamType,
+ local_address: &NetworkStreamAddress,
+ ) -> HttpListenProperties;
+
+ /// Determines the connection properties.
+ fn connection_properties(
+ listen_properties: &HttpListenProperties,
+ peer_address: &NetworkStreamAddress,
+ ) -> HttpConnectionProperties;
+
+ /// Determines the request properties.
+ fn request_properties(
+ connection_properties: &HttpConnectionProperties,
+ uri: &Uri,
+ headers: &HeaderMap,
+ ) -> HttpRequestProperties;
+}
+
+pub struct DefaultHttpRequestProperties {}
+
+impl HttpPropertyExtractor for DefaultHttpRequestProperties {
+ fn get_network_stream_for_rid(
+ state: &mut OpState,
+ rid: ResourceId,
+ ) -> Result<NetworkStream, AnyError> {
+ take_network_stream_resource(&mut state.resource_table, rid)
+ }
+
+ fn get_network_stream_listener_for_rid(
+ state: &mut OpState,
+ listener_rid: ResourceId,
+ ) -> Result<NetworkStreamListener, AnyError> {
+ take_network_stream_listener_resource(
+ &mut state.resource_table,
+ listener_rid,
+ )
+ }
+
+ fn listen_properties(
+ stream_type: NetworkStreamType,
+ local_address: &NetworkStreamAddress,
+ ) -> HttpListenProperties {
+ let scheme = req_scheme_from_stream_type(stream_type);
+ let fallback_host = req_host_from_addr(stream_type, local_address);
+ let local_port: Option<u16> = match local_address {
+ NetworkStreamAddress::Ip(ip) => Some(ip.port()),
+ #[cfg(unix)]
+ NetworkStreamAddress::Unix(_) => None,
+ };
+
+ HttpListenProperties {
+ scheme,
+ fallback_host,
+ local_port,
+ stream_type,
+ }
+ }
+
+ fn connection_properties(
+ listen_properties: &HttpListenProperties,
+ peer_address: &NetworkStreamAddress,
+ ) -> HttpConnectionProperties {
+ let peer_port: Option<u16> = match peer_address {
+ NetworkStreamAddress::Ip(ip) => Some(ip.port()),
+ #[cfg(unix)]
+ NetworkStreamAddress::Unix(_) => None,
+ };
+ let peer_address = match peer_address {
+ NetworkStreamAddress::Ip(addr) => Rc::from(addr.ip().to_string()),
+ #[cfg(unix)]
+ NetworkStreamAddress::Unix(_) => Rc::from("unix"),
+ };
+ let local_port = listen_properties.local_port;
+ let stream_type = listen_properties.stream_type;
+
+ HttpConnectionProperties {
+ stream_type,
+ peer_address,
+ peer_port,
+ local_port,
+ }
+ }
+
+ fn request_properties(
+ connection_properties: &HttpConnectionProperties,
+ uri: &Uri,
+ headers: &HeaderMap,
+ ) -> HttpRequestProperties {
+ let authority = req_host(
+ uri,
+ headers,
+ connection_properties.stream_type,
+ connection_properties.local_port.unwrap_or_default(),
+ )
+ .map(|s| s.into_owned());
+
+ HttpRequestProperties { authority }
+ }
+}
+
+/// Compute the fallback address from the [`NetworkStreamListenAddress`]. If the request has no authority/host in
+/// its URI, and there is no [`HeaderName::HOST`] header, we fall back to this.
+fn req_host_from_addr(
+ stream_type: NetworkStreamType,
+ addr: &NetworkStreamAddress,
+) -> String {
+ match addr {
+ NetworkStreamAddress::Ip(addr) => {
+ if (stream_type == NetworkStreamType::Tls && addr.port() == 443)
+ || (stream_type == NetworkStreamType::Tcp && addr.port() == 80)
+ {
+ if addr.ip().is_loopback() || addr.ip().is_unspecified() {
+ return "localhost".to_owned();
+ }
+ addr.ip().to_string()
+ } else {
+ if addr.ip().is_loopback() || addr.ip().is_unspecified() {
+ return format!("localhost:{}", addr.port());
+ }
+ addr.to_string()
+ }
+ }
+ // There is no standard way for unix domain socket URLs
+ // nginx and nodejs request use http://unix:[socket_path]:/ but it is not a valid URL
+ // httpie uses http+unix://[percent_encoding_of_path]/ which we follow
+ #[cfg(unix)]
+ NetworkStreamAddress::Unix(unix) => percent_encoding::percent_encode(
+ unix
+ .as_pathname()
+ .and_then(|x| x.to_str())
+ .unwrap_or_default()
+ .as_bytes(),
+ percent_encoding::NON_ALPHANUMERIC,
+ )
+ .to_string(),
+ }
+}
+
+fn req_scheme_from_stream_type(stream_type: NetworkStreamType) -> &'static str {
+ match stream_type {
+ NetworkStreamType::Tcp => "http://",
+ NetworkStreamType::Tls => "https://",
+ #[cfg(unix)]
+ NetworkStreamType::Unix => "http+unix://",
+ }
+}
+
+fn req_host<'a>(
+ uri: &'a Uri,
+ headers: &'a HeaderMap,
+ addr_type: NetworkStreamType,
+ port: u16,
+) -> Option<Cow<'a, str>> {
+ // Unix sockets always use the socket address
+ #[cfg(unix)]
+ if addr_type == NetworkStreamType::Unix {
+ return None;
+ }
+
+ // It is rare that an authority will be passed, but if it does, it takes priority
+ if let Some(auth) = uri.authority() {
+ match addr_type {
+ NetworkStreamType::Tcp => {
+ if port == 80 {
+ return Some(Cow::Borrowed(auth.host()));
+ }
+ }
+ NetworkStreamType::Tls => {
+ if port == 443 {
+ return Some(Cow::Borrowed(auth.host()));
+ }
+ }
+ #[cfg(unix)]
+ NetworkStreamType::Unix => {}
+ }
+ return Some(Cow::Borrowed(auth.as_str()));
+ }
+
+ // TODO(mmastrac): Most requests will use this path and we probably will want to optimize it in the future
+ if let Some(host) = headers.get(HOST) {
+ return Some(match host.to_str() {
+ Ok(host) => Cow::Borrowed(host),
+ Err(_) => Cow::Owned(
+ host
+ .as_bytes()
+ .iter()
+ .cloned()
+ .map(char::from)
+ .collect::<String>(),
+ ),
+ });
+ }
+
+ None
+}