summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--Cargo.lock1
-rw-r--r--cli/tests/unit/http_test.ts43
-rw-r--r--ext/http/Cargo.toml1
-rw-r--r--ext/http/lib.rs99
-rw-r--r--runtime/ops/http.rs16
5 files changed, 130 insertions, 30 deletions
diff --git a/Cargo.lock b/Cargo.lock
index 113529785..d78f55230 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -966,6 +966,7 @@ dependencies = [
"deno_core",
"deno_websocket",
"hyper",
+ "percent-encoding",
"ring",
"serde",
"tokio",
diff --git a/cli/tests/unit/http_test.ts b/cli/tests/unit/http_test.ts
index 75e0d5505..6a57c2fd1 100644
--- a/cli/tests/unit/http_test.ts
+++ b/cli/tests/unit/http_test.ts
@@ -1142,6 +1142,49 @@ Deno.test(
},
);
+// https://github.com/denoland/deno/pull/13628
+Deno.test(
+ {
+ ignore: Deno.build.os === "windows",
+ permissions: { read: true, write: true },
+ },
+ async function httpServerOnUnixSocket() {
+ const filePath = Deno.makeTempFileSync();
+
+ const promise = (async () => {
+ const listener = Deno.listen({ path: filePath, transport: "unix" });
+ for await (const conn of listener) {
+ const httpConn = Deno.serveHttp(conn);
+ for await (const { request, respondWith } of httpConn) {
+ const url = new URL(request.url);
+ assertEquals(url.protocol, "http+unix:");
+ assertEquals(decodeURIComponent(url.host), filePath);
+ assertEquals(url.pathname, "/path/name");
+ await respondWith(new Response("", { headers: {} }));
+ httpConn.close();
+ }
+ break;
+ }
+ })();
+
+ // fetch() does not supports unix domain sockets yet https://github.com/denoland/deno/issues/8821
+ const conn = await Deno.connect({ path: filePath, transport: "unix" });
+ const encoder = new TextEncoder();
+ // The Host header must be present and empty if it is not a Internet host name (RFC2616, Section 14.23)
+ const body = `GET /path/name HTTP/1.1\r\nHost:\r\n\r\n`;
+ const writeResult = await conn.write(encoder.encode(body));
+ assertEquals(body.length, writeResult);
+
+ const resp = new Uint8Array(200);
+ const readResult = await conn.read(resp);
+ assertEquals(readResult, 115);
+
+ conn.close();
+
+ await promise;
+ },
+);
+
function chunkedBodyReader(h: Headers, r: BufReader): Deno.Reader {
// Based on https://tools.ietf.org/html/rfc2616#section-19.4.6
const tp = new TextProtoReader(r);
diff --git a/ext/http/Cargo.toml b/ext/http/Cargo.toml
index 1554aecec..9bbe1c1de 100644
--- a/ext/http/Cargo.toml
+++ b/ext/http/Cargo.toml
@@ -19,6 +19,7 @@ bytes = "1"
deno_core = { version = "0.118.0", path = "../../core" }
deno_websocket = { version = "0.41.0", path = "../websocket" }
hyper = { version = "0.14.9", features = ["server", "stream", "http1", "http2", "runtime"] }
+percent-encoding = "2.1.0"
ring = "0.16.20"
serde = { version = "1.0.129", features = ["derive"] }
tokio = { version = "1.10.1", features = ["full"] }
diff --git a/ext/http/lib.rs b/ext/http/lib.rs
index 24dd77c92..e11d42da1 100644
--- a/ext/http/lib.rs
+++ b/ext/http/lib.rs
@@ -39,6 +39,7 @@ use hyper::service::Service;
use hyper::Body;
use hyper::Request;
use hyper::Response;
+use percent_encoding::percent_encode;
use serde::Deserialize;
use serde::Serialize;
use std::borrow::Cow;
@@ -49,7 +50,6 @@ use std::future::Future;
use std::io;
use std::mem::replace;
use std::mem::take;
-use std::net::SocketAddr;
use std::pin::Pin;
use std::rc::Rc;
use std::sync::Arc;
@@ -83,8 +83,27 @@ pub fn init() -> Extension {
.build()
}
+pub enum HttpSocketAddr {
+ IpSocket(std::net::SocketAddr),
+ #[cfg(unix)]
+ UnixSocket(tokio::net::unix::SocketAddr),
+}
+
+impl From<std::net::SocketAddr> for HttpSocketAddr {
+ fn from(addr: std::net::SocketAddr) -> Self {
+ Self::IpSocket(addr)
+ }
+}
+
+#[cfg(unix)]
+impl From<tokio::net::unix::SocketAddr> for HttpSocketAddr {
+ fn from(addr: tokio::net::unix::SocketAddr) -> Self {
+ Self::UnixSocket(addr)
+ }
+}
+
struct HttpConnResource {
- addr: SocketAddr,
+ addr: HttpSocketAddr,
scheme: &'static str,
acceptors_tx: mpsc::UnboundedSender<HttpAcceptor>,
closed_fut: Shared<RemoteHandle<Result<(), Arc<hyper::Error>>>>,
@@ -92,7 +111,7 @@ struct HttpConnResource {
}
impl HttpConnResource {
- fn new<S>(io: S, scheme: &'static str, addr: SocketAddr) -> Self
+ fn new<S>(io: S, scheme: &'static str, addr: HttpSocketAddr) -> Self
where
S: AsyncRead + AsyncWrite + Unpin + Send + 'static,
{
@@ -172,8 +191,8 @@ impl HttpConnResource {
self.scheme
}
- fn addr(&self) -> SocketAddr {
- self.addr
+ fn addr(&self) -> &HttpSocketAddr {
+ &self.addr
}
}
@@ -188,16 +207,17 @@ impl Resource for HttpConnResource {
}
/// Creates a new HttpConn resource which uses `io` as its transport.
-pub fn http_create_conn_resource<S>(
+pub fn http_create_conn_resource<S, A>(
state: &mut OpState,
io: S,
- addr: SocketAddr,
+ addr: A,
scheme: &'static str,
) -> Result<ResourceId, AnyError>
where
S: AsyncRead + AsyncWrite + Unpin + Send + 'static,
+ A: Into<HttpSocketAddr>,
{
- let conn = HttpConnResource::new(io, scheme, addr);
+ let conn = HttpConnResource::new(io, scheme, addr.into());
let rid = state.resource_table.add(conn);
Ok(rid)
}
@@ -375,30 +395,49 @@ async fn op_http_accept(
fn req_url(
req: &hyper::Request<hyper::Body>,
scheme: &'static str,
- addr: SocketAddr,
+ addr: &HttpSocketAddr,
) -> String {
- let host: Cow<str> = if let Some(auth) = req.uri().authority() {
- match addr.port() {
- 443 if scheme == "https" => Cow::Borrowed(auth.host()),
- 80 if scheme == "http" => Cow::Borrowed(auth.host()),
- _ => Cow::Borrowed(auth.as_str()), // Includes port number.
- }
- } else if let Some(host) = req.uri().host() {
- Cow::Borrowed(host)
- } else if let Some(host) = req.headers().get("HOST") {
- match host.to_str() {
- Ok(host) => Cow::Borrowed(host),
- Err(_) => Cow::Owned(
- host
- .as_bytes()
- .iter()
- .cloned()
- .map(char::from)
- .collect::<String>(),
- ),
+ let host: Cow<str> = match addr {
+ HttpSocketAddr::IpSocket(addr) => {
+ if let Some(auth) = req.uri().authority() {
+ match addr.port() {
+ 443 if scheme == "https" => Cow::Borrowed(auth.host()),
+ 80 if scheme == "http" => Cow::Borrowed(auth.host()),
+ _ => Cow::Borrowed(auth.as_str()), // Includes port number.
+ }
+ } else if let Some(host) = req.uri().host() {
+ Cow::Borrowed(host)
+ } else if let Some(host) = req.headers().get("HOST") {
+ match host.to_str() {
+ Ok(host) => Cow::Borrowed(host),
+ Err(_) => Cow::Owned(
+ host
+ .as_bytes()
+ .iter()
+ .cloned()
+ .map(char::from)
+ .collect::<String>(),
+ ),
+ }
+ } else {
+ Cow::Owned(addr.to_string())
+ }
}
- } else {
- Cow::Owned(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)]
+ HttpSocketAddr::UnixSocket(addr) => Cow::Owned(
+ percent_encode(
+ addr
+ .as_pathname()
+ .and_then(|x| x.to_str())
+ .unwrap_or_default()
+ .as_bytes(),
+ percent_encoding::NON_ALPHANUMERIC,
+ )
+ .to_string(),
+ ),
};
let path = req.uri().path_and_query().map_or("/", |p| p.as_str());
[scheme, "://", &host, path].concat()
diff --git a/runtime/ops/http.rs b/runtime/ops/http.rs
index fddac9261..53a99bd47 100644
--- a/runtime/ops/http.rs
+++ b/runtime/ops/http.rs
@@ -8,6 +8,7 @@ use deno_core::OpState;
use deno_core::ResourceId;
use deno_http::http_create_conn_resource;
use deno_net::io::TcpStreamResource;
+use deno_net::io::UnixStreamResource;
use deno_net::ops_tls::TlsStreamResource;
pub fn init() -> Extension {
@@ -45,5 +46,20 @@ fn op_http_start(
return http_create_conn_resource(state, tls_stream, addr, "https");
}
+ #[cfg(unix)]
+ if let Ok(resource_rc) = state
+ .resource_table
+ .take::<UnixStreamResource>(tcp_stream_rid)
+ {
+ super::check_unstable(state, "Deno.serveHttp");
+
+ let resource = Rc::try_unwrap(resource_rc)
+ .expect("Only a single use of this resource should happen");
+ let (read_half, write_half) = resource.into_inner();
+ let unix_stream = read_half.reunite(write_half)?;
+ let addr = unix_stream.local_addr()?;
+ return http_create_conn_resource(state, unix_stream, addr, "http+unix");
+ }
+
Err(bad_resource_id())
}