mirror of
https://github.com/denoland/deno.git
synced 2024-11-27 16:10:57 -05:00
fix(ext/node): refactor http.ServerResponse into function class (#26210)
While testing, I found out that light-my-request relies on `ServerResponse.connection`, which is deprecated, so I added that and `socket`, the non deprecated property. It also relies on an undocumented `_header` property, apparently for [raw header processing](https://github.com/fastify/light-my-request/blob/v6.1.0/lib/response.js#L180-L186). I added it as an empty string, feel free to provide other approaches. Fixes #19901 Co-authored-by: Bartek Iwańczuk <biwanczuk@gmail.com>
This commit is contained in:
parent
fd8bf08271
commit
8dd6177c62
2 changed files with 407 additions and 207 deletions
|
@ -34,6 +34,7 @@ import {
|
||||||
finished,
|
finished,
|
||||||
Readable as NodeReadable,
|
Readable as NodeReadable,
|
||||||
Writable as NodeWritable,
|
Writable as NodeWritable,
|
||||||
|
WritableOptions as NodeWritableOptions,
|
||||||
} from "node:stream";
|
} from "node:stream";
|
||||||
import {
|
import {
|
||||||
kUniqueHeaders,
|
kUniqueHeaders,
|
||||||
|
@ -70,6 +71,7 @@ import { resourceForReadableStream } from "ext:deno_web/06_streams.js";
|
||||||
import { UpgradedConn } from "ext:deno_net/01_net.js";
|
import { UpgradedConn } from "ext:deno_net/01_net.js";
|
||||||
import { STATUS_CODES } from "node:_http_server";
|
import { STATUS_CODES } from "node:_http_server";
|
||||||
import { methods as METHODS } from "node:_http_common";
|
import { methods as METHODS } from "node:_http_common";
|
||||||
|
import { deprecate } from "node:util";
|
||||||
|
|
||||||
const { internalRidSymbol } = core;
|
const { internalRidSymbol } = core;
|
||||||
const { ArrayIsArray, StringPrototypeToLowerCase } = primordials;
|
const { ArrayIsArray, StringPrototypeToLowerCase } = primordials;
|
||||||
|
@ -1184,49 +1186,95 @@ function onError(self, error, cb) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export class ServerResponse extends NodeWritable {
|
export type ServerResponse = {
|
||||||
statusCode = 200;
|
statusCode: number;
|
||||||
statusMessage?: string = undefined;
|
statusMessage?: string;
|
||||||
#headers: Record<string, string | string[]> = { __proto__: null };
|
|
||||||
#hasNonStringHeaders: boolean = false;
|
_headers: Record<string, string | string[]>;
|
||||||
#readable: ReadableStream;
|
_hasNonStringHeaders: boolean;
|
||||||
override writable = true;
|
|
||||||
// used by `npm:on-finished`
|
_readable: ReadableStream;
|
||||||
finished = false;
|
finished: boolean;
|
||||||
headersSent = false;
|
headersSent: boolean;
|
||||||
#resolve: (value: Response | PromiseLike<Response>) => void;
|
_resolve: (value: Response | PromiseLike<Response>) => void;
|
||||||
// deno-lint-ignore no-explicit-any
|
// deno-lint-ignore no-explicit-any
|
||||||
#socketOverride: any | null = null;
|
_socketOverride: any | null;
|
||||||
|
// deno-lint-ignore no-explicit-any
|
||||||
|
socket: any | null;
|
||||||
|
|
||||||
static #enqueue(controller: ReadableStreamDefaultController, chunk: Chunk) {
|
setHeader(name: string, value: string | string[]): void;
|
||||||
try {
|
appendHeader(name: string, value: string | string[]): void;
|
||||||
if (typeof chunk === "string") {
|
getHeader(name: string): string | string[];
|
||||||
controller.enqueue(ENCODER.encode(chunk));
|
removeHeader(name: string): void;
|
||||||
} else {
|
getHeaderNames(): string[];
|
||||||
controller.enqueue(chunk);
|
getHeaders(): Record<string, string | number | string[]>;
|
||||||
}
|
hasHeader(name: string): boolean;
|
||||||
} catch (_) {
|
|
||||||
// The stream might have been closed. Ignore the error.
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Returns true if the response body should be null with the given
|
writeHead(
|
||||||
* http status code */
|
status: number,
|
||||||
static #bodyShouldBeNull(status: number) {
|
statusMessage?: string,
|
||||||
return status === 101 || status === 204 || status === 205 || status === 304;
|
headers?:
|
||||||
}
|
| Record<string, string | number | string[]>
|
||||||
|
| Array<[string, string]>,
|
||||||
|
): void;
|
||||||
|
writeHead(
|
||||||
|
status: number,
|
||||||
|
headers?:
|
||||||
|
| Record<string, string | number | string[]>
|
||||||
|
| Array<[string, string]>,
|
||||||
|
): void;
|
||||||
|
|
||||||
constructor(
|
_ensureHeaders(singleChunk?: Chunk): void;
|
||||||
|
|
||||||
|
respond(final: boolean, singleChunk?: Chunk): void;
|
||||||
|
// deno-lint-ignore no-explicit-any
|
||||||
|
end(chunk?: any, encoding?: any, cb?: any): void;
|
||||||
|
|
||||||
|
flushHeaders(): void;
|
||||||
|
_implicitHeader(): void;
|
||||||
|
|
||||||
|
// Undocumented field used by `npm:light-my-request`.
|
||||||
|
_header: string;
|
||||||
|
|
||||||
|
assignSocket(socket): void;
|
||||||
|
detachSocket(socket): void;
|
||||||
|
} & { -readonly [K in keyof NodeWritable]: NodeWritable[K] };
|
||||||
|
|
||||||
|
type ServerResponseStatic = {
|
||||||
|
new (
|
||||||
resolve: (value: Response | PromiseLike<Response>) => void,
|
resolve: (value: Response | PromiseLike<Response>) => void,
|
||||||
socket: FakeSocket,
|
socket: FakeSocket,
|
||||||
) {
|
): ServerResponse;
|
||||||
let controller: ReadableByteStreamController;
|
_enqueue(controller: ReadableStreamDefaultController, chunk: Chunk): void;
|
||||||
const readable = new ReadableStream({
|
_bodyShouldBeNull(statusCode: number): boolean;
|
||||||
start(c) {
|
};
|
||||||
controller = c as ReadableByteStreamController;
|
|
||||||
},
|
export const ServerResponse = function (
|
||||||
});
|
this: ServerResponse,
|
||||||
super({
|
resolve: (value: Response | PromiseLike<Response>) => void,
|
||||||
|
socket: FakeSocket,
|
||||||
|
) {
|
||||||
|
this.statusCode = 200;
|
||||||
|
this.statusMessage = undefined;
|
||||||
|
this._headers = { __proto__: null };
|
||||||
|
this._hasNonStringHeaders = false;
|
||||||
|
this.writable = true;
|
||||||
|
|
||||||
|
// used by `npm:on-finished`
|
||||||
|
this.finished = false;
|
||||||
|
this.headersSent = false;
|
||||||
|
this._socketOverride = null;
|
||||||
|
|
||||||
|
let controller: ReadableByteStreamController;
|
||||||
|
const readable = new ReadableStream({
|
||||||
|
start(c) {
|
||||||
|
controller = c as ReadableByteStreamController;
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
NodeWritable.call(
|
||||||
|
this,
|
||||||
|
{
|
||||||
autoDestroy: true,
|
autoDestroy: true,
|
||||||
defaultEncoding: "utf-8",
|
defaultEncoding: "utf-8",
|
||||||
emitClose: true,
|
emitClose: true,
|
||||||
|
@ -1235,16 +1283,16 @@ export class ServerResponse extends NodeWritable {
|
||||||
write: (chunk, encoding, cb) => {
|
write: (chunk, encoding, cb) => {
|
||||||
// Writes chunks are directly written to the socket if
|
// Writes chunks are directly written to the socket if
|
||||||
// one is assigned via assignSocket()
|
// one is assigned via assignSocket()
|
||||||
if (this.#socketOverride && this.#socketOverride.writable) {
|
if (this._socketOverride && this._socketOverride.writable) {
|
||||||
this.#socketOverride.write(chunk, encoding);
|
this._socketOverride.write(chunk, encoding);
|
||||||
return cb();
|
return cb();
|
||||||
}
|
}
|
||||||
if (!this.headersSent) {
|
if (!this.headersSent) {
|
||||||
ServerResponse.#enqueue(controller, chunk);
|
ServerResponse._enqueue(controller, chunk);
|
||||||
this.respond(false);
|
this.respond(false);
|
||||||
return cb();
|
return cb();
|
||||||
}
|
}
|
||||||
ServerResponse.#enqueue(controller, chunk);
|
ServerResponse._enqueue(controller, chunk);
|
||||||
return cb();
|
return cb();
|
||||||
},
|
},
|
||||||
final: (cb) => {
|
final: (cb) => {
|
||||||
|
@ -1260,193 +1308,269 @@ export class ServerResponse extends NodeWritable {
|
||||||
}
|
}
|
||||||
return cb(null);
|
return cb(null);
|
||||||
},
|
},
|
||||||
});
|
} satisfies NodeWritableOptions,
|
||||||
this.#readable = readable;
|
);
|
||||||
this.#resolve = resolve;
|
|
||||||
this.socket = socket;
|
|
||||||
}
|
|
||||||
|
|
||||||
setHeader(name: string, value: string | string[]) {
|
this._readable = readable;
|
||||||
if (Array.isArray(value)) {
|
this._resolve = resolve;
|
||||||
this.#hasNonStringHeaders = true;
|
this.socket = socket;
|
||||||
}
|
|
||||||
this.#headers[StringPrototypeToLowerCase(name)] = value;
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
appendHeader(name: string, value: string | string[]) {
|
this._header = "";
|
||||||
const key = StringPrototypeToLowerCase(name);
|
} as unknown as ServerResponseStatic;
|
||||||
if (this.#headers[key] === undefined) {
|
|
||||||
if (Array.isArray(value)) this.#hasNonStringHeaders = true;
|
Object.setPrototypeOf(ServerResponse.prototype, NodeWritable.prototype);
|
||||||
this.#headers[key] = value;
|
Object.setPrototypeOf(ServerResponse, NodeWritable);
|
||||||
|
|
||||||
|
ServerResponse._enqueue = function (
|
||||||
|
this: ServerResponse,
|
||||||
|
controller: ReadableStreamDefaultController,
|
||||||
|
chunk: Chunk,
|
||||||
|
) {
|
||||||
|
try {
|
||||||
|
if (typeof chunk === "string") {
|
||||||
|
controller.enqueue(ENCODER.encode(chunk));
|
||||||
} else {
|
} else {
|
||||||
this.#hasNonStringHeaders = true;
|
controller.enqueue(chunk);
|
||||||
if (!Array.isArray(this.#headers[key])) {
|
}
|
||||||
this.#headers[key] = [this.#headers[key]];
|
} catch (_) {
|
||||||
|
// The stream might have been closed. Ignore the error.
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/** Returns true if the response body should be null with the given
|
||||||
|
* http status code */
|
||||||
|
ServerResponse._bodyShouldBeNull = function (
|
||||||
|
this: ServerResponse,
|
||||||
|
status: number,
|
||||||
|
) {
|
||||||
|
return status === 101 || status === 204 || status === 205 || status === 304;
|
||||||
|
};
|
||||||
|
|
||||||
|
ServerResponse.prototype.setHeader = function (
|
||||||
|
this: ServerResponse,
|
||||||
|
name: string,
|
||||||
|
value: string | string[],
|
||||||
|
) {
|
||||||
|
if (Array.isArray(value)) {
|
||||||
|
this._hasNonStringHeaders = true;
|
||||||
|
}
|
||||||
|
this._headers[StringPrototypeToLowerCase(name)] = value;
|
||||||
|
return this;
|
||||||
|
};
|
||||||
|
|
||||||
|
ServerResponse.prototype.appendHeader = function (
|
||||||
|
this: ServerResponse,
|
||||||
|
name: string,
|
||||||
|
value: string | string[],
|
||||||
|
) {
|
||||||
|
const key = StringPrototypeToLowerCase(name);
|
||||||
|
if (this._headers[key] === undefined) {
|
||||||
|
if (Array.isArray(value)) this._hasNonStringHeaders = true;
|
||||||
|
this._headers[key] = value;
|
||||||
|
} else {
|
||||||
|
this._hasNonStringHeaders = true;
|
||||||
|
if (!Array.isArray(this._headers[key])) {
|
||||||
|
this._headers[key] = [this._headers[key]];
|
||||||
|
}
|
||||||
|
const header = this._headers[key];
|
||||||
|
if (Array.isArray(value)) {
|
||||||
|
header.push(...value);
|
||||||
|
} else {
|
||||||
|
header.push(value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return this;
|
||||||
|
};
|
||||||
|
|
||||||
|
ServerResponse.prototype.getHeader = function (
|
||||||
|
this: ServerResponse,
|
||||||
|
name: string,
|
||||||
|
) {
|
||||||
|
return this._headers[StringPrototypeToLowerCase(name)];
|
||||||
|
};
|
||||||
|
|
||||||
|
ServerResponse.prototype.removeHeader = function (
|
||||||
|
this: ServerResponse,
|
||||||
|
name: string,
|
||||||
|
) {
|
||||||
|
delete this._headers[StringPrototypeToLowerCase(name)];
|
||||||
|
};
|
||||||
|
|
||||||
|
ServerResponse.prototype.getHeaderNames = function (this: ServerResponse) {
|
||||||
|
return Object.keys(this._headers);
|
||||||
|
};
|
||||||
|
|
||||||
|
ServerResponse.prototype.getHeaders = function (
|
||||||
|
this: ServerResponse,
|
||||||
|
): Record<string, string | number | string[]> {
|
||||||
|
return { __proto__: null, ...this._headers };
|
||||||
|
};
|
||||||
|
|
||||||
|
ServerResponse.prototype.hasHeader = function (
|
||||||
|
this: ServerResponse,
|
||||||
|
name: string,
|
||||||
|
) {
|
||||||
|
return Object.hasOwn(this._headers, name);
|
||||||
|
};
|
||||||
|
|
||||||
|
ServerResponse.prototype.writeHead = function (
|
||||||
|
this: ServerResponse,
|
||||||
|
status: number,
|
||||||
|
statusMessageOrHeaders?:
|
||||||
|
| string
|
||||||
|
| Record<string, string | number | string[]>
|
||||||
|
| Array<[string, string]>,
|
||||||
|
maybeHeaders?:
|
||||||
|
| Record<string, string | number | string[]>
|
||||||
|
| Array<[string, string]>,
|
||||||
|
) {
|
||||||
|
this.statusCode = status;
|
||||||
|
|
||||||
|
let headers = null;
|
||||||
|
if (typeof statusMessageOrHeaders === "string") {
|
||||||
|
this.statusMessage = statusMessageOrHeaders;
|
||||||
|
if (maybeHeaders !== undefined) {
|
||||||
|
headers = maybeHeaders;
|
||||||
|
}
|
||||||
|
} else if (statusMessageOrHeaders !== undefined) {
|
||||||
|
headers = statusMessageOrHeaders;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (headers !== null) {
|
||||||
|
if (ArrayIsArray(headers)) {
|
||||||
|
headers = headers as Array<[string, string]>;
|
||||||
|
for (let i = 0; i < headers.length; i++) {
|
||||||
|
this.appendHeader(headers[i][0], headers[i][1]);
|
||||||
}
|
}
|
||||||
const header = this.#headers[key];
|
} else {
|
||||||
if (Array.isArray(value)) {
|
headers = headers as Record<string, string>;
|
||||||
header.push(...value);
|
for (const k in headers) {
|
||||||
} else {
|
if (Object.hasOwn(headers, k)) {
|
||||||
header.push(value);
|
this.setHeader(k, headers[k]);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return this;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
getHeader(name: string) {
|
return this;
|
||||||
return this.#headers[StringPrototypeToLowerCase(name)];
|
};
|
||||||
}
|
|
||||||
removeHeader(name: string) {
|
|
||||||
delete this.#headers[StringPrototypeToLowerCase(name)];
|
|
||||||
}
|
|
||||||
getHeaderNames() {
|
|
||||||
return Object.keys(this.#headers);
|
|
||||||
}
|
|
||||||
getHeaders(): Record<string, string | number | string[]> {
|
|
||||||
// @ts-ignore Ignore null __proto__
|
|
||||||
return { __proto__: null, ...this.#headers };
|
|
||||||
}
|
|
||||||
hasHeader(name: string) {
|
|
||||||
return Object.hasOwn(this.#headers, name);
|
|
||||||
}
|
|
||||||
|
|
||||||
writeHead(
|
ServerResponse.prototype._ensureHeaders = function (
|
||||||
status: number,
|
this: ServerResponse,
|
||||||
statusMessage?: string,
|
singleChunk?: Chunk,
|
||||||
headers?:
|
) {
|
||||||
| Record<string, string | number | string[]>
|
if (this.statusCode === 200 && this.statusMessage === undefined) {
|
||||||
| Array<[string, string]>,
|
this.statusMessage = "OK";
|
||||||
): this;
|
}
|
||||||
writeHead(
|
if (typeof singleChunk === "string" && !this.hasHeader("content-type")) {
|
||||||
status: number,
|
this.setHeader("content-type", "text/plain;charset=UTF-8");
|
||||||
headers?:
|
}
|
||||||
| Record<string, string | number | string[]>
|
};
|
||||||
| Array<[string, string]>,
|
|
||||||
): this;
|
|
||||||
writeHead(
|
|
||||||
status: number,
|
|
||||||
statusMessageOrHeaders?:
|
|
||||||
| string
|
|
||||||
| Record<string, string | number | string[]>
|
|
||||||
| Array<[string, string]>,
|
|
||||||
maybeHeaders?:
|
|
||||||
| Record<string, string | number | string[]>
|
|
||||||
| Array<[string, string]>,
|
|
||||||
): this {
|
|
||||||
this.statusCode = status;
|
|
||||||
|
|
||||||
let headers = null;
|
ServerResponse.prototype.respond = function (
|
||||||
if (typeof statusMessageOrHeaders === "string") {
|
this: ServerResponse,
|
||||||
this.statusMessage = statusMessageOrHeaders;
|
final: boolean,
|
||||||
if (maybeHeaders !== undefined) {
|
singleChunk?: Chunk,
|
||||||
headers = maybeHeaders;
|
) {
|
||||||
}
|
this.headersSent = true;
|
||||||
} else if (statusMessageOrHeaders !== undefined) {
|
this._ensureHeaders(singleChunk);
|
||||||
headers = statusMessageOrHeaders;
|
let body = singleChunk ?? (final ? null : this._readable);
|
||||||
}
|
if (ServerResponse._bodyShouldBeNull(this.statusCode)) {
|
||||||
|
body = null;
|
||||||
if (headers !== null) {
|
}
|
||||||
if (ArrayIsArray(headers)) {
|
let headers: Record<string, string> | [string, string][] = this
|
||||||
headers = headers as Array<[string, string]>;
|
._headers as Record<string, string>;
|
||||||
for (let i = 0; i < headers.length; i++) {
|
if (this._hasNonStringHeaders) {
|
||||||
this.appendHeader(headers[i][0], headers[i][1]);
|
headers = [];
|
||||||
|
// Guard is not needed as this is a null prototype object.
|
||||||
|
// deno-lint-ignore guard-for-in
|
||||||
|
for (const key in this._headers) {
|
||||||
|
const entry = this._headers[key];
|
||||||
|
if (Array.isArray(entry)) {
|
||||||
|
for (const value of entry) {
|
||||||
|
headers.push([key, value]);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
headers = headers as Record<string, string>;
|
headers.push([key, entry]);
|
||||||
for (const k in headers) {
|
|
||||||
if (Object.hasOwn(headers, k)) {
|
|
||||||
this.setHeader(k, headers[k]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
#ensureHeaders(singleChunk?: Chunk) {
|
|
||||||
if (this.statusCode === 200 && this.statusMessage === undefined) {
|
|
||||||
this.statusMessage = "OK";
|
|
||||||
}
|
|
||||||
if (
|
|
||||||
typeof singleChunk === "string" &&
|
|
||||||
!this.hasHeader("content-type")
|
|
||||||
) {
|
|
||||||
this.setHeader("content-type", "text/plain;charset=UTF-8");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
respond(final: boolean, singleChunk?: Chunk) {
|
|
||||||
this.headersSent = true;
|
|
||||||
this.#ensureHeaders(singleChunk);
|
|
||||||
let body = singleChunk ?? (final ? null : this.#readable);
|
|
||||||
if (ServerResponse.#bodyShouldBeNull(this.statusCode)) {
|
|
||||||
body = null;
|
|
||||||
}
|
|
||||||
let headers: Record<string, string> | [string, string][] = this
|
|
||||||
.#headers as Record<string, string>;
|
|
||||||
if (this.#hasNonStringHeaders) {
|
|
||||||
headers = [];
|
|
||||||
// Guard is not needed as this is a null prototype object.
|
|
||||||
// deno-lint-ignore guard-for-in
|
|
||||||
for (const key in this.#headers) {
|
|
||||||
const entry = this.#headers[key];
|
|
||||||
if (Array.isArray(entry)) {
|
|
||||||
for (const value of entry) {
|
|
||||||
headers.push([key, value]);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
headers.push([key, entry]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
this.#resolve(
|
|
||||||
new Response(body, {
|
|
||||||
headers,
|
|
||||||
status: this.statusCode,
|
|
||||||
statusText: this.statusMessage,
|
|
||||||
}),
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
this._resolve(
|
||||||
|
new Response(body, {
|
||||||
|
headers,
|
||||||
|
status: this.statusCode,
|
||||||
|
statusText: this.statusMessage,
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
ServerResponse.prototype.end = function (
|
||||||
|
this: ServerResponse,
|
||||||
// deno-lint-ignore no-explicit-any
|
// deno-lint-ignore no-explicit-any
|
||||||
override end(chunk?: any, encoding?: any, cb?: any): this {
|
chunk?: any,
|
||||||
this.finished = true;
|
// deno-lint-ignore no-explicit-any
|
||||||
if (!chunk && "transfer-encoding" in this.#headers) {
|
encoding?: any,
|
||||||
// FIXME(bnoordhuis) Node sends a zero length chunked body instead, i.e.,
|
// deno-lint-ignore no-explicit-any
|
||||||
// the trailing "0\r\n", but respondWith() just hangs when I try that.
|
cb?: any,
|
||||||
this.#headers["content-length"] = "0";
|
) {
|
||||||
delete this.#headers["transfer-encoding"];
|
this.finished = true;
|
||||||
}
|
if (!chunk && "transfer-encoding" in this._headers) {
|
||||||
|
// FIXME(bnoordhuis) Node sends a zero length chunked body instead, i.e.,
|
||||||
// @ts-expect-error The signature for cb is stricter than the one implemented here
|
// the trailing "0\r\n", but respondWith() just hangs when I try that.
|
||||||
return super.end(chunk, encoding, cb);
|
this._headers["content-length"] = "0";
|
||||||
|
delete this._headers["transfer-encoding"];
|
||||||
}
|
}
|
||||||
|
|
||||||
flushHeaders() {
|
// @ts-expect-error The signature for cb is stricter than the one implemented here
|
||||||
// no-op
|
NodeWritable.prototype.end.call(this, chunk, encoding, cb);
|
||||||
}
|
};
|
||||||
|
|
||||||
// Undocumented API used by `npm:compression`.
|
ServerResponse.prototype.flushHeaders = function (this: ServerResponse) {
|
||||||
_implicitHeader() {
|
// no-op
|
||||||
this.writeHead(this.statusCode);
|
};
|
||||||
}
|
|
||||||
|
|
||||||
assignSocket(socket) {
|
// Undocumented API used by `npm:compression`.
|
||||||
if (socket._httpMessage) {
|
ServerResponse.prototype._implicitHeader = function (this: ServerResponse) {
|
||||||
throw new ERR_HTTP_SOCKET_ASSIGNED();
|
this.writeHead(this.statusCode);
|
||||||
}
|
};
|
||||||
socket._httpMessage = this;
|
|
||||||
this.#socketOverride = socket;
|
|
||||||
}
|
|
||||||
|
|
||||||
detachSocket(socket) {
|
ServerResponse.prototype.assignSocket = function (
|
||||||
assert(socket._httpMessage === this);
|
this: ServerResponse,
|
||||||
socket._httpMessage = null;
|
socket,
|
||||||
this.#socketOverride = null;
|
) {
|
||||||
|
if (socket._httpMessage) {
|
||||||
|
throw new ERR_HTTP_SOCKET_ASSIGNED();
|
||||||
}
|
}
|
||||||
}
|
socket._httpMessage = this;
|
||||||
|
this._socketOverride = socket;
|
||||||
|
};
|
||||||
|
|
||||||
|
ServerResponse.prototype.detachSocket = function (
|
||||||
|
this: ServerResponse,
|
||||||
|
socket,
|
||||||
|
) {
|
||||||
|
assert(socket._httpMessage === this);
|
||||||
|
socket._httpMessage = null;
|
||||||
|
this._socketOverride = null;
|
||||||
|
};
|
||||||
|
|
||||||
|
Object.defineProperty(ServerResponse.prototype, "connection", {
|
||||||
|
get: deprecate(
|
||||||
|
function (this: ServerResponse) {
|
||||||
|
return this._socketOverride;
|
||||||
|
},
|
||||||
|
"ServerResponse.prototype.connection is deprecated",
|
||||||
|
"DEP0066",
|
||||||
|
),
|
||||||
|
set: deprecate(
|
||||||
|
// deno-lint-ignore no-explicit-any
|
||||||
|
function (this: ServerResponse, socket: any) {
|
||||||
|
this._socketOverride = socket;
|
||||||
|
},
|
||||||
|
"ServerResponse.prototype.connection is deprecated",
|
||||||
|
"DEP0066",
|
||||||
|
),
|
||||||
|
});
|
||||||
|
|
||||||
// TODO(@AaronO): optimize
|
// TODO(@AaronO): optimize
|
||||||
export class IncomingMessageForServer extends NodeReadable {
|
export class IncomingMessageForServer extends NodeReadable {
|
||||||
|
|
|
@ -3,10 +3,14 @@
|
||||||
// deno-lint-ignore-file no-console
|
// deno-lint-ignore-file no-console
|
||||||
|
|
||||||
import EventEmitter from "node:events";
|
import EventEmitter from "node:events";
|
||||||
import http, { type RequestOptions, type ServerResponse } from "node:http";
|
import http, {
|
||||||
|
IncomingMessage,
|
||||||
|
type RequestOptions,
|
||||||
|
ServerResponse,
|
||||||
|
} from "node:http";
|
||||||
import url from "node:url";
|
import url from "node:url";
|
||||||
import https from "node:https";
|
import https from "node:https";
|
||||||
import net from "node:net";
|
import net, { Socket } from "node:net";
|
||||||
import fs from "node:fs";
|
import fs from "node:fs";
|
||||||
import { text } from "node:stream/consumers";
|
import { text } from "node:stream/consumers";
|
||||||
|
|
||||||
|
@ -1704,3 +1708,75 @@ Deno.test("[node/http] upgraded socket closes when the server closed without clo
|
||||||
await clientSocketClosed.promise;
|
await clientSocketClosed.promise;
|
||||||
await serverProcessClosed.promise;
|
await serverProcessClosed.promise;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// deno-lint-ignore require-await
|
||||||
|
Deno.test("[node/http] ServerResponse.call()", async () => {
|
||||||
|
function Wrapper(this: unknown, req: IncomingMessage) {
|
||||||
|
ServerResponse.call(this, req);
|
||||||
|
}
|
||||||
|
Object.setPrototypeOf(Wrapper.prototype, ServerResponse.prototype);
|
||||||
|
|
||||||
|
// deno-lint-ignore no-explicit-any
|
||||||
|
const wrapper = new (Wrapper as any)(new IncomingMessage(new Socket()));
|
||||||
|
|
||||||
|
assert(wrapper instanceof ServerResponse);
|
||||||
|
});
|
||||||
|
|
||||||
|
Deno.test("[node/http] ServerResponse _header", async () => {
|
||||||
|
const { promise, resolve } = Promise.withResolvers<void>();
|
||||||
|
const server = http.createServer((_req, res) => {
|
||||||
|
assert(Object.hasOwn(res, "_header"));
|
||||||
|
res.end();
|
||||||
|
});
|
||||||
|
|
||||||
|
server.listen(async () => {
|
||||||
|
const { port } = server.address() as { port: number };
|
||||||
|
const res = await fetch(`http://localhost:${port}`);
|
||||||
|
await res.body?.cancel();
|
||||||
|
server.close(() => {
|
||||||
|
resolve();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
await promise;
|
||||||
|
});
|
||||||
|
|
||||||
|
Deno.test("[node/http] ServerResponse connection", async () => {
|
||||||
|
const { promise, resolve } = Promise.withResolvers<void>();
|
||||||
|
const server = http.createServer((_req, res) => {
|
||||||
|
assert(Object.hasOwn(res, "connection"));
|
||||||
|
assert(res.connection instanceof Socket);
|
||||||
|
res.end();
|
||||||
|
});
|
||||||
|
|
||||||
|
server.listen(async () => {
|
||||||
|
const { port } = server.address() as { port: number };
|
||||||
|
const res = await fetch(`http://localhost:${port}`);
|
||||||
|
await res.body?.cancel();
|
||||||
|
server.close(() => {
|
||||||
|
resolve();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
await promise;
|
||||||
|
});
|
||||||
|
|
||||||
|
Deno.test("[node/http] ServerResponse socket", async () => {
|
||||||
|
const { promise, resolve } = Promise.withResolvers<void>();
|
||||||
|
const server = http.createServer((_req, res) => {
|
||||||
|
assert(Object.hasOwn(res, "socket"));
|
||||||
|
assert(res.socket instanceof Socket);
|
||||||
|
res.end();
|
||||||
|
});
|
||||||
|
|
||||||
|
server.listen(async () => {
|
||||||
|
const { port } = server.address() as { port: number };
|
||||||
|
const res = await fetch(`http://localhost:${port}`);
|
||||||
|
await res.body?.cancel();
|
||||||
|
server.close(() => {
|
||||||
|
resolve();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
await promise;
|
||||||
|
});
|
||||||
|
|
Loading…
Reference in a new issue