diff --git a/ext/node/lib.rs b/ext/node/lib.rs index a17e5f0ab5..b1a893086d 100644 --- a/ext/node/lib.rs +++ b/ext/node/lib.rs @@ -326,6 +326,7 @@ deno_core::extension!(deno_node, ops::zlib::brotli::op_brotli_decompress_stream_end, ops::http::op_node_http_fetch_response_upgrade, ops::http::op_node_http_request_with_conn
, + ops::http::op_node_http_request_with_tls_conn
, ops::http::op_node_http_await_response, ops::http::op_node_http_wait_for_connection, ops::http2::op_http2_connect, diff --git a/ext/node/ops/http.rs b/ext/node/ops/http.rs index 6380ecfe03..0fd088d235 100644 --- a/ext/node/ops/http.rs +++ b/ext/node/ops/http.rs @@ -35,6 +35,7 @@ use deno_core::Resource; use deno_core::ResourceId; use deno_fetch::ResBody; use deno_net::io::TcpStreamResource; +use deno_net::ops_tls::TlsStreamResource; use http::header::HeaderMap; use http::header::HeaderName; use http::header::HeaderValue; @@ -199,6 +200,120 @@ where Ok((rid, conn_rid)) } +// TODO(@satyarohith): deduplicate the code. +#[op2(async)] +#[serde] +pub async fn op_node_http_request_with_tls_conn
(
+ state: Rc ();
+ permissions.check_net_url(&url_parsed, "ClientRequest")?;
+ }
+
+ let mut header_map = HeaderMap::new();
+ for (key, value) in headers {
+ let name = HeaderName::from_bytes(&key)
+ .map_err(|err| type_error(err.to_string()))?;
+ let v = HeaderValue::from_bytes(&value)
+ .map_err(|err| type_error(err.to_string()))?;
+
+ header_map.append(name, v);
+ }
+
+ let (body, con_len) = if let Some(body) = body {
+ (
+ BodyExt::boxed(NodeHttpResourceToBodyAdapter::new(
+ state.borrow_mut().resource_table.take_any(body)?,
+ )),
+ None,
+ )
+ } else {
+ // POST and PUT requests should always have a 0 length content-length,
+ // if there is no body. https://fetch.spec.whatwg.org/#http-network-or-cache-fetch
+ let len = if matches!(method, Method::POST | Method::PUT) {
+ Some(0)
+ } else {
+ None
+ };
+ (
+ http_body_util::Empty::new()
+ .map_err(|never| match never {})
+ .boxed(),
+ len,
+ )
+ };
+
+ let mut request = http::Request::new(body);
+ *request.method_mut() = method.clone();
+ *request.uri_mut() = url_parsed
+ .path()
+ .to_string()
+ .parse()
+ .map_err(|_| type_error("Invalid URL"))?;
+ *request.headers_mut() = header_map;
+
+ if let Some((username, password)) = maybe_authority {
+ request.headers_mut().insert(
+ AUTHORIZATION,
+ deno_fetch::basic_auth(&username, password.as_deref()),
+ );
+ }
+ if let Some(len) = con_len {
+ request.headers_mut().insert(CONTENT_LENGTH, len.into());
+ }
+
+ 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(),
+ });
+ 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(
diff --git a/ext/node/polyfills/http.ts b/ext/node/polyfills/http.ts
index d26192a47d..0e4459f637 100644
--- a/ext/node/polyfills/http.ts
+++ b/ext/node/polyfills/http.ts
@@ -8,7 +8,9 @@ import {
op_node_http_await_response,
op_node_http_fetch_response_upgrade,
op_node_http_request_with_conn,
+ op_node_http_request_with_tls_conn,
op_node_http_wait_for_connection,
+ op_tls_start,
} from "ext:core/ops";
import { TextEncoder } from "ext:deno_web/08_text_encoding.js";
@@ -447,12 +449,28 @@ class ClientRequest extends OutgoingMessage {
(async () => {
try {
- const [rid, connRid] = await op_node_http_request_with_conn(
+ const parsedUrl = new URL(url);
+ const encrypted = parsedUrl.protocol === "https:";
+ let encryptedRid;
+ if (encrypted) {
+ const { 0: rid, 1: localAddr, 2: remoteAddr } = op_tls_start({
+ rid: this.socket.rid,
+ hostname: parsedUrl.hostname,
+ caCerts: [],
+ alpnProtocols: ["http/1.0", "http/1.1"],
+ });
+ encryptedRid = rid;
+ }
+ const op = encrypted
+ ? op_node_http_request_with_tls_conn
+ : op_node_http_request_with_conn;
+ const connectionRid = encrypted ? encryptedRid : this.socket.rid;
+ const [rid, connRid] = await op(
this.method,
url,
headers,
this._bodyWriteRid,
- this.socket.rid,
+ connectionRid,
);
// Emit request ready to let the request body to be written.
await op_node_http_wait_for_connection(connRid);
diff --git a/tests/unit_node/http_test.ts b/tests/unit_node/http_test.ts
index fabd53748d..ee52d431c0 100644
--- a/tests/unit_node/http_test.ts
+++ b/tests/unit_node/http_test.ts
@@ -687,9 +687,7 @@ Deno.test("[node/http] ClientRequest handle non-string headers", async () => {
assertEquals(headers!["1"], "2");
});
-Deno.test("[node/http] ClientRequest uses HTTP/1.1", {
- ignore: true,
-}, async () => {
+Deno.test("[node/https] ClientRequest uses HTTP/1.1", async () => {
let body = "";
const { promise, resolve, reject } = Promise.withResolvers