mirror of
https://github.com/denoland/deno.git
synced 2024-12-02 17:01:14 -05:00
introduce more ops to wait for connection ready
This commit is contained in:
parent
821d5a5653
commit
29fe1768a8
5 changed files with 134 additions and 58 deletions
|
@ -353,6 +353,8 @@ deno_core::extension!(deno_node,
|
||||||
ops::zlib::brotli::op_brotli_decompress_stream_end,
|
ops::zlib::brotli::op_brotli_decompress_stream_end,
|
||||||
ops::http::op_node_http_fetch_response_upgrade,
|
ops::http::op_node_http_fetch_response_upgrade,
|
||||||
ops::http::op_node_http_request_with_conn<P>,
|
ops::http::op_node_http_request_with_conn<P>,
|
||||||
|
ops::http::op_node_http_await_response,
|
||||||
|
ops::http::op_node_http_wait_for_connection,
|
||||||
ops::http2::op_http2_connect,
|
ops::http2::op_http2_connect,
|
||||||
ops::http2::op_http2_poll_client_connection,
|
ops::http2::op_http2_poll_client_connection,
|
||||||
ops::http2::op_http2_client_request,
|
ops::http2::op_http2_client_request,
|
||||||
|
|
|
@ -4,11 +4,14 @@ use std::borrow::Cow;
|
||||||
use std::cell::RefCell;
|
use std::cell::RefCell;
|
||||||
use std::pin::Pin;
|
use std::pin::Pin;
|
||||||
use std::rc::Rc;
|
use std::rc::Rc;
|
||||||
|
use std::sync::atomic::AtomicBool;
|
||||||
|
use std::sync::Arc;
|
||||||
use std::task::Context;
|
use std::task::Context;
|
||||||
use std::task::Poll;
|
use std::task::Poll;
|
||||||
|
|
||||||
use bytes::Bytes;
|
use bytes::Bytes;
|
||||||
use deno_core::anyhow;
|
use deno_core::anyhow;
|
||||||
|
use deno_core::anyhow::Error;
|
||||||
use deno_core::error::bad_resource;
|
use deno_core::error::bad_resource;
|
||||||
use deno_core::error::type_error;
|
use deno_core::error::type_error;
|
||||||
use deno_core::error::AnyError;
|
use deno_core::error::AnyError;
|
||||||
|
@ -17,6 +20,7 @@ use deno_core::futures::Future;
|
||||||
use deno_core::futures::FutureExt;
|
use deno_core::futures::FutureExt;
|
||||||
use deno_core::futures::Stream;
|
use deno_core::futures::Stream;
|
||||||
use deno_core::futures::StreamExt;
|
use deno_core::futures::StreamExt;
|
||||||
|
use deno_core::futures::TryFutureExt;
|
||||||
use deno_core::op2;
|
use deno_core::op2;
|
||||||
use deno_core::serde::Serialize;
|
use deno_core::serde::Serialize;
|
||||||
use deno_core::unsync::spawn;
|
use deno_core::unsync::spawn;
|
||||||
|
@ -39,8 +43,10 @@ use http::header::HeaderValue;
|
||||||
use http::header::AUTHORIZATION;
|
use http::header::AUTHORIZATION;
|
||||||
use http::header::CONTENT_LENGTH;
|
use http::header::CONTENT_LENGTH;
|
||||||
use http::Method;
|
use http::Method;
|
||||||
|
use http::Response;
|
||||||
use http_body_util::BodyExt;
|
use http_body_util::BodyExt;
|
||||||
use hyper::body::Frame;
|
use hyper::body::Frame;
|
||||||
|
use hyper::body::Incoming;
|
||||||
use hyper_util::rt::TokioIo;
|
use hyper_util::rt::TokioIo;
|
||||||
use std::cmp::min;
|
use std::cmp::min;
|
||||||
use tokio::io::AsyncReadExt;
|
use tokio::io::AsyncReadExt;
|
||||||
|
@ -60,6 +66,29 @@ pub struct NodeHttpResponse {
|
||||||
pub error: Option<String>,
|
pub error: Option<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub struct NodeHttpConnReady {
|
||||||
|
recv: tokio::sync::oneshot::Receiver<()>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl deno_core::Resource for NodeHttpConnReady {
|
||||||
|
fn name(&self) -> Cow<str> {
|
||||||
|
"nodeHttpConnReady".into()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct NodeHttpClientResponse {
|
||||||
|
response:
|
||||||
|
Pin<Box<dyn Future<Output = Result<Response<Incoming>, Error>> + Send>>,
|
||||||
|
url: String,
|
||||||
|
connection_started: Arc<AtomicBool>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl deno_core::Resource for NodeHttpClientResponse {
|
||||||
|
fn name(&self) -> Cow<str> {
|
||||||
|
"nodeHttpClientResponse".into()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[op2(async)]
|
#[op2(async)]
|
||||||
#[serde]
|
#[serde]
|
||||||
pub async fn op_node_http_request_with_conn<P>(
|
pub async fn op_node_http_request_with_conn<P>(
|
||||||
|
@ -69,7 +98,7 @@ pub async fn op_node_http_request_with_conn<P>(
|
||||||
#[serde] headers: Vec<(ByteString, ByteString)>,
|
#[serde] headers: Vec<(ByteString, ByteString)>,
|
||||||
#[smi] body: Option<ResourceId>,
|
#[smi] body: Option<ResourceId>,
|
||||||
#[smi] conn_rid: ResourceId,
|
#[smi] conn_rid: ResourceId,
|
||||||
) -> Result<NodeHttpResponse, AnyError>
|
) -> Result<(ResourceId, ResourceId), AnyError>
|
||||||
where
|
where
|
||||||
P: crate::NodePermissions + 'static,
|
P: crate::NodePermissions + 'static,
|
||||||
{
|
{
|
||||||
|
@ -85,11 +114,18 @@ where
|
||||||
let io = TokioIo::new(tcp_stream);
|
let io = TokioIo::new(tcp_stream);
|
||||||
let (mut sender, conn) = hyper::client::conn::http1::handshake(io).await?;
|
let (mut sender, conn) = hyper::client::conn::http1::handshake(io).await?;
|
||||||
|
|
||||||
|
let connection_started = Arc::new(AtomicBool::new(false));
|
||||||
|
let conn_start = connection_started.clone();
|
||||||
|
|
||||||
|
let (notify, receiver) = tokio::sync::oneshot::channel::<()>();
|
||||||
|
|
||||||
// Spawn a task to poll the connection, driving the HTTP state
|
// Spawn a task to poll the connection, driving the HTTP state
|
||||||
let _handle = tokio::task::spawn(async move {
|
let _handle = tokio::task::spawn(async move {
|
||||||
eprintln!("connection started");
|
eprintln!("rs: connection started");
|
||||||
|
conn_start.store(true, std::sync::atomic::Ordering::Relaxed);
|
||||||
|
let _ = notify.send(());
|
||||||
conn.await?;
|
conn.await?;
|
||||||
eprintln!("connection completed");
|
eprintln!("rs: connection completed");
|
||||||
Ok::<_, AnyError>(())
|
Ok::<_, AnyError>(())
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -156,12 +192,64 @@ where
|
||||||
request.headers_mut().insert(CONTENT_LENGTH, len.into());
|
request.headers_mut().insert(CONTENT_LENGTH, len.into());
|
||||||
}
|
}
|
||||||
|
|
||||||
// eprintln!("rs: sending request: {request:?}");
|
eprintln!("rs: sending request: {request:?}");
|
||||||
// let req_fut = sender.send_request(request);
|
// let req_fut = sender.send_request(request);
|
||||||
// let res = tokio::time::timeout(Duration::from_secs(10), req_fut).await??;
|
// let res = tokio::time::timeout(Duration::from_secs(10), req_fut).await??;
|
||||||
let res = sender.send_request(request).await?;
|
let res = sender.send_request(request).map_err(Error::from).boxed();
|
||||||
|
let rid = state
|
||||||
|
.borrow_mut()
|
||||||
|
.resource_table
|
||||||
|
.add(NodeHttpClientResponse {
|
||||||
|
response: res,
|
||||||
|
url: url.clone(),
|
||||||
|
connection_started,
|
||||||
|
});
|
||||||
|
let conn_rid = state
|
||||||
|
.borrow_mut()
|
||||||
|
.resource_table
|
||||||
|
.add(NodeHttpConnReady { recv: receiver });
|
||||||
|
|
||||||
|
Ok((rid, conn_rid))
|
||||||
|
}
|
||||||
|
|
||||||
|
#[op2(async)]
|
||||||
|
#[serde]
|
||||||
|
pub async fn op_node_http_wait_for_connection(
|
||||||
|
state: Rc<RefCell<OpState>>,
|
||||||
|
#[smi] rid: ResourceId,
|
||||||
|
) -> Result<ResourceId, AnyError> {
|
||||||
|
let resource = state
|
||||||
|
.borrow_mut()
|
||||||
|
.resource_table
|
||||||
|
.take::<NodeHttpConnReady>(rid)?;
|
||||||
|
let resource =
|
||||||
|
Rc::try_unwrap(resource).map_err(|_| bad_resource("NodeHttpConnReady"))?;
|
||||||
|
resource.recv.await?;
|
||||||
|
Ok(rid)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[op2(async)]
|
||||||
|
#[serde]
|
||||||
|
pub async fn op_node_http_await_response(
|
||||||
|
state: Rc<RefCell<OpState>>,
|
||||||
|
#[smi] rid: ResourceId,
|
||||||
|
) -> Result<NodeHttpResponse, AnyError> {
|
||||||
|
let resource = state
|
||||||
|
.borrow_mut()
|
||||||
|
.resource_table
|
||||||
|
.take::<NodeHttpClientResponse>(rid)?;
|
||||||
|
let resource = Rc::try_unwrap(resource)
|
||||||
|
.map_err(|_| bad_resource("NodeHttpClientResponse"))?;
|
||||||
|
|
||||||
|
eprintln!(
|
||||||
|
"rs: awaiting response: {}",
|
||||||
|
resource
|
||||||
|
.connection_started
|
||||||
|
.load(std::sync::atomic::Ordering::Relaxed)
|
||||||
|
);
|
||||||
|
let res = resource.response.await?;
|
||||||
// let res = tokio::time::timeout(Duration::from_secs(10), req_fut).await??;
|
// let res = tokio::time::timeout(Duration::from_secs(10), req_fut).await??;
|
||||||
// eprintln!("rs: received response");
|
eprintln!("rs: received response");
|
||||||
|
|
||||||
let status = res.status();
|
let status = res.status();
|
||||||
let mut res_headers = Vec::new();
|
let mut res_headers = Vec::new();
|
||||||
|
@ -196,7 +284,7 @@ where
|
||||||
status: status.as_u16(),
|
status: status.as_u16(),
|
||||||
status_text: status.canonical_reason().unwrap_or("").to_string(),
|
status_text: status.canonical_reason().unwrap_or("").to_string(),
|
||||||
headers: res_headers,
|
headers: res_headers,
|
||||||
url,
|
url: resource.url,
|
||||||
response_rid,
|
response_rid,
|
||||||
content_length,
|
content_length,
|
||||||
remote_addr_ip,
|
remote_addr_ip,
|
||||||
|
@ -461,6 +549,7 @@ impl Stream for NodeHttpResourceToBodyAdapter {
|
||||||
Poll::Ready(res) => match res {
|
Poll::Ready(res) => match res {
|
||||||
Ok(buf) if buf.is_empty() => Poll::Ready(None),
|
Ok(buf) if buf.is_empty() => Poll::Ready(None),
|
||||||
Ok(buf) => {
|
Ok(buf) => {
|
||||||
|
println!("rs: reading: {len}", len = buf.len());
|
||||||
this.1 = Some(this.0.clone().read(64 * 1024));
|
this.1 = Some(this.0.clone().read(64 * 1024));
|
||||||
Poll::Ready(Some(Ok(buf.to_vec().into())))
|
Poll::Ready(Some(Ok(buf.to_vec().into())))
|
||||||
}
|
}
|
||||||
|
|
|
@ -509,30 +509,25 @@ Object.defineProperties(
|
||||||
/** Right after socket is ready, we need to writeHeader() to setup the request and
|
/** Right after socket is ready, we need to writeHeader() to setup the request and
|
||||||
* client. This is invoked by onSocket(). */
|
* client. This is invoked by onSocket(). */
|
||||||
_flushHeaders() {
|
_flushHeaders() {
|
||||||
// console.log("flushHeaders");
|
console.log("flushHeaders");
|
||||||
if (this.socket) {
|
if (this.socket) {
|
||||||
if (!this._headerSent && this._header !== null) {
|
if (!this._headerSent && this._header !== null) {
|
||||||
this._writeHeader();
|
this._writeHeader();
|
||||||
this._headerSent = true;
|
this._headerSent = true;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// console.log("socket not found");
|
console.log("socket not found");
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
// deno-lint-ignore no-explicit-any
|
// deno-lint-ignore no-explicit-any
|
||||||
_send(data: any, encoding?: string | null, callback?: () => void) {
|
_send(data: any, encoding?: string | null, callback?: () => void) {
|
||||||
console.log("writing data:", data, "socket:", this.socket);
|
|
||||||
if (this.socket) {
|
if (this.socket) {
|
||||||
console.log("im never invoked");
|
|
||||||
if (!this._headerSent && this._header !== null) {
|
if (!this._headerSent && this._header !== null) {
|
||||||
this._writeHeader();
|
this._writeHeader();
|
||||||
this._headerSent = true;
|
this._headerSent = true;
|
||||||
}
|
}
|
||||||
|
return this._writeRaw(data, encoding, callback);
|
||||||
if (this._headerSent) {
|
|
||||||
return this._writeRaw(data, encoding, callback);
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
console.log("pushing data to outputData");
|
console.log("pushing data to outputData");
|
||||||
this.outputData.push({ data, encoding, callback });
|
this.outputData.push({ data, encoding, callback });
|
||||||
|
@ -543,39 +538,6 @@ Object.defineProperties(
|
||||||
throw new ERR_METHOD_NOT_IMPLEMENTED("_writeHeader()");
|
throw new ERR_METHOD_NOT_IMPLEMENTED("_writeHeader()");
|
||||||
},
|
},
|
||||||
|
|
||||||
async _flushBuffer() {
|
|
||||||
const outputLength = this.outputData.length;
|
|
||||||
if (outputLength <= 0 || !this.socket || !this._bodyWriter) {
|
|
||||||
return undefined;
|
|
||||||
}
|
|
||||||
|
|
||||||
const outputData = this.outputData;
|
|
||||||
let ret;
|
|
||||||
// Retain for(;;) loop for performance reasons
|
|
||||||
// Refs: https://github.com/nodejs/node/pull/30958
|
|
||||||
for (let i = 0; i < outputLength; i++) {
|
|
||||||
let { data, encoding, callback } = outputData[i];
|
|
||||||
if (typeof data === "string") {
|
|
||||||
data = Buffer.from(data, encoding);
|
|
||||||
}
|
|
||||||
if (data instanceof Buffer) {
|
|
||||||
data = new Uint8Array(data.buffer, data.byteOffset, data.byteLength);
|
|
||||||
}
|
|
||||||
ret = await this._bodyWriter.write(data).then(() => {
|
|
||||||
callback?.();
|
|
||||||
this.emit("drain");
|
|
||||||
}).catch((e) => {
|
|
||||||
this._requestSendError = e;
|
|
||||||
});
|
|
||||||
console.log("flushing data:", data, ret);
|
|
||||||
}
|
|
||||||
|
|
||||||
this.outputData = [];
|
|
||||||
this.outputSize = 0;
|
|
||||||
|
|
||||||
return ret;
|
|
||||||
},
|
|
||||||
|
|
||||||
_writeRaw(
|
_writeRaw(
|
||||||
// deno-lint-ignore no-explicit-any
|
// deno-lint-ignore no-explicit-any
|
||||||
data: any,
|
data: any,
|
||||||
|
@ -590,11 +552,23 @@ Object.defineProperties(
|
||||||
data = new Uint8Array(data.buffer, data.byteOffset, data.byteLength);
|
data = new Uint8Array(data.buffer, data.byteOffset, data.byteLength);
|
||||||
}
|
}
|
||||||
if (data.buffer.byteLength > 0) {
|
if (data.buffer.byteLength > 0) {
|
||||||
this._bodyWriter.write(data).then(() => {
|
console.log(
|
||||||
callback?.();
|
"writing to",
|
||||||
this.emit("drain");
|
this._bodyWriteRid,
|
||||||
}).catch((e) => {
|
"desired size",
|
||||||
this._requestSendError = e;
|
this._bodyWriter.desiredSize,
|
||||||
|
"writer",
|
||||||
|
this._bodyWriter,
|
||||||
|
);
|
||||||
|
this._bodyWriter.ready.then(() => {
|
||||||
|
if (this._bodyWriter.desiredSize > 0) {
|
||||||
|
this._bodyWriter.write(data).then(() => {
|
||||||
|
callback?.();
|
||||||
|
this.emit("drain");
|
||||||
|
}).catch((e) => {
|
||||||
|
this._requestSendError = e;
|
||||||
|
});
|
||||||
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
|
|
|
@ -5,8 +5,10 @@
|
||||||
|
|
||||||
import { core, primordials } from "ext:core/mod.js";
|
import { core, primordials } from "ext:core/mod.js";
|
||||||
import {
|
import {
|
||||||
|
op_node_http_await_response,
|
||||||
op_node_http_fetch_response_upgrade,
|
op_node_http_fetch_response_upgrade,
|
||||||
op_node_http_request_with_conn,
|
op_node_http_request_with_conn,
|
||||||
|
op_node_http_wait_for_connection,
|
||||||
} from "ext:core/ops";
|
} from "ext:core/ops";
|
||||||
|
|
||||||
import { TextEncoder } from "ext:deno_web/08_text_encoding.js";
|
import { TextEncoder } from "ext:deno_web/08_text_encoding.js";
|
||||||
|
@ -414,14 +416,14 @@ class ClientRequest extends OutgoingMessage {
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
debug("CLIENT use net.createConnection", optsWithoutSignal);
|
debug("CLIENT use net.createConnection", optsWithoutSignal);
|
||||||
// console.log("use net.createConnection");
|
console.log("use net.createConnection");
|
||||||
this.onSocket(netCreateConnection(optsWithoutSignal));
|
this.onSocket(netCreateConnection(optsWithoutSignal));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
_writeHeader() {
|
_writeHeader() {
|
||||||
// console.trace("_writeHeader invoked");
|
console.trace("_writeHeader invoked");
|
||||||
const url = this._createUrlStrFromOptions();
|
const url = this._createUrlStrFromOptions();
|
||||||
|
|
||||||
const headers = [];
|
const headers = [];
|
||||||
|
@ -460,15 +462,20 @@ class ClientRequest extends OutgoingMessage {
|
||||||
|
|
||||||
(async () => {
|
(async () => {
|
||||||
try {
|
try {
|
||||||
// console.trace("js: sending request", this.socket.rid);
|
console.trace("js: sending request", this.socket.rid);
|
||||||
// console.log("this.socket", this.socket);
|
// console.log("this.socket", this.socket);
|
||||||
const res = await op_node_http_request_with_conn(
|
const [rid, connRid] = await op_node_http_request_with_conn(
|
||||||
this.method,
|
this.method,
|
||||||
url,
|
url,
|
||||||
headers,
|
headers,
|
||||||
this._bodyWriteRid,
|
this._bodyWriteRid,
|
||||||
this.socket.rid,
|
this.socket.rid,
|
||||||
);
|
);
|
||||||
|
console.log("js: request sent", { rid, connRid });
|
||||||
|
// Emit request ready to let the request body to be written.
|
||||||
|
await op_node_http_wait_for_connection(connRid);
|
||||||
|
this.emit("requestReady");
|
||||||
|
const res = await op_node_http_await_response(rid);
|
||||||
console.log({ res });
|
console.log({ res });
|
||||||
// if (this._req.cancelHandleRid !== null) {
|
// if (this._req.cancelHandleRid !== null) {
|
||||||
// core.tryClose(this._req.cancelHandleRid);
|
// core.tryClose(this._req.cancelHandleRid);
|
||||||
|
@ -605,7 +612,9 @@ class ClientRequest extends OutgoingMessage {
|
||||||
// Note: the order is important, as the headers flush
|
// Note: the order is important, as the headers flush
|
||||||
// sets up the request.
|
// sets up the request.
|
||||||
this._flushHeaders();
|
this._flushHeaders();
|
||||||
this._flushBody();
|
this.on("requestReady", () => {
|
||||||
|
this._flushBody();
|
||||||
|
});
|
||||||
});
|
});
|
||||||
this.socket = socket;
|
this.socket = socket;
|
||||||
this.emit("socket", socket);
|
this.emit("socket", socket);
|
||||||
|
|
|
@ -376,8 +376,10 @@ export class TCP extends ConnectionWrap {
|
||||||
transport: "tcp",
|
transport: "tcp",
|
||||||
};
|
};
|
||||||
|
|
||||||
|
console.log("deno.connect start");
|
||||||
Deno.connect(connectOptions).then(
|
Deno.connect(connectOptions).then(
|
||||||
(conn: Deno.Conn) => {
|
(conn: Deno.Conn) => {
|
||||||
|
console.log("deno.connect success");
|
||||||
// Incorrect / backwards, but correcting the local address and port with
|
// Incorrect / backwards, but correcting the local address and port with
|
||||||
// what was actually used given we can't actually specify these in Deno.
|
// what was actually used given we can't actually specify these in Deno.
|
||||||
const localAddr = conn.localAddr as Deno.NetAddr;
|
const localAddr = conn.localAddr as Deno.NetAddr;
|
||||||
|
|
Loading…
Reference in a new issue