mirror of
https://github.com/denoland/deno.git
synced 2025-03-03 17:34:47 -05:00
feat(unstable): http3
This commit is contained in:
parent
a563265316
commit
bbb929fa83
13 changed files with 342 additions and 46 deletions
31
Cargo.lock
generated
31
Cargo.lock
generated
|
@ -1790,6 +1790,8 @@ dependencies = [
|
||||||
"deno_net",
|
"deno_net",
|
||||||
"deno_websocket",
|
"deno_websocket",
|
||||||
"flate2",
|
"flate2",
|
||||||
|
"h3",
|
||||||
|
"h3-quinn",
|
||||||
"http 0.2.12",
|
"http 0.2.12",
|
||||||
"http 1.1.0",
|
"http 1.1.0",
|
||||||
"http-body-util",
|
"http-body-util",
|
||||||
|
@ -3685,6 +3687,34 @@ dependencies = [
|
||||||
"tracing",
|
"tracing",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "h3"
|
||||||
|
version = "0.0.6"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "5e7675a0963b47a6d12fe44c279918b4ffb19baee838ac37f48d2722ad5bc6ab"
|
||||||
|
dependencies = [
|
||||||
|
"bytes",
|
||||||
|
"fastrand",
|
||||||
|
"futures-util",
|
||||||
|
"http 1.1.0",
|
||||||
|
"pin-project-lite",
|
||||||
|
"tokio",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "h3-quinn"
|
||||||
|
version = "0.0.7"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "17c799f413fceeea505236c4d8132f084ff4b55a652288d91439ee93dc24d855"
|
||||||
|
dependencies = [
|
||||||
|
"bytes",
|
||||||
|
"futures",
|
||||||
|
"h3",
|
||||||
|
"quinn",
|
||||||
|
"tokio",
|
||||||
|
"tokio-util",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "halfbrown"
|
name = "halfbrown"
|
||||||
version = "0.2.5"
|
version = "0.2.5"
|
||||||
|
@ -5968,6 +5998,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "62e96808277ec6f97351a2380e6c25114bc9e67037775464979f3037c92d05ef"
|
checksum = "62e96808277ec6f97351a2380e6c25114bc9e67037775464979f3037c92d05ef"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bytes",
|
"bytes",
|
||||||
|
"futures-io",
|
||||||
"pin-project-lite",
|
"pin-project-lite",
|
||||||
"quinn-proto",
|
"quinn-proto",
|
||||||
"quinn-udp",
|
"quinn-udp",
|
||||||
|
|
|
@ -135,6 +135,8 @@ fs3 = "0.5.0"
|
||||||
futures = "0.3.21"
|
futures = "0.3.21"
|
||||||
glob = "0.3.1"
|
glob = "0.3.1"
|
||||||
h2 = "0.4.4"
|
h2 = "0.4.4"
|
||||||
|
h3 = "0.0.6"
|
||||||
|
h3-quinn = "0.0.7"
|
||||||
hickory-resolver = { version = "0.25.0-alpha.4", features = ["tokio-runtime", "serde"] }
|
hickory-resolver = { version = "0.25.0-alpha.4", features = ["tokio-runtime", "serde"] }
|
||||||
http = "1.0"
|
http = "1.0"
|
||||||
http-body = "1.0"
|
http-body = "1.0"
|
||||||
|
@ -169,6 +171,7 @@ pin-project = "1.0.11" # don't pin because they yank crates from cargo
|
||||||
pretty_assertions = "=1.4.0"
|
pretty_assertions = "=1.4.0"
|
||||||
prost = "0.13"
|
prost = "0.13"
|
||||||
prost-build = "0.13"
|
prost-build = "0.13"
|
||||||
|
quinn = { version = "0.11.6", default-features = false, features = ["runtime-tokio", "rustls", "ring"] }
|
||||||
rand = "=0.8.5"
|
rand = "=0.8.5"
|
||||||
regex = "^1.7.0"
|
regex = "^1.7.0"
|
||||||
reqwest = { version = "=0.12.5", default-features = false, features = ["rustls-tls", "stream", "gzip", "brotli", "socks", "json", "http2"] } # pinned because of https://github.com/seanmonstar/reqwest/pull/1955
|
reqwest = { version = "=0.12.5", default-features = false, features = ["rustls-tls", "stream", "gzip", "brotli", "socks", "json", "http2"] } # pinned because of https://github.com/seanmonstar/reqwest/pull/1955
|
||||||
|
|
|
@ -42,6 +42,7 @@ const {
|
||||||
TypedArrayPrototypeGetSymbolToStringTag,
|
TypedArrayPrototypeGetSymbolToStringTag,
|
||||||
Uint8Array,
|
Uint8Array,
|
||||||
Promise,
|
Promise,
|
||||||
|
SafePromiseAll,
|
||||||
} = primordials;
|
} = primordials;
|
||||||
const {
|
const {
|
||||||
getAsyncContext,
|
getAsyncContext,
|
||||||
|
@ -340,7 +341,7 @@ class InnerRequest {
|
||||||
this.#methodAndUri = op_http_get_request_method_and_url(this.#external);
|
this.#methodAndUri = op_http_get_request_method_and_url(this.#external);
|
||||||
}
|
}
|
||||||
return {
|
return {
|
||||||
transport: "tcp",
|
transport,
|
||||||
hostname: this.#methodAndUri[3],
|
hostname: this.#methodAndUri[3],
|
||||||
port: this.#methodAndUri[4],
|
port: this.#methodAndUri[4],
|
||||||
};
|
};
|
||||||
|
@ -727,7 +728,7 @@ function serve(arg1, arg2) {
|
||||||
const path = listener.addr.path;
|
const path = listener.addr.path;
|
||||||
return serveHttpOnListener(listener, signal, handler, onError, () => {
|
return serveHttpOnListener(listener, signal, handler, onError, () => {
|
||||||
if (options.onListen) {
|
if (options.onListen) {
|
||||||
options.onListen(listener.addr);
|
options.onListen(listener.addr, "unix");
|
||||||
} else {
|
} else {
|
||||||
// deno-lint-ignore no-console
|
// deno-lint-ignore no-console
|
||||||
console.error(`Listening on ${path}`);
|
console.error(`Listening on ${path}`);
|
||||||
|
@ -754,6 +755,7 @@ function serve(arg1, arg2) {
|
||||||
}
|
}
|
||||||
|
|
||||||
let listener;
|
let listener;
|
||||||
|
let listenerQuic;
|
||||||
if (wantsHttps) {
|
if (wantsHttps) {
|
||||||
if (!options.cert || !options.key) {
|
if (!options.cert || !options.key) {
|
||||||
throw new TypeError(
|
throw new TypeError(
|
||||||
|
@ -765,6 +767,17 @@ function serve(arg1, arg2) {
|
||||||
listenOpts.alpnProtocols = ["h2", "http/1.1"];
|
listenOpts.alpnProtocols = ["h2", "http/1.1"];
|
||||||
listener = listenTls(listenOpts);
|
listener = listenTls(listenOpts);
|
||||||
listenOpts.port = listener.addr.port;
|
listenOpts.port = listener.addr.port;
|
||||||
|
|
||||||
|
if (Deno.QuicEndpoint) {
|
||||||
|
listenerQuic = new Deno.QuicEndpoint({
|
||||||
|
hostname: listenOpts.hostname,
|
||||||
|
port: listenOpts.port,
|
||||||
|
}).listen({
|
||||||
|
key: listenOpts.key,
|
||||||
|
cert: listenOpts.cert,
|
||||||
|
alpnProtocols: ["h3"],
|
||||||
|
});
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
listener = listen(listenOpts);
|
listener = listen(listenOpts);
|
||||||
listenOpts.port = listener.addr.port;
|
listenOpts.port = listener.addr.port;
|
||||||
|
@ -772,18 +785,56 @@ function serve(arg1, arg2) {
|
||||||
|
|
||||||
const addr = listener.addr;
|
const addr = listener.addr;
|
||||||
|
|
||||||
const onListen = (scheme) => {
|
const onListen = (scheme, transport = "tcp") => {
|
||||||
if (options.onListen) {
|
if (options.onListen) {
|
||||||
options.onListen(addr);
|
options.onListen(addr, transport);
|
||||||
} else {
|
} else {
|
||||||
const host = formatHostName(addr.hostname);
|
const host = formatHostName(addr.hostname);
|
||||||
|
|
||||||
// deno-lint-ignore no-console
|
// deno-lint-ignore no-console
|
||||||
console.error(`Listening on ${scheme}${host}:${addr.port}/`);
|
console.error(
|
||||||
|
`Listening with ${transport} on ${scheme}${host}:${addr.port}/`,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
return serveHttpOnListener(listener, signal, handler, onError, onListen);
|
const tcp = serveHttpOnListener(listener, signal, handler, onError, onListen);
|
||||||
|
|
||||||
|
if (listenerQuic) {
|
||||||
|
const udp = serveHttpOnListener(
|
||||||
|
listenerQuic,
|
||||||
|
signal,
|
||||||
|
handler,
|
||||||
|
onError,
|
||||||
|
onListen,
|
||||||
|
);
|
||||||
|
return {
|
||||||
|
addr: tcp.addr,
|
||||||
|
finished: SafePromiseAll([
|
||||||
|
tcp.finished,
|
||||||
|
udp.finished,
|
||||||
|
]),
|
||||||
|
async shutdown() {
|
||||||
|
await SafePromiseAll([
|
||||||
|
tcp.shutdown(),
|
||||||
|
udp.shutdown(),
|
||||||
|
]);
|
||||||
|
},
|
||||||
|
ref() {
|
||||||
|
tcp.ref();
|
||||||
|
udp.ref();
|
||||||
|
},
|
||||||
|
unref() {
|
||||||
|
tcp.unref();
|
||||||
|
udp.unref();
|
||||||
|
},
|
||||||
|
[SymbolAsyncDispose]() {
|
||||||
|
return this.shutdown();
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
return tcp;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -797,7 +848,7 @@ function serveHttpOnListener(listener, signal, handler, onError, onListen) {
|
||||||
);
|
);
|
||||||
const callback = mapToCallback(context, handler, onError);
|
const callback = mapToCallback(context, handler, onError);
|
||||||
|
|
||||||
onListen(context.scheme);
|
onListen(context.scheme, context.listener.addr.transport);
|
||||||
|
|
||||||
return serveHttpOn(context, listener.addr, callback);
|
return serveHttpOn(context, listener.addr, callback);
|
||||||
}
|
}
|
||||||
|
|
|
@ -31,7 +31,10 @@ deno_core.workspace = true
|
||||||
deno_net.workspace = true
|
deno_net.workspace = true
|
||||||
deno_websocket.workspace = true
|
deno_websocket.workspace = true
|
||||||
flate2.workspace = true
|
flate2.workspace = true
|
||||||
|
h3.workspace = true
|
||||||
|
h3-quinn.workspace = true
|
||||||
http.workspace = true
|
http.workspace = true
|
||||||
|
http-body-util.workspace = true
|
||||||
http_v02.workspace = true
|
http_v02.workspace = true
|
||||||
httparse.workspace = true
|
httparse.workspace = true
|
||||||
hyper.workspace = true
|
hyper.workspace = true
|
||||||
|
|
|
@ -3,6 +3,7 @@ use crate::compressible::is_content_compressible;
|
||||||
use crate::extract_network_stream;
|
use crate::extract_network_stream;
|
||||||
use crate::network_buffered_stream::NetworkStreamPrefixCheck;
|
use crate::network_buffered_stream::NetworkStreamPrefixCheck;
|
||||||
use crate::request_body::HttpRequestBody;
|
use crate::request_body::HttpRequestBody;
|
||||||
|
use crate::request_properties::listener_properties;
|
||||||
use crate::request_properties::HttpConnectionProperties;
|
use crate::request_properties::HttpConnectionProperties;
|
||||||
use crate::request_properties::HttpListenProperties;
|
use crate::request_properties::HttpListenProperties;
|
||||||
use crate::request_properties::HttpPropertyExtractor;
|
use crate::request_properties::HttpPropertyExtractor;
|
||||||
|
@ -22,6 +23,7 @@ use crate::Options;
|
||||||
use cache_control::CacheControl;
|
use cache_control::CacheControl;
|
||||||
use deno_core::external;
|
use deno_core::external;
|
||||||
use deno_core::futures::future::poll_fn;
|
use deno_core::futures::future::poll_fn;
|
||||||
|
use deno_core::futures::StreamExt;
|
||||||
use deno_core::futures::TryFutureExt;
|
use deno_core::futures::TryFutureExt;
|
||||||
use deno_core::op2;
|
use deno_core::op2;
|
||||||
use deno_core::serde_v8::from_v8;
|
use deno_core::serde_v8::from_v8;
|
||||||
|
@ -42,8 +44,12 @@ use deno_core::RcRef;
|
||||||
use deno_core::Resource;
|
use deno_core::Resource;
|
||||||
use deno_core::ResourceId;
|
use deno_core::ResourceId;
|
||||||
use deno_net::ops_tls::TlsStream;
|
use deno_net::ops_tls::TlsStream;
|
||||||
|
use deno_net::quic::ListenerResource as QuicListener;
|
||||||
use deno_net::raw::NetworkStream;
|
use deno_net::raw::NetworkStream;
|
||||||
|
use deno_net::raw::NetworkStreamAddress;
|
||||||
|
use deno_net::raw::NetworkStreamType;
|
||||||
use deno_websocket::ws_create_server_stream;
|
use deno_websocket::ws_create_server_stream;
|
||||||
|
use http_body_util::BodyExt;
|
||||||
use hyper::body::Incoming;
|
use hyper::body::Incoming;
|
||||||
use hyper::header::HeaderMap;
|
use hyper::header::HeaderMap;
|
||||||
use hyper::header::ACCEPT_ENCODING;
|
use hyper::header::ACCEPT_ENCODING;
|
||||||
|
@ -157,6 +163,10 @@ pub enum HttpNextError {
|
||||||
#[error("{0}")]
|
#[error("{0}")]
|
||||||
Hyper(#[from] hyper::Error),
|
Hyper(#[from] hyper::Error),
|
||||||
#[error(transparent)]
|
#[error(transparent)]
|
||||||
|
H3(#[from] h3::Error),
|
||||||
|
#[error(transparent)]
|
||||||
|
QuinnConnectionError(#[from] h3_quinn::quinn::ConnectionError),
|
||||||
|
#[error(transparent)]
|
||||||
JoinError(#[from] tokio::task::JoinError),
|
JoinError(#[from] tokio::task::JoinError),
|
||||||
#[error(transparent)]
|
#[error(transparent)]
|
||||||
Canceled(#[from] deno_core::Canceled),
|
Canceled(#[from] deno_core::Canceled),
|
||||||
|
@ -905,7 +915,12 @@ fn serve_https(
|
||||||
} = lifetime;
|
} = lifetime;
|
||||||
|
|
||||||
let svc = service_fn(move |req: Request| {
|
let svc = service_fn(move |req: Request| {
|
||||||
handle_request(req, request_info.clone(), server_state.clone(), tx.clone())
|
handle_request(
|
||||||
|
req.map(Into::into),
|
||||||
|
request_info.clone(),
|
||||||
|
server_state.clone(),
|
||||||
|
tx.clone(),
|
||||||
|
)
|
||||||
});
|
});
|
||||||
spawn(
|
spawn(
|
||||||
async move {
|
async move {
|
||||||
|
@ -953,7 +968,12 @@ fn serve_http(
|
||||||
} = lifetime;
|
} = lifetime;
|
||||||
|
|
||||||
let svc = service_fn(move |req: Request| {
|
let svc = service_fn(move |req: Request| {
|
||||||
handle_request(req, request_info.clone(), server_state.clone(), tx.clone())
|
handle_request(
|
||||||
|
req.map(Into::into),
|
||||||
|
request_info.clone(),
|
||||||
|
server_state.clone(),
|
||||||
|
tx.clone(),
|
||||||
|
)
|
||||||
});
|
});
|
||||||
spawn(
|
spawn(
|
||||||
serve_http2_autodetect(io, svc, listen_cancel_handle, options)
|
serve_http2_autodetect(io, svc, listen_cancel_handle, options)
|
||||||
|
@ -1062,9 +1082,19 @@ pub fn op_http_serve<HTTP>(
|
||||||
where
|
where
|
||||||
HTTP: HttpPropertyExtractor,
|
HTTP: HttpPropertyExtractor,
|
||||||
{
|
{
|
||||||
let listener =
|
let listener = {
|
||||||
HTTP::get_listener_for_rid(&mut state.borrow_mut(), listener_rid)
|
let mut bstate = state.borrow_mut();
|
||||||
.map_err(HttpNextError::Resource)?;
|
|
||||||
|
if let Ok(endpoint) =
|
||||||
|
bstate.resource_table.take::<QuicListener>(listener_rid)
|
||||||
|
{
|
||||||
|
drop(bstate);
|
||||||
|
return serve_on_quic(state, endpoint);
|
||||||
|
}
|
||||||
|
|
||||||
|
HTTP::get_listener_for_rid(&mut bstate, listener_rid)
|
||||||
|
.map_err(HttpNextError::Resource)?
|
||||||
|
};
|
||||||
|
|
||||||
let listen_properties = HTTP::listen_properties_from_listener(&listener)?;
|
let listen_properties = HTTP::listen_properties_from_listener(&listener)?;
|
||||||
|
|
||||||
|
@ -1110,6 +1140,117 @@ where
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn serve_on_quic(
|
||||||
|
state: Rc<RefCell<OpState>>,
|
||||||
|
listener: Rc<QuicListener>,
|
||||||
|
) -> Result<(ResourceId, &'static str, String), HttpNextError> {
|
||||||
|
let endpoint = listener.0.clone();
|
||||||
|
let (tx, rx) = tokio::sync::mpsc::channel(10);
|
||||||
|
let resource: Rc<HttpJoinHandle> = Rc::new(HttpJoinHandle::new(rx));
|
||||||
|
let lifetime = resource.lifetime();
|
||||||
|
|
||||||
|
let local_addr = endpoint.local_addr()?;
|
||||||
|
let properties = listener_properties(
|
||||||
|
NetworkStreamType::Tls,
|
||||||
|
NetworkStreamAddress::Ip(local_addr),
|
||||||
|
)?;
|
||||||
|
|
||||||
|
let handle = spawn(async move {
|
||||||
|
while let Some(incoming) = endpoint
|
||||||
|
.accept()
|
||||||
|
.or_cancel(lifetime.listen_cancel_handle.clone())
|
||||||
|
.await?
|
||||||
|
{
|
||||||
|
let lifetime = lifetime.clone();
|
||||||
|
let tx = tx.clone();
|
||||||
|
spawn(async move {
|
||||||
|
let conn = incoming
|
||||||
|
.accept()?
|
||||||
|
.or_cancel(lifetime.listen_cancel_handle.clone())
|
||||||
|
.await??;
|
||||||
|
|
||||||
|
let request_info = HttpConnectionProperties {
|
||||||
|
peer_address: Rc::from(conn.remote_address().ip().to_string()),
|
||||||
|
peer_port: Some(conn.remote_address().port()),
|
||||||
|
local_port: properties.local_port,
|
||||||
|
stream_type: properties.stream_type,
|
||||||
|
};
|
||||||
|
|
||||||
|
let mut h3_conn = h3::server::builder()
|
||||||
|
.send_grease(true)
|
||||||
|
.enable_datagram(true)
|
||||||
|
.enable_connect(true)
|
||||||
|
.enable_webtransport(true)
|
||||||
|
.max_webtransport_sessions(1u32.into())
|
||||||
|
.build(h3_quinn::Connection::new(conn))
|
||||||
|
.or_cancel(lifetime.connection_cancel_handle.clone())
|
||||||
|
.await??;
|
||||||
|
|
||||||
|
while let Some((req, stream)) = h3_conn
|
||||||
|
.accept()
|
||||||
|
.or_cancel(lifetime.connection_cancel_handle.clone())
|
||||||
|
.await??
|
||||||
|
{
|
||||||
|
let request_info = request_info.clone();
|
||||||
|
let lifetime = lifetime.clone();
|
||||||
|
let tx = tx.clone();
|
||||||
|
spawn(async move {
|
||||||
|
let (mut send, recv) = stream.split();
|
||||||
|
|
||||||
|
let (parts, _body) = req.into_parts();
|
||||||
|
let req = hyper::Request::from_parts(parts, recv.into());
|
||||||
|
|
||||||
|
match handle_request(
|
||||||
|
req,
|
||||||
|
request_info,
|
||||||
|
lifetime.server_state.clone(),
|
||||||
|
tx.clone(),
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
{
|
||||||
|
Ok(res) => {
|
||||||
|
let (parts, body) = res.into_parts();
|
||||||
|
let res = hyper::Response::from_parts(parts, ());
|
||||||
|
|
||||||
|
send.send_response(res).await?;
|
||||||
|
|
||||||
|
let mut body = body.into_data_stream();
|
||||||
|
while let Some(v) = body.next().await {
|
||||||
|
send.send_data(v.unwrap()).await?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Err(_) => {
|
||||||
|
send
|
||||||
|
.send_response(
|
||||||
|
hyper::Response::builder().status(500).body(()).unwrap(),
|
||||||
|
)
|
||||||
|
.await?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok::<_, HttpNextError>(())
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok::<_, HttpNextError>(())
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
});
|
||||||
|
|
||||||
|
// Set the handle after we start the future
|
||||||
|
*RcRef::map(&resource, |this| &this.join_handle)
|
||||||
|
.try_borrow_mut()
|
||||||
|
.unwrap() = Some(handle);
|
||||||
|
|
||||||
|
Ok((
|
||||||
|
state.borrow_mut().resource_table.add_rc(resource),
|
||||||
|
properties.scheme,
|
||||||
|
properties.fallback_host,
|
||||||
|
))
|
||||||
|
}
|
||||||
|
|
||||||
#[op2]
|
#[op2]
|
||||||
#[serde]
|
#[serde]
|
||||||
pub fn op_http_serve_on<HTTP>(
|
pub fn op_http_serve_on<HTTP>(
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license.
|
// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license.
|
||||||
|
use crate::service::RequestBody;
|
||||||
use bytes::Bytes;
|
use bytes::Bytes;
|
||||||
use deno_core::futures::stream::Peekable;
|
use deno_core::futures::stream::Peekable;
|
||||||
use deno_core::futures::Stream;
|
use deno_core::futures::Stream;
|
||||||
|
@ -10,7 +11,6 @@ use deno_core::BufView;
|
||||||
use deno_core::RcRef;
|
use deno_core::RcRef;
|
||||||
use deno_core::Resource;
|
use deno_core::Resource;
|
||||||
use hyper::body::Body;
|
use hyper::body::Body;
|
||||||
use hyper::body::Incoming;
|
|
||||||
use hyper::body::SizeHint;
|
use hyper::body::SizeHint;
|
||||||
use std::borrow::Cow;
|
use std::borrow::Cow;
|
||||||
use std::pin::Pin;
|
use std::pin::Pin;
|
||||||
|
@ -19,10 +19,10 @@ use std::task::ready;
|
||||||
use std::task::Poll;
|
use std::task::Poll;
|
||||||
|
|
||||||
/// Converts a hyper incoming body stream into a stream of [`Bytes`] that we can use to read in V8.
|
/// Converts a hyper incoming body stream into a stream of [`Bytes`] that we can use to read in V8.
|
||||||
struct ReadFuture(Incoming);
|
struct ReadFuture(RequestBody);
|
||||||
|
|
||||||
impl Stream for ReadFuture {
|
impl Stream for ReadFuture {
|
||||||
type Item = Result<Bytes, hyper::Error>;
|
type Item = Result<Bytes, crate::HttpNextError>;
|
||||||
|
|
||||||
fn poll_next(
|
fn poll_next(
|
||||||
self: Pin<&mut Self>,
|
self: Pin<&mut Self>,
|
||||||
|
@ -53,12 +53,15 @@ impl Stream for ReadFuture {
|
||||||
pub struct HttpRequestBody(AsyncRefCell<Peekable<ReadFuture>>, SizeHint);
|
pub struct HttpRequestBody(AsyncRefCell<Peekable<ReadFuture>>, SizeHint);
|
||||||
|
|
||||||
impl HttpRequestBody {
|
impl HttpRequestBody {
|
||||||
pub fn new(body: Incoming) -> Self {
|
pub fn new(body: RequestBody) -> Self {
|
||||||
let size_hint = body.size_hint();
|
let size_hint = body.size_hint();
|
||||||
Self(AsyncRefCell::new(ReadFuture(body).peekable()), size_hint)
|
Self(AsyncRefCell::new(ReadFuture(body).peekable()), size_hint)
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn read(self: Rc<Self>, limit: usize) -> Result<BufView, hyper::Error> {
|
async fn read(
|
||||||
|
self: Rc<Self>,
|
||||||
|
limit: usize,
|
||||||
|
) -> Result<BufView, crate::HttpNextError> {
|
||||||
let peekable = RcRef::map(self, |this| &this.0);
|
let peekable = RcRef::map(self, |this| &this.0);
|
||||||
let mut peekable = peekable.borrow_mut().await;
|
let mut peekable = peekable.borrow_mut().await;
|
||||||
match Pin::new(&mut *peekable).peek_mut().await {
|
match Pin::new(&mut *peekable).peek_mut().await {
|
||||||
|
|
|
@ -196,7 +196,7 @@ impl HttpPropertyExtractor for DefaultHttpPropertyExtractor {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn listener_properties(
|
pub(crate) fn listener_properties(
|
||||||
stream_type: NetworkStreamType,
|
stream_type: NetworkStreamType,
|
||||||
local_address: NetworkStreamAddress,
|
local_address: NetworkStreamAddress,
|
||||||
) -> Result<HttpListenProperties, std::io::Error> {
|
) -> Result<HttpListenProperties, std::io::Error> {
|
||||||
|
|
|
@ -2,6 +2,7 @@
|
||||||
use crate::request_properties::HttpConnectionProperties;
|
use crate::request_properties::HttpConnectionProperties;
|
||||||
use crate::response_body::ResponseBytesInner;
|
use crate::response_body::ResponseBytesInner;
|
||||||
use crate::response_body::ResponseStreamResult;
|
use crate::response_body::ResponseStreamResult;
|
||||||
|
use bytes::Buf;
|
||||||
use deno_core::futures::ready;
|
use deno_core::futures::ready;
|
||||||
use deno_core::BufView;
|
use deno_core::BufView;
|
||||||
use deno_core::OpState;
|
use deno_core::OpState;
|
||||||
|
@ -29,7 +30,7 @@ use std::task::Poll;
|
||||||
use std::task::Waker;
|
use std::task::Waker;
|
||||||
use tokio::sync::oneshot;
|
use tokio::sync::oneshot;
|
||||||
|
|
||||||
pub type Request = hyper::Request<Incoming>;
|
pub type Request = hyper::Request<RequestBody>;
|
||||||
pub type Response = hyper::Response<HttpRecordResponse>;
|
pub type Response = hyper::Response<HttpRecordResponse>;
|
||||||
|
|
||||||
#[cfg(feature = "__http_tracing")]
|
#[cfg(feature = "__http_tracing")]
|
||||||
|
@ -148,13 +149,54 @@ impl std::ops::Deref for HttpServerState {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
enum RequestBodyState {
|
type H3Stream = h3::server::RequestStream<h3_quinn::RecvStream, BufView>;
|
||||||
|
|
||||||
|
pub enum RequestBody {
|
||||||
Incoming(Incoming),
|
Incoming(Incoming),
|
||||||
|
H3(H3Stream),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Body for RequestBody {
|
||||||
|
type Data = bytes::Bytes;
|
||||||
|
type Error = crate::HttpNextError;
|
||||||
|
|
||||||
|
fn poll_frame(
|
||||||
|
self: Pin<&mut Self>,
|
||||||
|
cx: &mut Context<'_>,
|
||||||
|
) -> Poll<Option<Result<Frame<Self::Data>, Self::Error>>> {
|
||||||
|
match self.get_mut() {
|
||||||
|
Self::Incoming(incoming) => {
|
||||||
|
Pin::new(incoming).poll_frame(cx).map_err(Into::into)
|
||||||
|
}
|
||||||
|
Self::H3(stream) => match stream.poll_recv_data(cx)? {
|
||||||
|
Poll::Ready(v) => Poll::Ready(
|
||||||
|
v.map(|mut v| Ok(Frame::data(v.copy_to_bytes(v.remaining())))),
|
||||||
|
),
|
||||||
|
Poll::Pending => Poll::Pending,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<Incoming> for RequestBody {
|
||||||
|
fn from(value: Incoming) -> Self {
|
||||||
|
Self::Incoming(value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<H3Stream> for RequestBody {
|
||||||
|
fn from(value: H3Stream) -> Self {
|
||||||
|
Self::H3(value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
enum RequestBodyState {
|
||||||
|
Incoming(RequestBody),
|
||||||
Resource(#[allow(dead_code)] HttpRequestBodyAutocloser),
|
Resource(#[allow(dead_code)] HttpRequestBodyAutocloser),
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<Incoming> for RequestBodyState {
|
impl From<RequestBody> for RequestBodyState {
|
||||||
fn from(value: Incoming) -> Self {
|
fn from(value: RequestBody) -> Self {
|
||||||
RequestBodyState::Incoming(value)
|
RequestBodyState::Incoming(value)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -365,7 +407,7 @@ impl HttpRecord {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Take the Hyper body from this record.
|
/// Take the Hyper body from this record.
|
||||||
pub fn take_request_body(&self) -> Option<Incoming> {
|
pub fn take_request_body(&self) -> Option<RequestBody> {
|
||||||
let body_holder = &mut self.self_mut().request_body;
|
let body_holder = &mut self.self_mut().request_body;
|
||||||
let body = body_holder.take();
|
let body = body_holder.take();
|
||||||
match body {
|
match body {
|
||||||
|
@ -544,13 +586,13 @@ impl Body for HttpRecordResponse {
|
||||||
if let Some(trailers) = inner.trailers.take() {
|
if let Some(trailers) = inner.trailers.take() {
|
||||||
return Poll::Ready(Some(Ok(Frame::trailers(trailers))));
|
return Poll::Ready(Some(Ok(Frame::trailers(trailers))));
|
||||||
}
|
}
|
||||||
unreachable!()
|
return Poll::Ready(None);
|
||||||
}
|
}
|
||||||
ResponseBytesInner::Bytes(..) => {
|
ResponseBytesInner::Bytes(..) => {
|
||||||
drop(inner);
|
drop(inner);
|
||||||
let ResponseBytesInner::Bytes(data) = record.take_response_body()
|
let ResponseBytesInner::Bytes(data) = record.take_response_body()
|
||||||
else {
|
else {
|
||||||
unreachable!();
|
return Poll::Ready(None);
|
||||||
};
|
};
|
||||||
return Poll::Ready(Some(Ok(Frame::data(data))));
|
return Poll::Ready(Some(Ok(Frame::data(data))));
|
||||||
}
|
}
|
||||||
|
@ -665,7 +707,7 @@ mod tests {
|
||||||
};
|
};
|
||||||
let svc = service_fn(move |req: hyper::Request<Incoming>| {
|
let svc = service_fn(move |req: hyper::Request<Incoming>| {
|
||||||
handle_request(
|
handle_request(
|
||||||
req,
|
req.map(Into::into),
|
||||||
request_info.clone(),
|
request_info.clone(),
|
||||||
server_state.clone(),
|
server_state.clone(),
|
||||||
tx.clone(),
|
tx.clone(),
|
||||||
|
|
|
@ -29,7 +29,6 @@ import {
|
||||||
op_quic_incoming_remote_addr,
|
op_quic_incoming_remote_addr,
|
||||||
op_quic_incoming_remote_addr_validated,
|
op_quic_incoming_remote_addr_validated,
|
||||||
op_quic_listener_accept,
|
op_quic_listener_accept,
|
||||||
op_quic_listener_stop,
|
|
||||||
op_quic_recv_stream_get_id,
|
op_quic_recv_stream_get_id,
|
||||||
op_quic_send_stream_get_id,
|
op_quic_send_stream_get_id,
|
||||||
op_quic_send_stream_get_priority,
|
op_quic_send_stream_get_priority,
|
||||||
|
@ -45,9 +44,11 @@ import {
|
||||||
} from "ext:deno_web/06_streams.js";
|
} from "ext:deno_web/06_streams.js";
|
||||||
import { loadTlsKeyPair } from "ext:deno_net/02_tls.js";
|
import { loadTlsKeyPair } from "ext:deno_net/02_tls.js";
|
||||||
const {
|
const {
|
||||||
|
internalRidSymbol,
|
||||||
BadResourcePrototype,
|
BadResourcePrototype,
|
||||||
} = core;
|
} = core;
|
||||||
const {
|
const {
|
||||||
|
ObjectDefineProperty,
|
||||||
ObjectPrototypeIsPrototypeOf,
|
ObjectPrototypeIsPrototypeOf,
|
||||||
PromisePrototypeThen,
|
PromisePrototypeThen,
|
||||||
SymbolAsyncIterator,
|
SymbolAsyncIterator,
|
||||||
|
@ -84,7 +85,10 @@ class QuicEndpoint {
|
||||||
}
|
}
|
||||||
|
|
||||||
get addr() {
|
get addr() {
|
||||||
return op_quic_endpoint_get_addr(this.#endpoint);
|
return {
|
||||||
|
...op_quic_endpoint_get_addr(this.#endpoint),
|
||||||
|
transport: "quic",
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
listen(options) {
|
listen(options) {
|
||||||
|
@ -117,6 +121,16 @@ class QuicListener {
|
||||||
constructor(listener, endpoint) {
|
constructor(listener, endpoint) {
|
||||||
this.#listener = listener;
|
this.#listener = listener;
|
||||||
this.#endpoint = endpoint;
|
this.#endpoint = endpoint;
|
||||||
|
|
||||||
|
ObjectDefineProperty(this, internalRidSymbol, {
|
||||||
|
__proto__: null,
|
||||||
|
enumerable: false,
|
||||||
|
value: listener,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
get addr() {
|
||||||
|
return this.#endpoint.addr;
|
||||||
}
|
}
|
||||||
|
|
||||||
get endpoint() {
|
get endpoint() {
|
||||||
|
@ -151,7 +165,7 @@ class QuicListener {
|
||||||
}
|
}
|
||||||
|
|
||||||
stop() {
|
stop() {
|
||||||
op_quic_listener_stop(this.#listener);
|
core.close(this.#listener);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -20,7 +20,7 @@ deno_tls.workspace = true
|
||||||
hickory-proto = "0.25.0-alpha.4"
|
hickory-proto = "0.25.0-alpha.4"
|
||||||
hickory-resolver.workspace = true
|
hickory-resolver.workspace = true
|
||||||
pin-project.workspace = true
|
pin-project.workspace = true
|
||||||
quinn = { version = "0.11.6", default-features = false, features = ["runtime-tokio", "rustls", "ring"] }
|
quinn.workspace = true
|
||||||
rustls-tokio-stream.workspace = true
|
rustls-tokio-stream.workspace = true
|
||||||
serde.workspace = true
|
serde.workspace = true
|
||||||
socket2.workspace = true
|
socket2.workspace = true
|
||||||
|
|
|
@ -5,7 +5,7 @@ pub mod ops;
|
||||||
pub mod ops_tls;
|
pub mod ops_tls;
|
||||||
#[cfg(unix)]
|
#[cfg(unix)]
|
||||||
pub mod ops_unix;
|
pub mod ops_unix;
|
||||||
mod quic;
|
pub mod quic;
|
||||||
pub mod raw;
|
pub mod raw;
|
||||||
pub mod resolve_addr;
|
pub mod resolve_addr;
|
||||||
pub mod tcp;
|
pub mod tcp;
|
||||||
|
@ -190,7 +190,6 @@ deno_core::extension!(deno_net,
|
||||||
quic::op_quic_incoming_remote_addr,
|
quic::op_quic_incoming_remote_addr,
|
||||||
quic::op_quic_incoming_remote_addr_validated,
|
quic::op_quic_incoming_remote_addr_validated,
|
||||||
quic::op_quic_listener_accept,
|
quic::op_quic_listener_accept,
|
||||||
quic::op_quic_listener_stop,
|
|
||||||
quic::op_quic_recv_stream_get_id,
|
quic::op_quic_recv_stream_get_id,
|
||||||
quic::op_quic_send_stream_get_id,
|
quic::op_quic_send_stream_get_id,
|
||||||
quic::op_quic_send_stream_get_priority,
|
quic::op_quic_send_stream_get_priority,
|
||||||
|
|
|
@ -250,24 +250,27 @@ pub(crate) fn op_quic_endpoint_close(
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
struct ListenerResource(quinn::Endpoint, Arc<QuicServerConfig>);
|
pub struct ListenerResource(pub quinn::Endpoint, Arc<QuicServerConfig>);
|
||||||
|
|
||||||
impl Drop for ListenerResource {
|
impl Resource for ListenerResource {
|
||||||
fn drop(&mut self) {
|
fn name(&self) -> Cow<str> {
|
||||||
|
"quicListener".into()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn close(self: Rc<Self>) {
|
||||||
self.0.set_server_config(None);
|
self.0.set_server_config(None);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl GarbageCollected for ListenerResource {}
|
|
||||||
|
|
||||||
#[op2]
|
#[op2]
|
||||||
#[cppgc]
|
#[smi]
|
||||||
pub(crate) fn op_quic_endpoint_listen(
|
pub(crate) fn op_quic_endpoint_listen(
|
||||||
|
state: Rc<RefCell<OpState>>,
|
||||||
#[cppgc] endpoint: &EndpointResource,
|
#[cppgc] endpoint: &EndpointResource,
|
||||||
#[serde] args: ListenArgs,
|
#[serde] args: ListenArgs,
|
||||||
#[serde] transport_config: TransportConfig,
|
#[serde] transport_config: TransportConfig,
|
||||||
#[cppgc] keys: &TlsKeysHolder,
|
#[cppgc] keys: &TlsKeysHolder,
|
||||||
) -> Result<ListenerResource, QuicError> {
|
) -> Result<u32, QuicError> {
|
||||||
if !endpoint.can_listen {
|
if !endpoint.can_listen {
|
||||||
return Err(QuicError::CannotListen);
|
return Err(QuicError::CannotListen);
|
||||||
}
|
}
|
||||||
|
@ -301,7 +304,11 @@ pub(crate) fn op_quic_endpoint_listen(
|
||||||
|
|
||||||
endpoint.endpoint.set_server_config(Some(config));
|
endpoint.endpoint.set_server_config(Some(config));
|
||||||
|
|
||||||
Ok(ListenerResource(endpoint.endpoint.clone(), server_config))
|
let rid = state
|
||||||
|
.borrow_mut()
|
||||||
|
.resource_table
|
||||||
|
.add(ListenerResource(endpoint.endpoint.clone(), server_config));
|
||||||
|
Ok(rid)
|
||||||
}
|
}
|
||||||
|
|
||||||
struct ConnectionResource(
|
struct ConnectionResource(
|
||||||
|
@ -321,8 +328,13 @@ impl GarbageCollected for IncomingResource {}
|
||||||
#[op2(async)]
|
#[op2(async)]
|
||||||
#[cppgc]
|
#[cppgc]
|
||||||
pub(crate) async fn op_quic_listener_accept(
|
pub(crate) async fn op_quic_listener_accept(
|
||||||
#[cppgc] resource: &ListenerResource,
|
state: Rc<RefCell<OpState>>,
|
||||||
|
#[smi] resource_id: u32,
|
||||||
) -> Result<IncomingResource, QuicError> {
|
) -> Result<IncomingResource, QuicError> {
|
||||||
|
let resource = state
|
||||||
|
.borrow()
|
||||||
|
.resource_table
|
||||||
|
.get::<ListenerResource>(resource_id)?;
|
||||||
match resource.0.accept().await {
|
match resource.0.accept().await {
|
||||||
Some(incoming) => Ok(IncomingResource(
|
Some(incoming) => Ok(IncomingResource(
|
||||||
RefCell::new(Some(incoming)),
|
RefCell::new(Some(incoming)),
|
||||||
|
@ -332,11 +344,6 @@ pub(crate) async fn op_quic_listener_accept(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[op2(fast)]
|
|
||||||
pub(crate) fn op_quic_listener_stop(#[cppgc] resource: &ListenerResource) {
|
|
||||||
resource.0.set_server_config(None);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[op2]
|
#[op2]
|
||||||
#[string]
|
#[string]
|
||||||
pub(crate) fn op_quic_incoming_local_ip(
|
pub(crate) fn op_quic_incoming_local_ip(
|
||||||
|
|
|
@ -1013,6 +1013,8 @@ fn get_http_next_error(error: &HttpNextError) -> &'static str {
|
||||||
HttpNextError::Io(e) => get_io_error_class(e),
|
HttpNextError::Io(e) => get_io_error_class(e),
|
||||||
HttpNextError::WebSocketUpgrade(e) => get_websocket_upgrade_error(e),
|
HttpNextError::WebSocketUpgrade(e) => get_websocket_upgrade_error(e),
|
||||||
HttpNextError::Hyper(e) => get_hyper_error_class(e),
|
HttpNextError::Hyper(e) => get_hyper_error_class(e),
|
||||||
|
HttpNextError::H3(_) => "Http",
|
||||||
|
HttpNextError::QuinnConnectionError(_) => "Http",
|
||||||
HttpNextError::JoinError(_) => "Error",
|
HttpNextError::JoinError(_) => "Error",
|
||||||
HttpNextError::Canceled(e) => {
|
HttpNextError::Canceled(e) => {
|
||||||
let io_err: io::Error = e.to_owned().into();
|
let io_err: io::Error = e.to_owned().into();
|
||||||
|
|
Loading…
Add table
Reference in a new issue