1
0
Fork 0
mirror of https://github.com/denoland/deno.git synced 2024-12-23 15:49:44 -05:00

feat(ext/http): add support for unix domain sockets (#13628)

This commit is contained in:
ylxdzsw 2022-02-16 07:16:12 +08:00 committed by GitHub
parent 2dc5dba8ba
commit 074f53234a
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
5 changed files with 130 additions and 30 deletions

1
Cargo.lock generated
View file

@ -966,6 +966,7 @@ dependencies = [
"deno_core",
"deno_websocket",
"hyper",
"percent-encoding",
"ring",
"serde",
"tokio",

View file

@ -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);

View file

@ -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"] }

View file

@ -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.
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 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())
// 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()

View file

@ -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())
}