mirror of
https://github.com/denoland/deno.git
synced 2024-12-22 15:24:46 -05:00
fix(ext/http): Make Deno.serveHttp()
work when proxying (#23269)
Closes https://github.com/denoland/deno/issues/21900
This commit is contained in:
parent
cb12a93503
commit
d3b63bb315
3 changed files with 143 additions and 49 deletions
|
@ -137,8 +137,10 @@ class HttpConn {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
const { 0: streamRid, 1: method, 2: url } = nextRequest;
|
const { 0: readStreamRid, 1: writeStreamRid, 2: method, 3: url } =
|
||||||
SetPrototypeAdd(this.#managedResources, streamRid);
|
nextRequest;
|
||||||
|
SetPrototypeAdd(this.#managedResources, readStreamRid);
|
||||||
|
SetPrototypeAdd(this.#managedResources, writeStreamRid);
|
||||||
|
|
||||||
/** @type {ReadableStream<Uint8Array> | undefined} */
|
/** @type {ReadableStream<Uint8Array> | undefined} */
|
||||||
let body = null;
|
let body = null;
|
||||||
|
@ -146,17 +148,16 @@ class HttpConn {
|
||||||
// It will be closed automatically once the request has been handled and
|
// It will be closed automatically once the request has been handled and
|
||||||
// the response has been sent.
|
// the response has been sent.
|
||||||
if (method !== "GET" && method !== "HEAD") {
|
if (method !== "GET" && method !== "HEAD") {
|
||||||
body = readableStreamForRid(streamRid, false);
|
body = readableStreamForRid(readStreamRid, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
const innerRequest = newInnerRequest(
|
const innerRequest = newInnerRequest(
|
||||||
method,
|
method,
|
||||||
url,
|
url,
|
||||||
() => op_http_headers(streamRid),
|
() => op_http_headers(readStreamRid),
|
||||||
body !== null ? new InnerBody(body) : null,
|
body !== null ? new InnerBody(body) : null,
|
||||||
false,
|
false,
|
||||||
);
|
);
|
||||||
innerRequest[streamRid] = streamRid;
|
|
||||||
const abortController = new AbortController();
|
const abortController = new AbortController();
|
||||||
const request = fromInnerRequest(
|
const request = fromInnerRequest(
|
||||||
innerRequest,
|
innerRequest,
|
||||||
|
@ -167,7 +168,8 @@ class HttpConn {
|
||||||
|
|
||||||
const respondWith = createRespondWith(
|
const respondWith = createRespondWith(
|
||||||
this,
|
this,
|
||||||
streamRid,
|
readStreamRid,
|
||||||
|
writeStreamRid,
|
||||||
abortController,
|
abortController,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -178,10 +180,10 @@ class HttpConn {
|
||||||
close() {
|
close() {
|
||||||
if (!this.#closed) {
|
if (!this.#closed) {
|
||||||
this.#closed = true;
|
this.#closed = true;
|
||||||
core.close(this.#rid);
|
core.tryClose(this.#rid);
|
||||||
for (const rid of new SafeSetIterator(this.#managedResources)) {
|
for (const rid of new SafeSetIterator(this.#managedResources)) {
|
||||||
SetPrototypeDelete(this.#managedResources, rid);
|
SetPrototypeDelete(this.#managedResources, rid);
|
||||||
core.close(rid);
|
core.tryClose(rid);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -209,7 +211,8 @@ class HttpConn {
|
||||||
|
|
||||||
function createRespondWith(
|
function createRespondWith(
|
||||||
httpConn,
|
httpConn,
|
||||||
streamRid,
|
readStreamRid,
|
||||||
|
writeStreamRid,
|
||||||
abortController,
|
abortController,
|
||||||
) {
|
) {
|
||||||
return async function respondWith(resp) {
|
return async function respondWith(resp) {
|
||||||
|
@ -270,7 +273,7 @@ function createRespondWith(
|
||||||
);
|
);
|
||||||
try {
|
try {
|
||||||
await op_http_write_headers(
|
await op_http_write_headers(
|
||||||
streamRid,
|
writeStreamRid,
|
||||||
innerResp.status ?? 200,
|
innerResp.status ?? 200,
|
||||||
innerResp.headerList,
|
innerResp.headerList,
|
||||||
isStreamingResponseBody ? null : respBody,
|
isStreamingResponseBody ? null : respBody,
|
||||||
|
@ -310,7 +313,7 @@ function createRespondWith(
|
||||||
reader = respBody.getReader(); // Acquire JS lock.
|
reader = respBody.getReader(); // Acquire JS lock.
|
||||||
try {
|
try {
|
||||||
await op_http_write_resource(
|
await op_http_write_resource(
|
||||||
streamRid,
|
writeStreamRid,
|
||||||
resourceBacking.rid,
|
resourceBacking.rid,
|
||||||
);
|
);
|
||||||
if (resourceBacking.autoClose) core.tryClose(resourceBacking.rid);
|
if (resourceBacking.autoClose) core.tryClose(resourceBacking.rid);
|
||||||
|
@ -340,7 +343,7 @@ function createRespondWith(
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
await op_http_write(streamRid, value);
|
await op_http_write(writeStreamRid, value);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
const connError = httpConn[connErrorSymbol];
|
const connError = httpConn[connErrorSymbol];
|
||||||
if (
|
if (
|
||||||
|
@ -359,7 +362,7 @@ function createRespondWith(
|
||||||
|
|
||||||
if (success) {
|
if (success) {
|
||||||
try {
|
try {
|
||||||
await op_http_shutdown(streamRid);
|
await op_http_shutdown(writeStreamRid);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
await reader.cancel(error);
|
await reader.cancel(error);
|
||||||
throw error;
|
throw error;
|
||||||
|
@ -370,7 +373,7 @@ function createRespondWith(
|
||||||
const ws = resp[_ws];
|
const ws = resp[_ws];
|
||||||
if (ws) {
|
if (ws) {
|
||||||
const wsRid = await op_http_upgrade_websocket(
|
const wsRid = await op_http_upgrade_websocket(
|
||||||
streamRid,
|
readStreamRid,
|
||||||
);
|
);
|
||||||
ws[_rid] = wsRid;
|
ws[_rid] = wsRid;
|
||||||
ws[_protocol] = resp.headers.get("sec-websocket-protocol");
|
ws[_protocol] = resp.headers.get("sec-websocket-protocol");
|
||||||
|
@ -395,8 +398,11 @@ function createRespondWith(
|
||||||
abortController.abort(error);
|
abortController.abort(error);
|
||||||
throw error;
|
throw error;
|
||||||
} finally {
|
} finally {
|
||||||
if (deleteManagedResource(httpConn, streamRid)) {
|
if (deleteManagedResource(httpConn, readStreamRid)) {
|
||||||
core.close(streamRid);
|
core.tryClose(readStreamRid);
|
||||||
|
}
|
||||||
|
if (deleteManagedResource(httpConn, writeStreamRid)) {
|
||||||
|
core.tryClose(writeStreamRid);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
|
@ -209,7 +209,15 @@ impl HttpConnResource {
|
||||||
// Accepts a new incoming HTTP request.
|
// Accepts a new incoming HTTP request.
|
||||||
async fn accept(
|
async fn accept(
|
||||||
self: &Rc<Self>,
|
self: &Rc<Self>,
|
||||||
) -> Result<Option<(HttpStreamResource, String, String)>, AnyError> {
|
) -> Result<
|
||||||
|
Option<(
|
||||||
|
HttpStreamReadResource,
|
||||||
|
HttpStreamWriteResource,
|
||||||
|
String,
|
||||||
|
String,
|
||||||
|
)>,
|
||||||
|
AnyError,
|
||||||
|
> {
|
||||||
let fut = async {
|
let fut = async {
|
||||||
let (request_tx, request_rx) = oneshot::channel();
|
let (request_tx, request_rx) = oneshot::channel();
|
||||||
let (response_tx, response_rx) = oneshot::channel();
|
let (response_tx, response_rx) = oneshot::channel();
|
||||||
|
@ -218,7 +226,6 @@ impl HttpConnResource {
|
||||||
self.acceptors_tx.unbounded_send(acceptor).ok()?;
|
self.acceptors_tx.unbounded_send(acceptor).ok()?;
|
||||||
|
|
||||||
let request = request_rx.await.ok()?;
|
let request = request_rx.await.ok()?;
|
||||||
|
|
||||||
let accept_encoding = {
|
let accept_encoding = {
|
||||||
let encodings =
|
let encodings =
|
||||||
fly_accept_encoding::encodings_iter_http_02(request.headers())
|
fly_accept_encoding::encodings_iter_http_02(request.headers())
|
||||||
|
@ -234,9 +241,10 @@ impl HttpConnResource {
|
||||||
|
|
||||||
let method = request.method().to_string();
|
let method = request.method().to_string();
|
||||||
let url = req_url(&request, self.scheme, &self.addr);
|
let url = req_url(&request, self.scheme, &self.addr);
|
||||||
let stream =
|
let read_stream = HttpStreamReadResource::new(self, request);
|
||||||
HttpStreamResource::new(self, request, response_tx, accept_encoding);
|
let write_stream =
|
||||||
Some((stream, method, url))
|
HttpStreamWriteResource::new(self, response_tx, accept_encoding);
|
||||||
|
Some((read_stream, write_stream, method, url))
|
||||||
};
|
};
|
||||||
|
|
||||||
async {
|
async {
|
||||||
|
@ -348,38 +356,34 @@ impl HttpAcceptor {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A resource representing a single HTTP request/response stream.
|
pub struct HttpStreamReadResource {
|
||||||
pub struct HttpStreamResource {
|
_conn: Rc<HttpConnResource>,
|
||||||
conn: Rc<HttpConnResource>,
|
|
||||||
pub rd: AsyncRefCell<HttpRequestReader>,
|
pub rd: AsyncRefCell<HttpRequestReader>,
|
||||||
wr: AsyncRefCell<HttpResponseWriter>,
|
|
||||||
accept_encoding: Encoding,
|
|
||||||
cancel_handle: CancelHandle,
|
cancel_handle: CancelHandle,
|
||||||
size: SizeHint,
|
size: SizeHint,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl HttpStreamResource {
|
pub struct HttpStreamWriteResource {
|
||||||
fn new(
|
conn: Rc<HttpConnResource>,
|
||||||
conn: &Rc<HttpConnResource>,
|
wr: AsyncRefCell<HttpResponseWriter>,
|
||||||
request: Request<Body>,
|
accept_encoding: Encoding,
|
||||||
response_tx: oneshot::Sender<Response<Body>>,
|
}
|
||||||
accept_encoding: Encoding,
|
|
||||||
) -> Self {
|
impl HttpStreamReadResource {
|
||||||
|
fn new(conn: &Rc<HttpConnResource>, request: Request<Body>) -> Self {
|
||||||
let size = request.body().size_hint();
|
let size = request.body().size_hint();
|
||||||
Self {
|
Self {
|
||||||
conn: conn.clone(),
|
_conn: conn.clone(),
|
||||||
rd: HttpRequestReader::Headers(request).into(),
|
rd: HttpRequestReader::Headers(request).into(),
|
||||||
wr: HttpResponseWriter::Headers(response_tx).into(),
|
|
||||||
accept_encoding,
|
|
||||||
size,
|
size,
|
||||||
cancel_handle: CancelHandle::new(),
|
cancel_handle: CancelHandle::new(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Resource for HttpStreamResource {
|
impl Resource for HttpStreamReadResource {
|
||||||
fn name(&self) -> Cow<str> {
|
fn name(&self) -> Cow<str> {
|
||||||
"httpStream".into()
|
"httpReadStream".into()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn read(self: Rc<Self>, limit: usize) -> AsyncResult<BufView> {
|
fn read(self: Rc<Self>, limit: usize) -> AsyncResult<BufView> {
|
||||||
|
@ -440,6 +444,26 @@ impl Resource for HttpStreamResource {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl HttpStreamWriteResource {
|
||||||
|
fn new(
|
||||||
|
conn: &Rc<HttpConnResource>,
|
||||||
|
response_tx: oneshot::Sender<Response<Body>>,
|
||||||
|
accept_encoding: Encoding,
|
||||||
|
) -> Self {
|
||||||
|
Self {
|
||||||
|
conn: conn.clone(),
|
||||||
|
wr: HttpResponseWriter::Headers(response_tx).into(),
|
||||||
|
accept_encoding,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Resource for HttpStreamWriteResource {
|
||||||
|
fn name(&self) -> Cow<str> {
|
||||||
|
"httpWriteStream".into()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// The read half of an HTTP stream.
|
/// The read half of an HTTP stream.
|
||||||
pub enum HttpRequestReader {
|
pub enum HttpRequestReader {
|
||||||
Headers(Request<Body>),
|
Headers(Request<Body>),
|
||||||
|
@ -504,7 +528,9 @@ impl Drop for BodyUncompressedSender {
|
||||||
#[derive(Serialize)]
|
#[derive(Serialize)]
|
||||||
#[serde(rename_all = "camelCase")]
|
#[serde(rename_all = "camelCase")]
|
||||||
struct NextRequestResponse(
|
struct NextRequestResponse(
|
||||||
// stream_rid:
|
// read_stream_rid:
|
||||||
|
ResourceId,
|
||||||
|
// write_stream_rid:
|
||||||
ResourceId,
|
ResourceId,
|
||||||
// method:
|
// method:
|
||||||
// This is a String rather than a ByteString because reqwest will only return
|
// This is a String rather than a ByteString because reqwest will only return
|
||||||
|
@ -523,10 +549,17 @@ async fn op_http_accept(
|
||||||
let conn = state.borrow().resource_table.get::<HttpConnResource>(rid)?;
|
let conn = state.borrow().resource_table.get::<HttpConnResource>(rid)?;
|
||||||
|
|
||||||
match conn.accept().await {
|
match conn.accept().await {
|
||||||
Ok(Some((stream, method, url))) => {
|
Ok(Some((read_stream, write_stream, method, url))) => {
|
||||||
let stream_rid =
|
let read_stream_rid = state
|
||||||
state.borrow_mut().resource_table.add_rc(Rc::new(stream));
|
.borrow_mut()
|
||||||
let r = NextRequestResponse(stream_rid, method, url);
|
.resource_table
|
||||||
|
.add_rc(Rc::new(read_stream));
|
||||||
|
let write_stream_rid = state
|
||||||
|
.borrow_mut()
|
||||||
|
.resource_table
|
||||||
|
.add_rc(Rc::new(write_stream));
|
||||||
|
let r =
|
||||||
|
NextRequestResponse(read_stream_rid, write_stream_rid, method, url);
|
||||||
Ok(Some(r))
|
Ok(Some(r))
|
||||||
}
|
}
|
||||||
Ok(None) => Ok(None),
|
Ok(None) => Ok(None),
|
||||||
|
@ -628,7 +661,7 @@ async fn op_http_write_headers(
|
||||||
let stream = state
|
let stream = state
|
||||||
.borrow_mut()
|
.borrow_mut()
|
||||||
.resource_table
|
.resource_table
|
||||||
.get::<HttpStreamResource>(rid)?;
|
.get::<HttpStreamWriteResource>(rid)?;
|
||||||
|
|
||||||
// Track supported encoding
|
// Track supported encoding
|
||||||
let encoding = stream.accept_encoding;
|
let encoding = stream.accept_encoding;
|
||||||
|
@ -693,7 +726,7 @@ fn op_http_headers(
|
||||||
state: &mut OpState,
|
state: &mut OpState,
|
||||||
#[smi] rid: u32,
|
#[smi] rid: u32,
|
||||||
) -> Result<Vec<(ByteString, ByteString)>, AnyError> {
|
) -> Result<Vec<(ByteString, ByteString)>, AnyError> {
|
||||||
let stream = state.resource_table.get::<HttpStreamResource>(rid)?;
|
let stream = state.resource_table.get::<HttpStreamReadResource>(rid)?;
|
||||||
let rd = RcRef::map(&stream, |r| &r.rd)
|
let rd = RcRef::map(&stream, |r| &r.rd)
|
||||||
.try_borrow()
|
.try_borrow()
|
||||||
.ok_or_else(|| http_error("already in use"))?;
|
.ok_or_else(|| http_error("already in use"))?;
|
||||||
|
@ -849,7 +882,7 @@ async fn op_http_write_resource(
|
||||||
let http_stream = state
|
let http_stream = state
|
||||||
.borrow()
|
.borrow()
|
||||||
.resource_table
|
.resource_table
|
||||||
.get::<HttpStreamResource>(rid)?;
|
.get::<HttpStreamWriteResource>(rid)?;
|
||||||
let mut wr = RcRef::map(&http_stream, |r| &r.wr).borrow_mut().await;
|
let mut wr = RcRef::map(&http_stream, |r| &r.wr).borrow_mut().await;
|
||||||
let resource = state.borrow().resource_table.get_any(stream)?;
|
let resource = state.borrow().resource_table.get_any(stream)?;
|
||||||
loop {
|
loop {
|
||||||
|
@ -908,7 +941,7 @@ async fn op_http_write(
|
||||||
let stream = state
|
let stream = state
|
||||||
.borrow()
|
.borrow()
|
||||||
.resource_table
|
.resource_table
|
||||||
.get::<HttpStreamResource>(rid)?;
|
.get::<HttpStreamWriteResource>(rid)?;
|
||||||
let mut wr = RcRef::map(&stream, |r| &r.wr).borrow_mut().await;
|
let mut wr = RcRef::map(&stream, |r| &r.wr).borrow_mut().await;
|
||||||
|
|
||||||
match &mut *wr {
|
match &mut *wr {
|
||||||
|
@ -960,7 +993,7 @@ async fn op_http_shutdown(
|
||||||
let stream = state
|
let stream = state
|
||||||
.borrow()
|
.borrow()
|
||||||
.resource_table
|
.resource_table
|
||||||
.get::<HttpStreamResource>(rid)?;
|
.get::<HttpStreamWriteResource>(rid)?;
|
||||||
let mut wr = RcRef::map(&stream, |r| &r.wr).borrow_mut().await;
|
let mut wr = RcRef::map(&stream, |r| &r.wr).borrow_mut().await;
|
||||||
let wr = take(&mut *wr);
|
let wr = take(&mut *wr);
|
||||||
match wr {
|
match wr {
|
||||||
|
@ -1008,7 +1041,7 @@ async fn op_http_upgrade_websocket(
|
||||||
let stream = state
|
let stream = state
|
||||||
.borrow_mut()
|
.borrow_mut()
|
||||||
.resource_table
|
.resource_table
|
||||||
.get::<HttpStreamResource>(rid)?;
|
.get::<HttpStreamReadResource>(rid)?;
|
||||||
let mut rd = RcRef::map(&stream, |r| &r.rd).borrow_mut().await;
|
let mut rd = RcRef::map(&stream, |r| &r.rd).borrow_mut().await;
|
||||||
|
|
||||||
let request = match &mut *rd {
|
let request = match &mut *rd {
|
||||||
|
|
|
@ -2668,6 +2668,61 @@ Deno.test(
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
|
Deno.test("proxy with fetch", async () => {
|
||||||
|
const listener = Deno.listen({ port: listenPort });
|
||||||
|
const deferred = Promise.withResolvers<void>();
|
||||||
|
|
||||||
|
const server = Deno.serve({ port: listenPort + 1 }, (_req) => {
|
||||||
|
return new Response("Hello world");
|
||||||
|
});
|
||||||
|
|
||||||
|
let httpConn: Deno.HttpConn;
|
||||||
|
async function handleHttp(conn: Deno.Conn) {
|
||||||
|
httpConn = Deno.serveHttp(conn);
|
||||||
|
for await (const e of httpConn) {
|
||||||
|
await e.respondWith(serve(e.request));
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function serve(req: Request) {
|
||||||
|
return await fetch(`http://localhost:${listenPort + 1}/`, req);
|
||||||
|
}
|
||||||
|
|
||||||
|
const originServer = (async () => {
|
||||||
|
for await (const conn of listener) {
|
||||||
|
handleHttp(conn);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
})();
|
||||||
|
|
||||||
|
const proxiedRequest = (async () => {
|
||||||
|
const conn = await Deno.connect({ port: listenPort });
|
||||||
|
const payload = new TextEncoder().encode(
|
||||||
|
"POST /api/sessions HTTP/1.1\x0d\x0aConnection: keep-alive\x0d\x0aContent-Length: 2\x0d\x0a\x0d\x0a{}",
|
||||||
|
);
|
||||||
|
const n = await conn.write(payload);
|
||||||
|
assertEquals(n, 76);
|
||||||
|
const buf = new Uint8Array(1000);
|
||||||
|
const nread = await conn.read(buf);
|
||||||
|
assertEquals(nread, 150);
|
||||||
|
const respText = new TextDecoder().decode(buf);
|
||||||
|
assert(respText.includes("HTTP/1.1 200 OK"));
|
||||||
|
assert(respText.includes("content-type: text/plain;charset=UTF-8"));
|
||||||
|
assert(respText.includes("vary: Accept-Encoding"));
|
||||||
|
assert(respText.includes("content-length: 11"));
|
||||||
|
assert(respText.includes("Hello world"));
|
||||||
|
conn.close();
|
||||||
|
deferred.resolve();
|
||||||
|
})();
|
||||||
|
await proxiedRequest;
|
||||||
|
await originServer;
|
||||||
|
await deferred.promise;
|
||||||
|
await server.shutdown();
|
||||||
|
await server.finished;
|
||||||
|
httpConn!.close();
|
||||||
|
});
|
||||||
|
|
||||||
function chunkedBodyReader(h: Headers, r: BufReader): Deno.Reader {
|
function chunkedBodyReader(h: Headers, r: BufReader): Deno.Reader {
|
||||||
// Based on https://tools.ietf.org/html/rfc2616#section-19.4.6
|
// Based on https://tools.ietf.org/html/rfc2616#section-19.4.6
|
||||||
const tp = new TextProtoReader(r);
|
const tp = new TextProtoReader(r);
|
||||||
|
|
Loading…
Reference in a new issue