1
0
Fork 0
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:
Satya Rohith 2024-09-13 14:22:52 +05:30
parent 821d5a5653
commit 29fe1768a8
No known key found for this signature in database
GPG key ID: B2705CF40523EB05
5 changed files with 134 additions and 58 deletions

View file

@ -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,

View file

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

View file

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

View file

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

View file

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