mirror of
https://github.com/denoland/deno.git
synced 2024-11-28 16:20:57 -05:00
fix(ext/node) add node http methods (#22630)
fixes #22627 This PR fixes a node compat issue that is preventing `serverless-http` and `serverless-express` npm modules from working with Deno. These modules are useful for running apps on AWS Lambda (and other serverless infra). --------- Signed-off-by: Igor Zinkovsky <igor@deno.com>
This commit is contained in:
parent
627c49c9d8
commit
dc16c996dd
3 changed files with 107 additions and 1 deletions
|
@ -51,6 +51,7 @@ import { notImplemented, warnNotImplemented } from "ext:deno_node/_utils.ts";
|
||||||
import {
|
import {
|
||||||
connResetException,
|
connResetException,
|
||||||
ERR_HTTP_HEADERS_SENT,
|
ERR_HTTP_HEADERS_SENT,
|
||||||
|
ERR_HTTP_SOCKET_ASSIGNED,
|
||||||
ERR_INVALID_ARG_TYPE,
|
ERR_INVALID_ARG_TYPE,
|
||||||
ERR_INVALID_HTTP_TOKEN,
|
ERR_INVALID_HTTP_TOKEN,
|
||||||
ERR_INVALID_PROTOCOL,
|
ERR_INVALID_PROTOCOL,
|
||||||
|
@ -1340,6 +1341,8 @@ export class ServerResponse extends NodeWritable {
|
||||||
headersSent = false;
|
headersSent = false;
|
||||||
#firstChunk: Chunk | null = null;
|
#firstChunk: Chunk | null = null;
|
||||||
#resolve: (value: Response | PromiseLike<Response>) => void;
|
#resolve: (value: Response | PromiseLike<Response>) => void;
|
||||||
|
// deno-lint-ignore no-explicit-any
|
||||||
|
#socketOverride: any | null = null;
|
||||||
|
|
||||||
static #enqueue(controller: ReadableStreamDefaultController, chunk: Chunk) {
|
static #enqueue(controller: ReadableStreamDefaultController, chunk: Chunk) {
|
||||||
if (typeof chunk === "string") {
|
if (typeof chunk === "string") {
|
||||||
|
@ -1369,7 +1372,11 @@ export class ServerResponse extends NodeWritable {
|
||||||
autoDestroy: true,
|
autoDestroy: true,
|
||||||
defaultEncoding: "utf-8",
|
defaultEncoding: "utf-8",
|
||||||
emitClose: true,
|
emitClose: true,
|
||||||
write: (chunk, _encoding, cb) => {
|
write: (chunk, encoding, cb) => {
|
||||||
|
if (this.#socketOverride && this.#socketOverride.writable) {
|
||||||
|
this.#socketOverride.write(chunk, encoding);
|
||||||
|
return cb();
|
||||||
|
}
|
||||||
if (!this.headersSent) {
|
if (!this.headersSent) {
|
||||||
if (this.#firstChunk === null) {
|
if (this.#firstChunk === null) {
|
||||||
this.#firstChunk = chunk;
|
this.#firstChunk = chunk;
|
||||||
|
@ -1418,6 +1425,9 @@ export class ServerResponse extends NodeWritable {
|
||||||
getHeaderNames() {
|
getHeaderNames() {
|
||||||
return Array.from(this.#headers.keys());
|
return Array.from(this.#headers.keys());
|
||||||
}
|
}
|
||||||
|
getHeaders() {
|
||||||
|
return Object.fromEntries(this.#headers.entries());
|
||||||
|
}
|
||||||
hasHeader(name: string) {
|
hasHeader(name: string) {
|
||||||
return this.#headers.has(name);
|
return this.#headers.has(name);
|
||||||
}
|
}
|
||||||
|
@ -1483,6 +1493,20 @@ export class ServerResponse extends NodeWritable {
|
||||||
_implicitHeader() {
|
_implicitHeader() {
|
||||||
this.writeHead(this.statusCode);
|
this.writeHead(this.statusCode);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
assignSocket(socket) {
|
||||||
|
if (socket._httpMessage) {
|
||||||
|
throw new ERR_HTTP_SOCKET_ASSIGNED();
|
||||||
|
}
|
||||||
|
socket._httpMessage = this;
|
||||||
|
this.#socketOverride = socket;
|
||||||
|
}
|
||||||
|
|
||||||
|
detachSocket(socket) {
|
||||||
|
assert(socket._httpMessage === this);
|
||||||
|
socket._httpMessage = null;
|
||||||
|
this.#socketOverride = null;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO(@AaronO): optimize
|
// TODO(@AaronO): optimize
|
||||||
|
@ -1534,6 +1558,10 @@ export class IncomingMessageForServer extends NodeReadable {
|
||||||
return "1.1";
|
return "1.1";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
set httpVersion(val) {
|
||||||
|
assert(val === "1.1");
|
||||||
|
}
|
||||||
|
|
||||||
get headers() {
|
get headers() {
|
||||||
if (!this.#headers) {
|
if (!this.#headers) {
|
||||||
this.#headers = {};
|
this.#headers = {};
|
||||||
|
@ -1546,6 +1574,10 @@ export class IncomingMessageForServer extends NodeReadable {
|
||||||
return this.#headers;
|
return this.#headers;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
set headers(val) {
|
||||||
|
this.#headers = val;
|
||||||
|
}
|
||||||
|
|
||||||
get upgrade(): boolean {
|
get upgrade(): boolean {
|
||||||
return Boolean(
|
return Boolean(
|
||||||
this.#req.headers.get("connection")?.toLowerCase().includes("upgrade") &&
|
this.#req.headers.get("connection")?.toLowerCase().includes("upgrade") &&
|
||||||
|
|
|
@ -2543,6 +2543,15 @@ export class ERR_OS_NO_HOMEDIR extends NodeSystemError {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export class ERR_HTTP_SOCKET_ASSIGNED extends NodeError {
|
||||||
|
constructor() {
|
||||||
|
super(
|
||||||
|
"ERR_HTTP_SOCKET_ASSIGNED",
|
||||||
|
`ServerResponse has an already assigned socket`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
interface UvExceptionContext {
|
interface UvExceptionContext {
|
||||||
syscall: string;
|
syscall: string;
|
||||||
path?: string;
|
path?: string;
|
||||||
|
|
|
@ -3,6 +3,7 @@
|
||||||
import EventEmitter from "node:events";
|
import EventEmitter from "node:events";
|
||||||
import http, { type RequestOptions } from "node:http";
|
import http, { type RequestOptions } from "node:http";
|
||||||
import https from "node:https";
|
import https from "node:https";
|
||||||
|
import net from "node:net";
|
||||||
import { assert, assertEquals, fail } from "@std/assert/mod.ts";
|
import { assert, assertEquals, fail } from "@std/assert/mod.ts";
|
||||||
import { assertSpyCalls, spy } from "@std/testing/mock.ts";
|
import { assertSpyCalls, spy } from "@std/testing/mock.ts";
|
||||||
|
|
||||||
|
@ -938,3 +939,67 @@ Deno.test("[node/http] ServerResponse getHeader", async () => {
|
||||||
|
|
||||||
await promise;
|
await promise;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
Deno.test("[node/http] IncomingMessage override", () => {
|
||||||
|
const req = new http.IncomingMessage(new net.Socket());
|
||||||
|
// https://github.com/dougmoscrop/serverless-http/blob/3aaa6d0fe241109a8752efb011c242d249f32368/lib/request.js#L20-L30
|
||||||
|
Object.assign(req, {
|
||||||
|
ip: "1.1.1.1",
|
||||||
|
complete: true,
|
||||||
|
httpVersion: "1.1",
|
||||||
|
httpVersionMajor: "1",
|
||||||
|
httpVersionMinor: "1",
|
||||||
|
method: "GET",
|
||||||
|
headers: {},
|
||||||
|
body: "",
|
||||||
|
url: "https://1.1.1.1",
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
Deno.test("[node/http] ServerResponse assignSocket and detachSocket", () => {
|
||||||
|
const req = new http.IncomingMessage(new net.Socket());
|
||||||
|
const res = new http.ServerResponse(req);
|
||||||
|
let writtenData: string | Uint8Array | undefined = undefined;
|
||||||
|
let writtenEncoding: string | Uint8Array | undefined = undefined;
|
||||||
|
const socket = {
|
||||||
|
_writableState: {},
|
||||||
|
writable: true,
|
||||||
|
on: Function.prototype,
|
||||||
|
removeListener: Function.prototype,
|
||||||
|
destroy: Function.prototype,
|
||||||
|
cork: Function.prototype,
|
||||||
|
uncork: Function.prototype,
|
||||||
|
write: (
|
||||||
|
data: string | Uint8Array,
|
||||||
|
encoding: string,
|
||||||
|
_cb?: (err?: Error) => void,
|
||||||
|
) => {
|
||||||
|
writtenData = data;
|
||||||
|
writtenEncoding = encoding;
|
||||||
|
},
|
||||||
|
};
|
||||||
|
// @ts-ignore it's a socket mock
|
||||||
|
res.assignSocket(socket);
|
||||||
|
|
||||||
|
res.write("Hello World!", "utf8");
|
||||||
|
assertEquals(writtenData, Buffer.from("Hello World!"));
|
||||||
|
assertEquals(writtenEncoding, "buffer");
|
||||||
|
|
||||||
|
writtenData = undefined;
|
||||||
|
writtenEncoding = undefined;
|
||||||
|
|
||||||
|
// @ts-ignore it's a socket mock
|
||||||
|
res.detachSocket(socket);
|
||||||
|
res.write("Hello World!", "utf8");
|
||||||
|
assertEquals(writtenData, undefined);
|
||||||
|
assertEquals(writtenEncoding, undefined);
|
||||||
|
});
|
||||||
|
|
||||||
|
Deno.test("[node/http] ServerResponse getHeaders", () => {
|
||||||
|
const req = new http.IncomingMessage(new net.Socket());
|
||||||
|
const res = new http.ServerResponse(req);
|
||||||
|
res.setHeader("foo", "bar");
|
||||||
|
res.setHeader("bar", "baz");
|
||||||
|
assertEquals(res.getHeaderNames(), ["bar", "foo"]);
|
||||||
|
assertEquals(res.getHeaders(), { "bar": "baz", "foo": "bar" });
|
||||||
|
});
|
||||||
|
|
Loading…
Reference in a new issue