1
0
Fork 0
mirror of https://github.com/denoland/deno.git synced 2024-12-25 00:29:09 -05:00
denoland-deno/ext/node/polyfills/http2.ts

2133 lines
56 KiB
TypeScript

// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license.
// Copyright Joyent and Node contributors. All rights reserved. MIT license.
// TODO(petamoriken): enable prefer-primordials for node polyfills
// deno-lint-ignore-file prefer-primordials
import { core } from "ext:core/mod.js";
import {
op_http2_client_get_response,
op_http2_client_get_response_body_chunk,
op_http2_client_get_response_trailers,
op_http2_client_request,
op_http2_client_reset_stream,
op_http2_client_send_data,
op_http2_client_send_trailers,
op_http2_connect,
op_http2_poll_client_connection,
} from "ext:core/ops";
import { notImplemented, warnNotImplemented } from "ext:deno_node/_utils.ts";
import { EventEmitter } from "node:events";
import { Buffer } from "node:buffer";
import { connect as netConnect, Server, Socket, TCP } from "node:net";
import { connect as tlsConnect } from "node:tls";
import { TypedArray } from "ext:deno_node/internal/util/types.ts";
import {
kHandle,
kMaybeDestroy,
kUpdateTimer,
setStreamTimeout,
} from "ext:deno_node/internal/stream_base_commons.ts";
import { FileHandle } from "node:fs/promises";
import { kStreamBaseField } from "ext:deno_node/internal_binding/stream_wrap.ts";
import { addTrailers, serveHttpOnConnection } from "ext:deno_http/00_serve.js";
import { nextTick } from "ext:deno_node/_next_tick.ts";
import { TextEncoder } from "ext:deno_web/08_text_encoding.js";
import { Duplex } from "node:stream";
import {
AbortError,
ERR_HTTP2_CONNECT_AUTHORITY,
ERR_HTTP2_CONNECT_PATH,
ERR_HTTP2_CONNECT_SCHEME,
ERR_HTTP2_GOAWAY_SESSION,
ERR_HTTP2_INVALID_PSEUDOHEADER,
ERR_HTTP2_INVALID_SESSION,
ERR_HTTP2_INVALID_STREAM,
ERR_HTTP2_SESSION_ERROR,
ERR_HTTP2_STREAM_CANCEL,
ERR_HTTP2_STREAM_ERROR,
ERR_HTTP2_TRAILERS_ALREADY_SENT,
ERR_HTTP2_TRAILERS_NOT_READY,
ERR_HTTP2_UNSUPPORTED_PROTOCOL,
ERR_INVALID_HTTP_TOKEN,
ERR_SOCKET_CLOSED,
} from "ext:deno_node/internal/errors.ts";
import { _checkIsHttpToken } from "ext:deno_node/_http_common.ts";
const kSession = Symbol("session");
const kAlpnProtocol = Symbol("alpnProtocol");
const kAuthority = Symbol("authority");
const kEncrypted = Symbol("encrypted");
const kID = Symbol("id");
const kInit = Symbol("init");
const kInfoHeaders = Symbol("sent-info-headers");
const kOrigin = Symbol("origin");
const kPendingRequestCalls = Symbol("kPendingRequestCalls");
const kProtocol = Symbol("protocol");
const kSentHeaders = Symbol("sent-headers");
const kSentTrailers = Symbol("sent-trailers");
const kState = Symbol("state");
const kType = Symbol("type");
const kTimeout = Symbol("timeout");
const kDenoResponse = Symbol("kDenoResponse");
const kDenoRid = Symbol("kDenoRid");
const kDenoClientRid = Symbol("kDenoClientRid");
const kDenoConnRid = Symbol("kDenoConnRid");
const kPollConnPromise = Symbol("kPollConnPromise");
const STREAM_FLAGS_PENDING = 0x0;
const STREAM_FLAGS_READY = 0x1;
const STREAM_FLAGS_CLOSED = 0x2;
const STREAM_FLAGS_HEADERS_SENT = 0x4;
const STREAM_FLAGS_HEAD_REQUEST = 0x8;
const STREAM_FLAGS_ABORTED = 0x10;
const STREAM_FLAGS_HAS_TRAILERS = 0x20;
const SESSION_FLAGS_PENDING = 0x0;
const SESSION_FLAGS_READY = 0x1;
const SESSION_FLAGS_CLOSED = 0x2;
const SESSION_FLAGS_DESTROYED = 0x4;
const ENCODER = new TextEncoder();
type Http2Headers = Record<string, string | string[]>;
const debugHttp2Enabled = false;
function debugHttp2(...args) {
if (debugHttp2Enabled) {
console.log(...args);
}
}
export class Http2Session extends EventEmitter {
constructor(type, _options /* socket */) {
super();
// TODO(bartlomieju): Handle sockets here
this[kState] = {
destroyCode: constants.NGHTTP2_NO_ERROR,
flags: SESSION_FLAGS_PENDING,
goawayCode: null,
goawayLastStreamID: null,
streams: new Map(),
pendingStreams: new Set(),
pendingAck: 0,
writeQueueSize: 0,
originSet: undefined,
};
this[kEncrypted] = undefined;
this[kAlpnProtocol] = undefined;
this[kType] = type;
this[kTimeout] = null;
// this[kProxySocket] = null;
// this[kSocket] = socket;
// this[kHandle] = undefined;
// TODO(bartlomieju): connecting via socket
}
get encrypted(): boolean {
return this[kEncrypted];
}
get alpnProtocol(): string | undefined {
return this[kAlpnProtocol];
}
get originSet(): string[] | undefined {
if (!this.encrypted || this.destroyed) {
return undefined;
}
// TODO(bartlomieju):
return [];
}
get connecting(): boolean {
return (this[kState].flags & SESSION_FLAGS_READY) === 0;
}
get closed(): boolean {
return !!(this[kState].flags & SESSION_FLAGS_CLOSED);
}
get destroyed(): boolean {
return !!(this[kState].flags & SESSION_FLAGS_DESTROYED);
}
[kUpdateTimer]() {
if (this.destroyed) {
return;
}
if (this[kTimeout]) {
this[kTimeout].refresh();
}
}
setLocalWindowSize(_windowSize: number) {
notImplemented("Http2Session.setLocalWindowSize");
}
ping(
_payload: Buffer | TypedArray | DataView,
_callback: () => void,
): boolean {
notImplemented("Http2Session.ping");
return false;
}
get socket(): Socket /*| TlsSocket*/ {
warnNotImplemented("Http2Session.socket");
return {};
}
get type(): number {
return this[kType];
}
get pendingSettingsAck() {
return this[kState].pendingAck > 0;
}
get state(): Record<string, unknown> {
return {};
}
get localSettings(): Record<string, unknown> {
notImplemented("Http2Session.localSettings");
return {};
}
get remoteSettings(): Record<string, unknown> {
notImplemented("Http2Session.remoteSettings");
return {};
}
settings(_settings: Record<string, unknown>, _callback: () => void) {
notImplemented("Http2Session.settings");
}
goaway(
_code: number,
_lastStreamID: number,
_opaqueData: Buffer | TypedArray | DataView,
) {
warnNotImplemented("Http2Session.goaway");
if (this[kDenoConnRid]) {
core.tryClose(this[kDenoConnRid]);
}
if (this[kDenoClientRid]) {
core.tryClose(this[kDenoClientRid]);
}
}
destroy(error = constants.NGHTTP2_NO_ERROR, code?: number) {
if (this.destroyed) {
return;
}
if (typeof error === "number") {
code = error;
error = code !== constants.NGHTTP2_NO_ERROR
? new ERR_HTTP2_SESSION_ERROR(code)
: undefined;
}
if (code === undefined && error != null) {
code = constants.NGHTTP2_INTERNAL_ERROR;
}
closeSession(this, code, error);
}
close(callback?: () => void) {
if (this.closed || this.destroyed) {
return;
}
this[kState].flags |= SESSION_FLAGS_CLOSED;
if (typeof callback === "function") {
this.once("close", callback);
}
this.goaway();
this[kMaybeDestroy]();
}
[kMaybeDestroy](error?: number) {
if (!error) {
const state = this[kState];
// Don't destroy if the session is not closed or there are pending or open
// streams.
if (
!this.closed || state.streams.size > 0 || state.pendingStreams.size >
0
) {
return;
}
}
this.destroy(error);
}
ref() {
warnNotImplemented("Http2Session.ref");
}
unref() {
warnNotImplemented("Http2Session.unref");
}
setTimeout(msecs: number, callback?: () => void) {
setStreamTimeout.call(this, msecs, callback);
}
}
function emitClose(session: Http2Session, error?: Error) {
if (error) {
session.emit("error", error);
}
session.emit("close");
}
function finishSessionClose(session: Http2Session, error?: Error) {
// TODO(bartlomieju): handle sockets
nextTick(emitClose, session, error);
}
function closeSession(session: Http2Session, code?: number, error?: Error) {
const state = session[kState];
state.flags |= SESSION_FLAGS_DESTROYED;
state.destroyCode = code;
session.setTimeout(0);
session.removeAllListeners("timeout");
// Destroy open and pending streams
if (state.pendingStreams.size > 0 || state.streams.size > 0) {
const cancel = new ERR_HTTP2_STREAM_CANCEL(error);
state.pendingStreams.forEach((stream) => stream.destroy(cancel));
state.streams.forEach((stream) => stream.destroy(cancel));
}
// TODO(bartlomieju): handle sockets
debugHttp2(
">>> closeSession",
session[kDenoConnRid],
session[kDenoClientRid],
);
console.table(Deno[Deno.internal].core.resources());
if (session[kDenoConnRid]) {
core.tryClose(session[kDenoConnRid]);
}
if (session[kDenoClientRid]) {
core.tryClose(session[kDenoClientRid]);
}
finishSessionClose(session, error);
}
export class ServerHttp2Session extends Http2Session {
constructor() {
super(constants.NGHTTP2_SESSION_SERVER, {});
}
altsvc(
_alt: string,
_originOrStream: number | string | URL | { origin: string },
) {
notImplemented("ServerHttp2Session.altsvc");
}
origin(..._origins: (string | URL | { origin: string })[]) {
notImplemented("ServerHttp2Session.origins");
}
}
function assertValidPseudoHeader(header: string) {
switch (header) {
case ":authority":
case ":path":
case ":method":
case ":scheme":
case ":status":
return;
default:
throw new ERR_HTTP2_INVALID_PSEUDOHEADER(header);
}
}
export class ClientHttp2Session extends Http2Session {
#connectPromise: Promise<void>;
#refed = true;
constructor(
// deno-lint-ignore no-explicit-any
socket: any,
url: string,
options: Record<string, unknown>,
) {
super(constants.NGHTTP2_SESSION_CLIENT, options);
this[kPendingRequestCalls] = null;
this[kDenoClientRid] = undefined;
this[kDenoConnRid] = undefined;
this[kPollConnPromise] = undefined;
socket.on("error", socketOnError);
socket.on("close", socketOnClose);
const connPromise = new Promise((resolve) => {
const eventName = url.startsWith("https") ? "secureConnect" : "connect";
socket.once(eventName, () => {
const rid = socket[kHandle][kStreamBaseField].rid;
nextTick(() => {
resolve(rid);
});
});
});
socket[kSession] = this;
// TODO(bartlomieju): cleanup
this.#connectPromise = (async () => {
debugHttp2(">>> before connect");
const connRid_ = await connPromise;
// console.log(">>>> awaited connRid", connRid_, url);
const [clientRid, connRid] = await op_http2_connect(connRid_, url);
debugHttp2(">>> after connect", clientRid, connRid);
this[kDenoClientRid] = clientRid;
this[kDenoConnRid] = connRid;
(async () => {
try {
const promise = op_http2_poll_client_connection(
this[kDenoConnRid],
);
this[kPollConnPromise] = promise;
if (!this.#refed) {
this.unref();
}
await promise;
} catch (e) {
this.emit("error", e);
}
})();
this.emit("connect", this, {});
})();
}
ref() {
this.#refed = true;
if (this[kPollConnPromise]) {
core.refOpPromise(this[kPollConnPromise]);
}
}
unref() {
this.#refed = false;
if (this[kPollConnPromise]) {
core.unrefOpPromise(this[kPollConnPromise]);
}
}
request(
headers: Http2Headers,
options?: Record<string, unknown>,
): ClientHttp2Stream {
if (this.destroyed) {
throw new ERR_HTTP2_INVALID_SESSION();
}
if (this.closed) {
throw new ERR_HTTP2_GOAWAY_SESSION();
}
this[kUpdateTimer]();
if (headers !== null && headers !== undefined) {
const keys = Object.keys(headers);
for (let i = 0; i < keys.length; i++) {
const header = keys[i];
if (header[0] === ":") {
assertValidPseudoHeader(header);
} else if (header && !_checkIsHttpToken(header)) {
this.destroy(new ERR_INVALID_HTTP_TOKEN("Header name", header));
}
}
}
headers = Object.assign({ __proto__: null }, headers);
options = { ...options };
if (headers[constants.HTTP2_HEADER_METHOD] === undefined) {
headers[constants.HTTP2_HEADER_METHOD] = constants.HTTP2_METHOD_GET;
}
const connect =
headers[constants.HTTP2_HEADER_METHOD] === constants.HTTP2_METHOD_CONNECT;
if (!connect || headers[constants.HTTP2_HEADER_PROTOCOL] !== undefined) {
if (getAuthority(headers) === undefined) {
headers[constants.HTTP2_HEADER_AUTHORITY] = this[kAuthority];
}
if (headers[constants.HTTP2_HEADER_SCHEME] === undefined) {
headers[constants.HTTP2_HEADER_SCHEME] = this[kProtocol].slice(0, -1);
}
if (headers[constants.HTTP2_HEADER_PATH] === undefined) {
headers[constants.HTTP2_HEADER_PATH] = "/";
}
} else {
if (headers[constants.HTTP2_HEADER_AUTHORITY] === undefined) {
throw new ERR_HTTP2_CONNECT_AUTHORITY();
}
if (headers[constants.HTTP2_HEADER_SCHEME] === undefined) {
throw new ERR_HTTP2_CONNECT_SCHEME();
}
if (headers[constants.HTTP2_HEADER_PATH] === undefined) {
throw new ERR_HTTP2_CONNECT_PATH();
}
}
if (options.endStream === undefined) {
const method = headers[constants.HTTP2_HEADER_METHOD];
options.endStream = method === constants.HTTP2_METHOD_DELETE ||
method === constants.HTTP2_METHOD_GET ||
method === constants.HTTP2_METHOD_HEAD;
} else {
options.endStream = !!options.endStream;
}
const stream = new ClientHttp2Stream(
options,
this,
this.#connectPromise,
headers,
);
stream[kSentHeaders] = headers;
stream[kOrigin] = `${headers[constants.HTTP2_HEADER_SCHEME]}://${
getAuthority(headers)
}`;
if (options.endStream) {
stream.end();
}
if (options.waitForTrailers) {
stream[kState].flags |= STREAM_FLAGS_HAS_TRAILERS;
}
const { signal } = options;
if (signal) {
const aborter = () => {
stream.destroy(new AbortError(undefined, { cause: signal.reason }));
};
if (signal.aborted) {
aborter();
} else {
// TODO(bartlomieju): handle this
// const disposable = EventEmitter.addAbortListener(signal, aborter);
// stream.once("close", disposable[Symbol.dispose]);
}
}
// TODO(bartlomieju): handle this
const onConnect = () => {};
if (this.connecting) {
if (this[kPendingRequestCalls] !== null) {
this[kPendingRequestCalls].push(onConnect);
} else {
this[kPendingRequestCalls] = [onConnect];
this.once("connect", () => {
this[kPendingRequestCalls].forEach((f) => f());
this[kPendingRequestCalls] = null;
});
}
} else {
onConnect();
}
return stream;
}
}
function getAuthority(headers) {
if (headers[constants.HTTP2_HEADER_AUTHORITY] !== undefined) {
return headers[constants.HTTP2_HEADER_AUTHORITY];
}
if (headers[constants.HTTP2_HEADER_HOST] !== undefined) {
return headers[constants.HTTP2_HEADER_HOST];
}
return undefined;
}
export class Http2Stream extends EventEmitter {
#session: Http2Session;
#headers: Promise<Http2Headers>;
#controllerPromise: Promise<ReadableStreamDefaultController<Uint8Array>>;
#readerPromise: Promise<ReadableStream<Uint8Array>>;
#closed: boolean;
_response: Response;
constructor(
session: Http2Session,
headers: Promise<Http2Headers>,
controllerPromise: Promise<ReadableStreamDefaultController<Uint8Array>>,
readerPromise: Promise<ReadableStream<Uint8Array>>,
) {
super();
this.#session = session;
this.#headers = headers;
this.#controllerPromise = controllerPromise;
this.#readerPromise = readerPromise;
this.#closed = false;
nextTick(() => {
(async () => {
const headers = await this.#headers;
this.emit("headers", headers);
})();
(async () => {
const reader = await this.#readerPromise;
if (reader) {
for await (const data of reader) {
this.emit("data", new Buffer(data));
}
}
this.emit("end");
})();
});
}
// TODO(mmastrac): Implement duplex
end() {
(async () => {
const controller = await this.#controllerPromise;
controller.close();
})();
}
write(buffer, callback?: () => void) {
(async () => {
const controller = await this.#controllerPromise;
if (typeof buffer === "string") {
controller.enqueue(ENCODER.encode(buffer));
} else {
controller.enqueue(buffer);
}
callback?.();
})();
}
setEncoding(_encoding) {}
resume() {
}
pause() {
}
get aborted(): boolean {
notImplemented("Http2Stream.aborted");
return false;
}
get bufferSize(): number {
notImplemented("Http2Stream.bufferSize");
return 0;
}
close(_code: number, _callback: () => void) {
this.#closed = true;
this.emit("close");
}
get closed(): boolean {
return this.#closed;
}
get destroyed(): boolean {
return false;
}
get endAfterHeaders(): boolean {
notImplemented("Http2Stream.endAfterHeaders");
return false;
}
get id(): number | undefined {
notImplemented("Http2Stream.id");
return undefined;
}
get pending(): boolean {
notImplemented("Http2Stream.pending");
return false;
}
priority(_options: Record<string, unknown>) {
notImplemented("Http2Stream.priority");
}
get rstCode(): number {
// notImplemented("Http2Stream.rstCode");
return 0;
}
get sentHeaders(): boolean {
notImplemented("Http2Stream.sentHeaders");
return false;
}
get sentInfoHeaders(): Record<string, unknown> {
notImplemented("Http2Stream.sentInfoHeaders");
return {};
}
get sentTrailers(): Record<string, unknown> {
notImplemented("Http2Stream.sentTrailers");
return {};
}
get session(): Http2Session {
return this.#session;
}
setTimeout(msecs: number, callback?: () => void) {
setStreamTimeout(this, msecs, callback);
}
get state(): Record<string, unknown> {
notImplemented("Http2Stream.state");
return {};
}
sendTrailers(_headers: Record<string, unknown>) {
addTrailers(this._response, [["grpc-status", "0"], ["grpc-message", "OK"]]);
}
}
async function clientHttp2Request(
session,
sessionConnectPromise,
headers,
options,
) {
debugHttp2(
">>> waiting for connect promise",
sessionConnectPromise,
headers,
options,
);
await sessionConnectPromise;
const reqHeaders: string[][] = [];
const pseudoHeaders = {};
for (const [key, value] of Object.entries(headers)) {
if (key[0] === ":") {
pseudoHeaders[key] = value;
} else {
reqHeaders.push([key, Array.isArray(value) ? value[0] : value]);
}
}
debugHttp2(
"waited for connect promise",
!!options.waitForTrailers,
pseudoHeaders,
reqHeaders,
);
return await op_http2_client_request(
session[kDenoClientRid],
pseudoHeaders,
reqHeaders,
);
}
export class ClientHttp2Stream extends Duplex {
#requestPromise: Promise<[number, number]>;
#responsePromise: Promise<void>;
#rid: number | undefined = undefined;
#encoding = "utf8";
constructor(
options: Record<string, unknown>,
session: Http2Session,
sessionConnectPromise: Promise<void>,
headers: Record<string, string>,
) {
options.allowHalfOpen = true;
options.decodeString = false;
options.autoDestroy = false;
super(options);
this.cork();
this[kSession] = session;
session[kState].pendingStreams.add(this);
this._readableState.readingMore = true;
this[kState] = {
didRead: false,
flags: STREAM_FLAGS_PENDING | STREAM_FLAGS_HEADERS_SENT,
rstCode: constants.NGHTTP2_NO_ERROR,
writeQueueSize: 0,
trailersReady: false,
endAfterHeaders: false,
shutdownWritableCalled: false,
};
this[kDenoResponse] = undefined;
this[kDenoRid] = undefined;
this.#requestPromise = clientHttp2Request(
session,
sessionConnectPromise,
headers,
options,
);
debugHttp2(">>> created clienthttp2stream");
// TODO(bartlomieju): save it so we can unref
this.#responsePromise = (async () => {
debugHttp2(">>> before request promise", session[kDenoClientRid]);
const [streamRid, streamId] = await this.#requestPromise;
this.#rid = streamRid;
this[kDenoRid] = streamRid;
this[kInit](streamId);
debugHttp2(
">>> after request promise",
session[kDenoClientRid],
this.#rid,
);
const response = await op_http2_client_get_response(
this.#rid,
);
debugHttp2(">>> after get response", response);
const headers = {
":status": response.statusCode,
...Object.fromEntries(response.headers),
};
debugHttp2(">>> emitting response", headers);
this.emit("response", headers, 0);
this[kDenoResponse] = response;
this.emit("ready");
})();
}
[kUpdateTimer]() {
if (this.destroyed) {
return;
}
if (this[kTimeout]) {
this[kTimeout].refresh();
}
if (this[kSession]) {
this[kSession][kUpdateTimer]();
}
}
[kInit](id) {
const state = this[kState];
state.flags |= STREAM_FLAGS_READY;
const session = this[kSession];
session[kState].pendingStreams.delete(this);
session[kState].streams.set(id, this);
// TODO(bartlomieju): handle socket handle
this[kID] = id;
this.uncork();
this.emit("ready");
}
get bufferSize() {
return this[kState].writeQueueSize + this.writableLength;
}
get endAfterHeaders() {
return this[kState].endAfterHeaders;
}
get sentHeaders() {
return this[kSentHeaders];
}
get sentTrailers() {
return this[kSentTrailers];
}
get sendInfoHeaders() {
return this[kInfoHeaders];
}
get pending(): boolean {
return this[kID] === undefined;
}
get id(): number | undefined {
return this[kID];
}
get session(): Http2Session {
return this[kSession];
}
_onTimeout() {
callTimeout(this, kSession);
}
get headersSent() {
return !!(this[kState].flags & STREAM_FLAGS_HEADERS_SENT);
}
get aborted() {
return !!(this[kState].flags & STREAM_FLAGS_ABORTED);
}
get headRequest() {
return !!(this[kState].flags & STREAM_FLAGS_HEAD_REQUEST);
}
get rstCode() {
return this[kState].rstCode;
}
get state(): Record<string, unknown> {
notImplemented("Http2Stream.state");
return {};
}
// [kAfterAsyncWrite]() {}
// [kWriteGeneric]() {}
// TODO(bartlomieju): clean up
_write(chunk, encoding, callback?: () => void) {
debugHttp2(">>> _write", callback);
if (typeof encoding === "function") {
callback = encoding;
encoding = "utf8";
}
let data;
if (typeof encoding === "string") {
data = ENCODER.encode(chunk);
} else {
data = chunk.buffer;
}
this.#requestPromise
.then(() => {
debugHttp2(">>> _write", this.#rid, data, encoding, callback);
return op_http2_client_send_data(
this.#rid,
data,
);
})
.then(() => {
callback?.();
debugHttp2(
"this.writableFinished",
this.pending,
this.destroyed,
this.writableFinished,
);
})
.catch((e) => {
callback?.(e);
});
}
// TODO(bartlomieju): finish this method
_writev(_chunks, _callback?) {
notImplemented("ClientHttp2Stream._writev");
}
_final(cb) {
debugHttp2("_final", new Error());
if (this.pending) {
this.once("ready", () => this._final(cb));
return;
}
shutdownWritable(this, cb);
}
// TODO(bartlomieju): needs a proper cleanup
_read() {
if (this.destroyed) {
this.push(null);
return;
}
if (!this[kState].didRead) {
this._readableState.readingMore = false;
this[kState].didRead = true;
}
// if (!this.pending) {
// streamOnResume(this);
// } else {
// this.once("ready", () => streamOnResume(this));
// }
if (!this[kDenoResponse]) {
this.once("ready", this._read);
return;
}
debugHttp2(">>> read");
(async () => {
const [chunk, finished] = await op_http2_client_get_response_body_chunk(
this[kDenoResponse].bodyRid,
);
debugHttp2(">>> chunk", chunk, finished, this[kDenoResponse].bodyRid);
if (chunk === null) {
const trailerList = await op_http2_client_get_response_trailers(
this[kDenoResponse].bodyRid,
);
if (trailerList) {
const trailers = Object.fromEntries(trailerList);
this.emit("trailers", trailers);
}
debugHttp2("tryClose");
core.tryClose(this[kDenoResponse].bodyRid);
this.push(null);
debugHttp2(">>> read null chunk");
this[kMaybeDestroy]();
return;
}
let result;
if (this.#encoding === "utf8") {
result = this.push(new TextDecoder().decode(new Uint8Array(chunk)));
} else {
result = this.push(new Uint8Array(chunk));
}
debugHttp2(">>> read result", result);
})();
}
// TODO(bartlomieju):
priority(_options: Record<string, unknown>) {
notImplemented("Http2Stream.priority");
}
sendTrailers(trailers: Record<string, unknown>) {
debugHttp2("sendTrailers", trailers);
if (this.destroyed || this.closed) {
throw new ERR_HTTP2_INVALID_STREAM();
}
if (this[kSentTrailers]) {
throw new ERR_HTTP2_TRAILERS_ALREADY_SENT();
}
if (!this[kState].trailersReady) {
throw new ERR_HTTP2_TRAILERS_NOT_READY();
}
trailers = Object.assign({ __proto__: null }, trailers);
const trailerList = [];
for (const [key, value] of Object.entries(trailers)) {
trailerList.push([key, value]);
}
this[kSentTrailers] = trailers;
// deno-lint-ignore no-this-alias
const stream = this;
stream[kState].flags &= ~STREAM_FLAGS_HAS_TRAILERS;
debugHttp2("sending trailers", this.#rid, trailers);
op_http2_client_send_trailers(
this.#rid,
trailerList,
).then(() => {
stream[kMaybeDestroy]();
core.tryClose(this.#rid);
}).catch((e) => {
debugHttp2(">>> send trailers error", e);
core.tryClose(this.#rid);
stream._destroy(e);
});
}
get closed(): boolean {
return !!(this[kState].flags & STREAM_FLAGS_CLOSED);
}
close(code: number = constants.NGHTTP2_NO_ERROR, callback?: () => void) {
debugHttp2(">>> close", code, this.closed, callback);
if (this.closed) {
return;
}
if (typeof callback !== "undefined") {
this.once("close", callback);
}
closeStream(this, code);
}
_destroy(err, callback) {
debugHttp2(">>> ClientHttp2Stream._destroy", err, callback);
const session = this[kSession];
const id = this[kID];
const state = this[kState];
const sessionState = session[kState];
const sessionCode = sessionState.goawayCode || sessionState.destroyCode;
let code = this.closed ? this.rstCode : sessionCode;
if (err != null) {
if (sessionCode) {
code = sessionCode;
} else if (err instanceof AbortError) {
code = constants.NGHTTP2_CANCEL;
} else {
code = constants.NGHTTP2_INTERNAL_ERROR;
}
}
if (!this.closed) {
// TODO(bartlomieju): this not handle `socket handle`
closeStream(this, code, kNoRstStream);
}
sessionState.streams.delete(id);
sessionState.pendingStreams.delete(this);
sessionState.writeQueueSize -= state.writeQueueSize;
state.writeQueueSize = 0;
const nameForErrorCode = {};
if (
err == null && code !== constants.NGHTTP2_NO_ERROR &&
code !== constants.NGHTTP2_CANCEL
) {
err = new ERR_HTTP2_STREAM_ERROR(nameForErrorCode[code] || code);
}
this[kSession] = undefined;
session[kMaybeDestroy]();
callback(err);
}
[kMaybeDestroy](code = constants.NGHTTP2_NO_ERROR) {
debugHttp2(
">>> ClientHttp2Stream[kMaybeDestroy]",
code,
this.writableFinished,
this.readable,
this.closed,
);
if (code !== constants.NGHTTP2_NO_ERROR) {
this._destroy();
return;
}
if (this.writableFinished) {
if (!this.readable && this.closed) {
debugHttp2("going into _destroy");
this._destroy();
return;
}
}
}
setTimeout(msecs: number, callback?: () => void) {
// TODO(bartlomieju): fix this call, it's crashing on `this` being undefined;
// some strange transpilation quirk going on here.
setStreamTimeout.call(this, msecs, callback);
}
}
function shutdownWritable(stream, callback) {
debugHttp2(">>> shutdownWritable", callback);
const state = stream[kState];
if (state.shutdownWritableCalled) {
return callback();
}
state.shutdownWritableCalled = true;
onStreamTrailers(stream);
callback();
// TODO(bartlomieju): might have to add "finish" event listener here,
// check it.
}
function onStreamTrailers(stream) {
stream[kState].trailersReady = true;
debugHttp2(">>> onStreamTrailers", stream.destroyed, stream.closed);
if (stream.destroyed || stream.closed) {
return;
}
if (!stream.emit("wantTrailers")) {
debugHttp2(">>> onStreamTrailers no wantTrailers");
stream.sendTrailers({});
}
debugHttp2(">>> onStreamTrailers wantTrailers");
}
const kNoRstStream = 0;
const kSubmitRstStream = 1;
const kForceRstStream = 2;
function closeStream(stream, code, rstStreamStatus = kSubmitRstStream) {
const state = stream[kState];
state.flags |= STREAM_FLAGS_CLOSED;
state.rstCode = code;
stream.setTimeout(0);
stream.removeAllListeners("timeout");
const { ending } = stream._writableState;
if (!ending) {
if (!stream.aborted) {
state.flags |= STREAM_FLAGS_ABORTED;
stream.emit("aborted");
}
stream.end();
}
if (rstStreamStatus != kNoRstStream) {
debugHttp2(
">>> closeStream",
!ending,
stream.writableFinished,
code !== constants.NGHTTP2_NO_ERROR,
rstStreamStatus === kForceRstStream,
);
if (
!ending || stream.writableFinished ||
code !== constants.NGHTTP2_NO_ERROR || rstStreamStatus === kForceRstStream
) {
finishCloseStream(stream, code);
} else {
stream.once("finish", () => finishCloseStream(stream, code));
}
}
}
function finishCloseStream(stream, code) {
debugHttp2(">>> finishCloseStream", stream.readableEnded, code);
if (stream.pending) {
stream.push(null);
stream.once("ready", () => {
op_http2_client_reset_stream(
stream[kDenoRid],
code,
).then(() => {
debugHttp2(
">>> finishCloseStream close",
stream[kDenoRid],
stream[kDenoResponse].bodyRid,
);
core.tryClose(stream[kDenoRid]);
core.tryClose(stream[kDenoResponse].bodyRid);
stream.emit("close");
});
});
} else {
stream.resume();
op_http2_client_reset_stream(
stream[kDenoRid],
code,
).then(() => {
debugHttp2(
">>> finishCloseStream close2",
stream[kDenoRid],
stream[kDenoResponse].bodyRid,
);
core.tryClose(stream[kDenoRid]);
core.tryClose(stream[kDenoResponse].bodyRid);
nextTick(() => {
stream.emit("close");
});
}).catch(() => {
debugHttp2(
">>> finishCloseStream close2 catch",
stream[kDenoRid],
stream[kDenoResponse].bodyRid,
);
core.tryClose(stream[kDenoRid]);
core.tryClose(stream[kDenoResponse].bodyRid);
nextTick(() => {
stream.emit("close");
});
});
}
}
function callTimeout() {
notImplemented("callTimeout");
}
export class ServerHttp2Stream extends Http2Stream {
_deferred: ReturnType<typeof Promise.withResolvers<Response>>;
#body: ReadableStream<Uint8Array>;
#waitForTrailers: boolean;
#headersSent: boolean;
constructor(
session: Http2Session,
headers: Promise<Http2Headers>,
controllerPromise: Promise<ReadableStreamDefaultController<Uint8Array>>,
reader: ReadableStream<Uint8Array>,
body: ReadableStream<Uint8Array>,
) {
super(session, headers, controllerPromise, Promise.resolve(reader));
this._deferred = Promise.withResolvers<Response>();
this.#body = body;
}
additionalHeaders(_headers: Record<string, unknown>) {
notImplemented("ServerHttp2Stream.additionalHeaders");
}
end(): void {
super.end();
if (this.#waitForTrailers) {
this.emit("wantTrailers");
}
}
get headersSent(): boolean {
return this.#headersSent;
}
get pushAllowed(): boolean {
notImplemented("ServerHttp2Stream.pushAllowed");
return false;
}
pushStream(
_headers: Record<string, unknown>,
_options: Record<string, unknown>,
_callback: () => unknown,
) {
notImplemented("ServerHttp2Stream.pushStream");
}
respond(
headers: Http2Headers,
options: Record<string, unknown>,
) {
this.#headersSent = true;
const response: ResponseInit = {};
if (headers) {
for (const [name, value] of Object.entries(headers)) {
if (name == constants.HTTP2_HEADER_STATUS) {
response.status = Number(value);
}
}
}
if (options?.endStream) {
this._deferred.resolve(this._response = new Response("", response));
} else {
this.#waitForTrailers = options?.waitForTrailers;
this._deferred.resolve(
this._response = new Response(this.#body, response),
);
}
}
respondWithFD(
_fd: number | FileHandle,
_headers: Record<string, unknown>,
_options: Record<string, unknown>,
) {
notImplemented("ServerHttp2Stream.respondWithFD");
}
respondWithFile(
_path: string | Buffer | URL,
_headers: Record<string, unknown>,
_options: Record<string, unknown>,
) {
notImplemented("ServerHttp2Stream.respondWithFile");
}
}
export class Http2Server extends Server {
#options: Record<string, unknown> = {};
#abortController;
#server;
timeout = 0;
constructor(
options: Record<string, unknown>,
requestListener: () => unknown,
) {
super(options);
this.#abortController = new AbortController();
this.on(
"connection",
(conn: Deno.Conn) => {
try {
const session = new ServerHttp2Session();
this.emit("session", session);
this.#server = serveHttpOnConnection(
conn,
this.#abortController.signal,
async (req: Request) => {
try {
const controllerDeferred = Promise.withResolvers<
ReadableStreamDefaultController<Uint8Array>
>();
const body = new ReadableStream({
start(controller) {
controllerDeferred.resolve(controller);
},
});
const headers: Http2Headers = {};
for (const [name, value] of req.headers) {
headers[name] = value;
}
headers[constants.HTTP2_HEADER_PATH] =
new URL(req.url).pathname;
const stream = new ServerHttp2Stream(
session,
Promise.resolve(headers),
controllerDeferred.promise,
req.body,
body,
);
session.emit("stream", stream, headers);
this.emit("stream", stream, headers);
return await stream._deferred.promise;
} catch (e) {
console.log(">>> Error in serveHttpOnConnection", e);
}
return new Response("");
},
() => {
console.log(">>> error");
},
() => {},
);
} catch (e) {
console.log(">>> Error in Http2Server", e);
}
},
);
this.on(
"newListener",
(event) => console.log(`Event in newListener: ${event}`),
);
this.#options = options;
if (typeof requestListener === "function") {
this.on("request", requestListener);
}
}
// Prevent the TCP server from wrapping this in a socket, since we need it to serve HTTP
_createSocket(clientHandle: TCP) {
return clientHandle[kStreamBaseField];
}
close(callback?: () => unknown) {
if (callback) {
this.on("close", callback);
}
this.#abortController.abort();
super.close();
}
setTimeout(msecs: number, callback?: () => unknown) {
this.timeout = msecs;
if (callback !== undefined) {
this.on("timeout", callback);
}
}
updateSettings(settings: Record<string, unknown>) {
this.#options.settings = { ...this.#options.settings, ...settings };
}
}
export class Http2SecureServer extends Server {
#options: Record<string, unknown> = {};
timeout = 0;
constructor(
options: Record<string, unknown>,
requestListener: () => unknown,
) {
super(options, function () {
notImplemented("connectionListener");
});
this.#options = options;
if (typeof requestListener === "function") {
this.on("request", requestListener);
}
}
close(_callback?: () => unknown) {
notImplemented("Http2SecureServer.close");
}
setTimeout(msecs: number, callback?: () => unknown) {
this.timeout = msecs;
if (callback !== undefined) {
this.on("timeout", callback);
}
}
updateSettings(settings: Record<string, unknown>) {
this.#options.settings = { ...this.#options.settings, ...settings };
}
}
export function createServer(
options: Record<string, unknown>,
onRequestHandler: () => unknown,
): Http2Server {
if (typeof options === "function") {
onRequestHandler = options;
options = {};
}
return new Http2Server(options, onRequestHandler);
}
export function createSecureServer(
_options: Record<string, unknown>,
_onRequestHandler: () => unknown,
): Http2SecureServer {
notImplemented("http2.createSecureServer");
return new Http2SecureServer();
}
export function connect(
authority: string | URL,
options: Record<string, unknown>,
callback: (session: ClientHttp2Session) => void,
): ClientHttp2Session {
debugHttp2(">>> http2.connect", options);
if (typeof options === "function") {
callback = options;
options = undefined;
}
options = { ...options };
if (typeof authority === "string") {
authority = new URL(authority);
}
const protocol = authority.protocol || options.protocol || "https:";
let port = 0;
if (authority.port !== "") {
port = Number(authority.port);
} else if (protocol === "http:") {
port = 80;
} else {
port = 443;
}
if (port == 0) {
throw new Error("Invalid port");
}
let host = "localhost";
if (authority.hostname) {
host = authority.hostname;
if (host[0] === "[") {
host = host.slice(1, -1);
}
} else if (authority.host) {
host = authority.host;
}
let url, socket;
if (typeof options.createConnection === "function") {
url = `http://${host}${port == 80 ? "" : (":" + port)}`;
socket = options.createConnection(host, options);
} else {
switch (protocol) {
case "http:":
url = `http://${host}${port == 80 ? "" : (":" + port)}`;
socket = netConnect({ port, host, ...options, pauseOnCreate: true });
break;
case "https:":
// TODO(bartlomieju): handle `initializeTLSOptions` here
url = `https://${host}${port == 443 ? "" : (":" + port)}`;
socket = tlsConnect(port, host, { manualStart: true });
break;
default:
throw new ERR_HTTP2_UNSUPPORTED_PROTOCOL(protocol);
}
}
// Pause so no "socket.read()" starts in the background that would
// prevent us from taking ownership of the socket in `ClientHttp2Session`
socket.pause();
const session = new ClientHttp2Session(socket, url, options);
session[kAuthority] = `${options.servername || host}:${port}`;
session[kProtocol] = protocol;
if (typeof callback === "function") {
session.once("connect", callback);
}
return session;
}
function socketOnError(error) {
const session = this[kSession];
if (session !== undefined) {
if (error.code === "ECONNRESET" && session[kState].goawayCode !== null) {
return session.destroy();
}
debugHttp2(">>>> socket error", error);
session.destroy(error);
}
}
function socketOnClose() {
const session = this[kSession];
if (session !== undefined) {
debugHttp2(">>>> socket closed");
const err = session.connecting ? new ERR_SOCKET_CLOSED() : null;
const state = session[kState];
state.streams.forEach((stream) => stream.close(constants.NGHTTP2_CANCEL));
state.pendingStreams.forEach((stream) =>
stream.close(constants.NGHTTP2_CANCEL)
);
session.close();
session[kMaybeDestroy](err);
}
}
export const constants = {
NGHTTP2_ERR_FRAME_SIZE_ERROR: -522,
NGHTTP2_NV_FLAG_NONE: 0,
NGHTTP2_NV_FLAG_NO_INDEX: 1,
NGHTTP2_SESSION_SERVER: 0,
NGHTTP2_SESSION_CLIENT: 1,
NGHTTP2_STREAM_STATE_IDLE: 1,
NGHTTP2_STREAM_STATE_OPEN: 2,
NGHTTP2_STREAM_STATE_RESERVED_LOCAL: 3,
NGHTTP2_STREAM_STATE_RESERVED_REMOTE: 4,
NGHTTP2_STREAM_STATE_HALF_CLOSED_LOCAL: 5,
NGHTTP2_STREAM_STATE_HALF_CLOSED_REMOTE: 6,
NGHTTP2_STREAM_STATE_CLOSED: 7,
NGHTTP2_FLAG_NONE: 0,
NGHTTP2_FLAG_END_STREAM: 1,
NGHTTP2_FLAG_END_HEADERS: 4,
NGHTTP2_FLAG_ACK: 1,
NGHTTP2_FLAG_PADDED: 8,
NGHTTP2_FLAG_PRIORITY: 32,
DEFAULT_SETTINGS_HEADER_TABLE_SIZE: 4096,
DEFAULT_SETTINGS_ENABLE_PUSH: 1,
DEFAULT_SETTINGS_MAX_CONCURRENT_STREAMS: 4294967295,
DEFAULT_SETTINGS_INITIAL_WINDOW_SIZE: 65535,
DEFAULT_SETTINGS_MAX_FRAME_SIZE: 16384,
DEFAULT_SETTINGS_MAX_HEADER_LIST_SIZE: 65535,
DEFAULT_SETTINGS_ENABLE_CONNECT_PROTOCOL: 0,
MAX_MAX_FRAME_SIZE: 16777215,
MIN_MAX_FRAME_SIZE: 16384,
MAX_INITIAL_WINDOW_SIZE: 2147483647,
NGHTTP2_SETTINGS_HEADER_TABLE_SIZE: 1,
NGHTTP2_SETTINGS_ENABLE_PUSH: 2,
NGHTTP2_SETTINGS_MAX_CONCURRENT_STREAMS: 3,
NGHTTP2_SETTINGS_INITIAL_WINDOW_SIZE: 4,
NGHTTP2_SETTINGS_MAX_FRAME_SIZE: 5,
NGHTTP2_SETTINGS_MAX_HEADER_LIST_SIZE: 6,
NGHTTP2_SETTINGS_ENABLE_CONNECT_PROTOCOL: 8,
PADDING_STRATEGY_NONE: 0,
PADDING_STRATEGY_ALIGNED: 1,
PADDING_STRATEGY_MAX: 2,
PADDING_STRATEGY_CALLBACK: 1,
NGHTTP2_NO_ERROR: 0,
NGHTTP2_PROTOCOL_ERROR: 1,
NGHTTP2_INTERNAL_ERROR: 2,
NGHTTP2_FLOW_CONTROL_ERROR: 3,
NGHTTP2_SETTINGS_TIMEOUT: 4,
NGHTTP2_STREAM_CLOSED: 5,
NGHTTP2_FRAME_SIZE_ERROR: 6,
NGHTTP2_REFUSED_STREAM: 7,
NGHTTP2_CANCEL: 8,
NGHTTP2_COMPRESSION_ERROR: 9,
NGHTTP2_CONNECT_ERROR: 10,
NGHTTP2_ENHANCE_YOUR_CALM: 11,
NGHTTP2_INADEQUATE_SECURITY: 12,
NGHTTP2_HTTP_1_1_REQUIRED: 13,
NGHTTP2_DEFAULT_WEIGHT: 16,
HTTP2_HEADER_STATUS: ":status",
HTTP2_HEADER_METHOD: ":method",
HTTP2_HEADER_AUTHORITY: ":authority",
HTTP2_HEADER_SCHEME: ":scheme",
HTTP2_HEADER_PATH: ":path",
HTTP2_HEADER_PROTOCOL: ":protocol",
HTTP2_HEADER_ACCEPT_ENCODING: "accept-encoding",
HTTP2_HEADER_ACCEPT_LANGUAGE: "accept-language",
HTTP2_HEADER_ACCEPT_RANGES: "accept-ranges",
HTTP2_HEADER_ACCEPT: "accept",
HTTP2_HEADER_ACCESS_CONTROL_ALLOW_CREDENTIALS:
"access-control-allow-credentials",
HTTP2_HEADER_ACCESS_CONTROL_ALLOW_HEADERS: "access-control-allow-headers",
HTTP2_HEADER_ACCESS_CONTROL_ALLOW_METHODS: "access-control-allow-methods",
HTTP2_HEADER_ACCESS_CONTROL_ALLOW_ORIGIN: "access-control-allow-origin",
HTTP2_HEADER_ACCESS_CONTROL_EXPOSE_HEADERS: "access-control-expose-headers",
HTTP2_HEADER_ACCESS_CONTROL_REQUEST_HEADERS: "access-control-request-headers",
HTTP2_HEADER_ACCESS_CONTROL_REQUEST_METHOD: "access-control-request-method",
HTTP2_HEADER_AGE: "age",
HTTP2_HEADER_AUTHORIZATION: "authorization",
HTTP2_HEADER_CACHE_CONTROL: "cache-control",
HTTP2_HEADER_CONNECTION: "connection",
HTTP2_HEADER_CONTENT_DISPOSITION: "content-disposition",
HTTP2_HEADER_CONTENT_ENCODING: "content-encoding",
HTTP2_HEADER_CONTENT_LENGTH: "content-length",
HTTP2_HEADER_CONTENT_TYPE: "content-type",
HTTP2_HEADER_COOKIE: "cookie",
HTTP2_HEADER_DATE: "date",
HTTP2_HEADER_ETAG: "etag",
HTTP2_HEADER_FORWARDED: "forwarded",
HTTP2_HEADER_HOST: "host",
HTTP2_HEADER_IF_MODIFIED_SINCE: "if-modified-since",
HTTP2_HEADER_IF_NONE_MATCH: "if-none-match",
HTTP2_HEADER_IF_RANGE: "if-range",
HTTP2_HEADER_LAST_MODIFIED: "last-modified",
HTTP2_HEADER_LINK: "link",
HTTP2_HEADER_LOCATION: "location",
HTTP2_HEADER_RANGE: "range",
HTTP2_HEADER_REFERER: "referer",
HTTP2_HEADER_SERVER: "server",
HTTP2_HEADER_SET_COOKIE: "set-cookie",
HTTP2_HEADER_STRICT_TRANSPORT_SECURITY: "strict-transport-security",
HTTP2_HEADER_TRANSFER_ENCODING: "transfer-encoding",
HTTP2_HEADER_TE: "te",
HTTP2_HEADER_UPGRADE_INSECURE_REQUESTS: "upgrade-insecure-requests",
HTTP2_HEADER_UPGRADE: "upgrade",
HTTP2_HEADER_USER_AGENT: "user-agent",
HTTP2_HEADER_VARY: "vary",
HTTP2_HEADER_X_CONTENT_TYPE_OPTIONS: "x-content-type-options",
HTTP2_HEADER_X_FRAME_OPTIONS: "x-frame-options",
HTTP2_HEADER_KEEP_ALIVE: "keep-alive",
HTTP2_HEADER_PROXY_CONNECTION: "proxy-connection",
HTTP2_HEADER_X_XSS_PROTECTION: "x-xss-protection",
HTTP2_HEADER_ALT_SVC: "alt-svc",
HTTP2_HEADER_CONTENT_SECURITY_POLICY: "content-security-policy",
HTTP2_HEADER_EARLY_DATA: "early-data",
HTTP2_HEADER_EXPECT_CT: "expect-ct",
HTTP2_HEADER_ORIGIN: "origin",
HTTP2_HEADER_PURPOSE: "purpose",
HTTP2_HEADER_TIMING_ALLOW_ORIGIN: "timing-allow-origin",
HTTP2_HEADER_X_FORWARDED_FOR: "x-forwarded-for",
HTTP2_HEADER_PRIORITY: "priority",
HTTP2_HEADER_ACCEPT_CHARSET: "accept-charset",
HTTP2_HEADER_ACCESS_CONTROL_MAX_AGE: "access-control-max-age",
HTTP2_HEADER_ALLOW: "allow",
HTTP2_HEADER_CONTENT_LANGUAGE: "content-language",
HTTP2_HEADER_CONTENT_LOCATION: "content-location",
HTTP2_HEADER_CONTENT_MD5: "content-md5",
HTTP2_HEADER_CONTENT_RANGE: "content-range",
HTTP2_HEADER_DNT: "dnt",
HTTP2_HEADER_EXPECT: "expect",
HTTP2_HEADER_EXPIRES: "expires",
HTTP2_HEADER_FROM: "from",
HTTP2_HEADER_IF_MATCH: "if-match",
HTTP2_HEADER_IF_UNMODIFIED_SINCE: "if-unmodified-since",
HTTP2_HEADER_MAX_FORWARDS: "max-forwards",
HTTP2_HEADER_PREFER: "prefer",
HTTP2_HEADER_PROXY_AUTHENTICATE: "proxy-authenticate",
HTTP2_HEADER_PROXY_AUTHORIZATION: "proxy-authorization",
HTTP2_HEADER_REFRESH: "refresh",
HTTP2_HEADER_RETRY_AFTER: "retry-after",
HTTP2_HEADER_TRAILER: "trailer",
HTTP2_HEADER_TK: "tk",
HTTP2_HEADER_VIA: "via",
HTTP2_HEADER_WARNING: "warning",
HTTP2_HEADER_WWW_AUTHENTICATE: "www-authenticate",
HTTP2_HEADER_HTTP2_SETTINGS: "http2-settings",
HTTP2_METHOD_ACL: "ACL",
HTTP2_METHOD_BASELINE_CONTROL: "BASELINE-CONTROL",
HTTP2_METHOD_BIND: "BIND",
HTTP2_METHOD_CHECKIN: "CHECKIN",
HTTP2_METHOD_CHECKOUT: "CHECKOUT",
HTTP2_METHOD_CONNECT: "CONNECT",
HTTP2_METHOD_COPY: "COPY",
HTTP2_METHOD_DELETE: "DELETE",
HTTP2_METHOD_GET: "GET",
HTTP2_METHOD_HEAD: "HEAD",
HTTP2_METHOD_LABEL: "LABEL",
HTTP2_METHOD_LINK: "LINK",
HTTP2_METHOD_LOCK: "LOCK",
HTTP2_METHOD_MERGE: "MERGE",
HTTP2_METHOD_MKACTIVITY: "MKACTIVITY",
HTTP2_METHOD_MKCALENDAR: "MKCALENDAR",
HTTP2_METHOD_MKCOL: "MKCOL",
HTTP2_METHOD_MKREDIRECTREF: "MKREDIRECTREF",
HTTP2_METHOD_MKWORKSPACE: "MKWORKSPACE",
HTTP2_METHOD_MOVE: "MOVE",
HTTP2_METHOD_OPTIONS: "OPTIONS",
HTTP2_METHOD_ORDERPATCH: "ORDERPATCH",
HTTP2_METHOD_PATCH: "PATCH",
HTTP2_METHOD_POST: "POST",
HTTP2_METHOD_PRI: "PRI",
HTTP2_METHOD_PROPFIND: "PROPFIND",
HTTP2_METHOD_PROPPATCH: "PROPPATCH",
HTTP2_METHOD_PUT: "PUT",
HTTP2_METHOD_REBIND: "REBIND",
HTTP2_METHOD_REPORT: "REPORT",
HTTP2_METHOD_SEARCH: "SEARCH",
HTTP2_METHOD_TRACE: "TRACE",
HTTP2_METHOD_UNBIND: "UNBIND",
HTTP2_METHOD_UNCHECKOUT: "UNCHECKOUT",
HTTP2_METHOD_UNLINK: "UNLINK",
HTTP2_METHOD_UNLOCK: "UNLOCK",
HTTP2_METHOD_UPDATE: "UPDATE",
HTTP2_METHOD_UPDATEREDIRECTREF: "UPDATEREDIRECTREF",
HTTP2_METHOD_VERSION_CONTROL: "VERSION-CONTROL",
HTTP_STATUS_CONTINUE: 100,
HTTP_STATUS_SWITCHING_PROTOCOLS: 101,
HTTP_STATUS_PROCESSING: 102,
HTTP_STATUS_EARLY_HINTS: 103,
HTTP_STATUS_OK: 200,
HTTP_STATUS_CREATED: 201,
HTTP_STATUS_ACCEPTED: 202,
HTTP_STATUS_NON_AUTHORITATIVE_INFORMATION: 203,
HTTP_STATUS_NO_CONTENT: 204,
HTTP_STATUS_RESET_CONTENT: 205,
HTTP_STATUS_PARTIAL_CONTENT: 206,
HTTP_STATUS_MULTI_STATUS: 207,
HTTP_STATUS_ALREADY_REPORTED: 208,
HTTP_STATUS_IM_USED: 226,
HTTP_STATUS_MULTIPLE_CHOICES: 300,
HTTP_STATUS_MOVED_PERMANENTLY: 301,
HTTP_STATUS_FOUND: 302,
HTTP_STATUS_SEE_OTHER: 303,
HTTP_STATUS_NOT_MODIFIED: 304,
HTTP_STATUS_USE_PROXY: 305,
HTTP_STATUS_TEMPORARY_REDIRECT: 307,
HTTP_STATUS_PERMANENT_REDIRECT: 308,
HTTP_STATUS_BAD_REQUEST: 400,
HTTP_STATUS_UNAUTHORIZED: 401,
HTTP_STATUS_PAYMENT_REQUIRED: 402,
HTTP_STATUS_FORBIDDEN: 403,
HTTP_STATUS_NOT_FOUND: 404,
HTTP_STATUS_METHOD_NOT_ALLOWED: 405,
HTTP_STATUS_NOT_ACCEPTABLE: 406,
HTTP_STATUS_PROXY_AUTHENTICATION_REQUIRED: 407,
HTTP_STATUS_REQUEST_TIMEOUT: 408,
HTTP_STATUS_CONFLICT: 409,
HTTP_STATUS_GONE: 410,
HTTP_STATUS_LENGTH_REQUIRED: 411,
HTTP_STATUS_PRECONDITION_FAILED: 412,
HTTP_STATUS_PAYLOAD_TOO_LARGE: 413,
HTTP_STATUS_URI_TOO_LONG: 414,
HTTP_STATUS_UNSUPPORTED_MEDIA_TYPE: 415,
HTTP_STATUS_RANGE_NOT_SATISFIABLE: 416,
HTTP_STATUS_EXPECTATION_FAILED: 417,
HTTP_STATUS_TEAPOT: 418,
HTTP_STATUS_MISDIRECTED_REQUEST: 421,
HTTP_STATUS_UNPROCESSABLE_ENTITY: 422,
HTTP_STATUS_LOCKED: 423,
HTTP_STATUS_FAILED_DEPENDENCY: 424,
HTTP_STATUS_TOO_EARLY: 425,
HTTP_STATUS_UPGRADE_REQUIRED: 426,
HTTP_STATUS_PRECONDITION_REQUIRED: 428,
HTTP_STATUS_TOO_MANY_REQUESTS: 429,
HTTP_STATUS_REQUEST_HEADER_FIELDS_TOO_LARGE: 431,
HTTP_STATUS_UNAVAILABLE_FOR_LEGAL_REASONS: 451,
HTTP_STATUS_INTERNAL_SERVER_ERROR: 500,
HTTP_STATUS_NOT_IMPLEMENTED: 501,
HTTP_STATUS_BAD_GATEWAY: 502,
HTTP_STATUS_SERVICE_UNAVAILABLE: 503,
HTTP_STATUS_GATEWAY_TIMEOUT: 504,
HTTP_STATUS_HTTP_VERSION_NOT_SUPPORTED: 505,
HTTP_STATUS_VARIANT_ALSO_NEGOTIATES: 506,
HTTP_STATUS_INSUFFICIENT_STORAGE: 507,
HTTP_STATUS_LOOP_DETECTED: 508,
HTTP_STATUS_BANDWIDTH_LIMIT_EXCEEDED: 509,
HTTP_STATUS_NOT_EXTENDED: 510,
HTTP_STATUS_NETWORK_AUTHENTICATION_REQUIRED: 511,
};
// const kSingleValueHeaders = new Set([
// constants.HTTP2_HEADER_STATUS,
// constants.HTTP2_HEADER_METHOD,
// constants.HTTP2_HEADER_AUTHORITY,
// constants.HTTP2_HEADER_SCHEME,
// constants.HTTP2_HEADER_PATH,
// constants.HTTP2_HEADER_PROTOCOL,
// constants.HTTP2_HEADER_ACCESS_CONTROL_ALLOW_CREDENTIALS,
// constants.HTTP2_HEADER_ACCESS_CONTROL_MAX_AGE,
// constants.HTTP2_HEADER_ACCESS_CONTROL_REQUEST_METHOD,
// constants.HTTP2_HEADER_AGE,
// constants.HTTP2_HEADER_AUTHORIZATION,
// constants.HTTP2_HEADER_CONTENT_ENCODING,
// constants.HTTP2_HEADER_CONTENT_LANGUAGE,
// constants.HTTP2_HEADER_CONTENT_LENGTH,
// constants.HTTP2_HEADER_CONTENT_LOCATION,
// constants.HTTP2_HEADER_CONTENT_MD5,
// constants.HTTP2_HEADER_CONTENT_RANGE,
// constants.HTTP2_HEADER_CONTENT_TYPE,
// constants.HTTP2_HEADER_DATE,
// constants.HTTP2_HEADER_DNT,
// constants.HTTP2_HEADER_ETAG,
// constants.HTTP2_HEADER_EXPIRES,
// constants.HTTP2_HEADER_FROM,
// constants.HTTP2_HEADER_HOST,
// constants.HTTP2_HEADER_IF_MATCH,
// constants.HTTP2_HEADER_IF_MODIFIED_SINCE,
// constants.HTTP2_HEADER_IF_NONE_MATCH,
// constants.HTTP2_HEADER_IF_RANGE,
// constants.HTTP2_HEADER_IF_UNMODIFIED_SINCE,
// constants.HTTP2_HEADER_LAST_MODIFIED,
// constants.HTTP2_HEADER_LOCATION,
// constants.HTTP2_HEADER_MAX_FORWARDS,
// constants.HTTP2_HEADER_PROXY_AUTHORIZATION,
// constants.HTTP2_HEADER_RANGE,
// constants.HTTP2_HEADER_REFERER,
// constants.HTTP2_HEADER_RETRY_AFTER,
// constants.HTTP2_HEADER_TK,
// constants.HTTP2_HEADER_UPGRADE_INSECURE_REQUESTS,
// constants.HTTP2_HEADER_USER_AGENT,
// constants.HTTP2_HEADER_X_CONTENT_TYPE_OPTIONS,
// ]);
export function getDefaultSettings(): Record<string, unknown> {
notImplemented("http2.getDefaultSettings");
return {};
}
export function getPackedSettings(_settings: Record<string, unknown>): Buffer {
notImplemented("http2.getPackedSettings");
return {};
}
export function getUnpackedSettings(
_buffer: Buffer | TypedArray,
): Record<string, unknown> {
notImplemented("http2.getUnpackedSettings");
return {};
}
export const sensitiveHeaders = Symbol("nodejs.http2.sensitiveHeaders");
export class Http2ServerRequest {
constructor() {
}
get aborted(): boolean {
notImplemented("Http2ServerRequest.aborted");
return false;
}
get authority(): string {
notImplemented("Http2ServerRequest.authority");
return "";
}
get complete(): boolean {
notImplemented("Http2ServerRequest.complete");
return false;
}
get connection(): Socket /*| TlsSocket*/ {
notImplemented("Http2ServerRequest.connection");
return {};
}
destroy(_error: Error) {
notImplemented("Http2ServerRequest.destroy");
}
get headers(): Record<string, unknown> {
notImplemented("Http2ServerRequest.headers");
return {};
}
get httpVersion(): string {
notImplemented("Http2ServerRequest.httpVersion");
return "";
}
get method(): string {
notImplemented("Http2ServerRequest.method");
return "";
}
get rawHeaders(): string[] {
notImplemented("Http2ServerRequest.rawHeaders");
return [];
}
get rawTrailers(): string[] {
notImplemented("Http2ServerRequest.rawTrailers");
return [];
}
get scheme(): string {
notImplemented("Http2ServerRequest.scheme");
return "";
}
setTimeout(msecs: number, callback?: () => unknown) {
this.stream.setTimeout(callback, msecs);
}
get socket(): Socket /*| TlsSocket*/ {
notImplemented("Http2ServerRequest.socket");
return {};
}
get stream(): Http2Stream {
notImplemented("Http2ServerRequest.stream");
return new Http2Stream();
}
get trailers(): Record<string, unknown> {
notImplemented("Http2ServerRequest.trailers");
return {};
}
get url(): string {
notImplemented("Http2ServerRequest.url");
return "";
}
}
export class Http2ServerResponse {
constructor() {
}
addTrailers(_headers: Record<string, unknown>) {
notImplemented("Http2ServerResponse.addTrailers");
}
get connection(): Socket /*| TlsSocket*/ {
notImplemented("Http2ServerResponse.connection");
return {};
}
createPushResponse(
_headers: Record<string, unknown>,
_callback: () => unknown,
) {
notImplemented("Http2ServerResponse.createPushResponse");
}
end(
_data: string | Buffer | Uint8Array,
_encoding: string,
_callback: () => unknown,
) {
notImplemented("Http2ServerResponse.end");
}
get finished(): boolean {
notImplemented("Http2ServerResponse.finished");
return false;
}
getHeader(_name: string): string {
notImplemented("Http2ServerResponse.getHeader");
return "";
}
getHeaderNames(): string[] {
notImplemented("Http2ServerResponse.getHeaderNames");
return [];
}
getHeaders(): Record<string, unknown> {
notImplemented("Http2ServerResponse.getHeaders");
return {};
}
hasHeader(_name: string) {
notImplemented("Http2ServerResponse.hasHeader");
}
get headersSent(): boolean {
notImplemented("Http2ServerResponse.headersSent");
return false;
}
removeHeader(_name: string) {
notImplemented("Http2ServerResponse.removeHeader");
}
get req(): Http2ServerRequest {
notImplemented("Http2ServerResponse.req");
return new Http2ServerRequest();
}
get sendDate(): boolean {
notImplemented("Http2ServerResponse.sendDate");
return false;
}
setHeader(_name: string, _value: string | string[]) {
notImplemented("Http2ServerResponse.setHeader");
}
setTimeout(msecs: number, callback?: () => unknown) {
this.stream.setTimeout(msecs, callback);
}
get socket(): Socket /*| TlsSocket*/ {
notImplemented("Http2ServerResponse.socket");
return {};
}
get statusCode(): number {
notImplemented("Http2ServerResponse.statusCode");
return 0;
}
get statusMessage(): string {
notImplemented("Http2ServerResponse.statusMessage");
return "";
}
get stream(): Http2Stream {
notImplemented("Http2ServerResponse.stream");
return new Http2Stream();
}
get writableEnded(): boolean {
notImplemented("Http2ServerResponse.writableEnded");
return false;
}
write(
_chunk: string | Buffer | Uint8Array,
_encoding: string,
_callback: () => unknown,
) {
notImplemented("Http2ServerResponse.write");
return this.write;
}
writeContinue() {
notImplemented("Http2ServerResponse.writeContinue");
}
writeEarlyHints(_hints: Record<string, unknown>) {
notImplemented("Http2ServerResponse.writeEarlyHints");
}
writeHead(
_statusCode: number,
_statusMessage: string,
_headers: Record<string, unknown>,
) {
notImplemented("Http2ServerResponse.writeHead");
}
}
export default {
createServer,
createSecureServer,
connect,
constants,
getDefaultSettings,
getPackedSettings,
getUnpackedSettings,
sensitiveHeaders,
Http2ServerRequest,
Http2ServerResponse,
};