mirror of
https://github.com/oven-sh/bun
synced 2026-02-04 07:58:54 +00:00
Compare commits
62 Commits
dylan/pyth
...
nektro-pat
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ec67c571c1 | ||
|
|
f7c1f61092 | ||
|
|
82bd167124 | ||
|
|
9f0f413514 | ||
|
|
24b53d4761 | ||
|
|
9e35139d48 | ||
|
|
16ebb9ff4b | ||
|
|
37eb2e109a | ||
|
|
efa796686c | ||
|
|
fd9661444e | ||
|
|
91fd7b8539 | ||
|
|
f82c3d2647 | ||
|
|
8ba4ecc645 | ||
|
|
e87f28b8ff | ||
|
|
80444cbe5f | ||
|
|
b5005bacce | ||
|
|
19b35561fd | ||
|
|
7ccbe81f55 | ||
|
|
851b4c8946 | ||
|
|
a45ec96a46 | ||
|
|
0003e28e85 | ||
|
|
5174f56dde | ||
|
|
e3ea1b50a4 | ||
|
|
25f06a32b1 | ||
|
|
6fb772a295 | ||
|
|
ef74316ca9 | ||
|
|
9f78fd7dd6 | ||
|
|
90f45180fb | ||
|
|
94dd5d5bdb | ||
|
|
4898e4ef0c | ||
|
|
4f9ad23fa5 | ||
|
|
b0fc59a836 | ||
|
|
16b260e3f7 | ||
|
|
81489d39cb | ||
|
|
3ec8e702c6 | ||
|
|
ef89084b44 | ||
|
|
78fef11e64 | ||
|
|
c573f05a47 | ||
|
|
dc8fd146ed | ||
|
|
160d2e2b2b | ||
|
|
e8bc2623ec | ||
|
|
e5a0f0386c | ||
|
|
096a711247 | ||
|
|
3d4dc08905 | ||
|
|
85c2652fa3 | ||
|
|
5c8cfa44f3 | ||
|
|
3ff6af3236 | ||
|
|
c23d8a9d61 | ||
|
|
5caf27484d | ||
|
|
8750884853 | ||
|
|
c1453a664d | ||
|
|
6c8a9d3da4 | ||
|
|
3907641cfd | ||
|
|
5ab767e993 | ||
|
|
87878d4c0a | ||
|
|
95064ceb7b | ||
|
|
e4a4b9179e | ||
|
|
2c06cfd119 | ||
|
|
849db19655 | ||
|
|
0447c4743b | ||
|
|
c5deba01d4 | ||
|
|
655cc58d90 |
@@ -615,7 +615,7 @@ function getTestBunStep(platform, options, testOptions = {}) {
|
||||
retry: getRetry(),
|
||||
cancel_on_build_failing: isMergeQueue(),
|
||||
parallelism: unifiedTests ? undefined : os === "darwin" ? 2 : 10,
|
||||
timeout_in_minutes: profile === "asan" || os === "windows" ? 45 : 30,
|
||||
timeout_in_minutes: 120,
|
||||
env: {
|
||||
ASAN_OPTIONS: "allow_user_segv_handler=1:disable_coredump=0:detect_leaks=0",
|
||||
},
|
||||
|
||||
@@ -60,6 +60,7 @@
|
||||
"lint:fix": "oxlint --config oxlint.json --fix",
|
||||
"test": "node scripts/runner.node.mjs --exec-path ./build/debug/bun-debug",
|
||||
"test:release": "node scripts/runner.node.mjs --exec-path ./build/release/bun",
|
||||
"testleak": "BUN_DESTRUCT_VM_ON_EXIT=1 ASAN_OPTIONS=detect_leaks=1 LSAN_OPTIONS=malloc_context_size=100:print_suppressions=1:suppressions=$npm_config_local_prefix/test/leaksan.supp ./build/debug/bun-debug",
|
||||
"banned": "bun test test/internal/ban-words.test.ts",
|
||||
"glob-sources": "bun scripts/glob-sources.mjs",
|
||||
"zig": "vendor/zig/zig.exe",
|
||||
|
||||
32
packages/bun-types/overrides.d.ts
vendored
32
packages/bun-types/overrides.d.ts
vendored
@@ -1,5 +1,7 @@
|
||||
export {};
|
||||
|
||||
type TODO = any;
|
||||
|
||||
declare module "stream/web" {
|
||||
interface ReadableStream {
|
||||
/**
|
||||
@@ -261,7 +263,7 @@ declare global {
|
||||
"FLUSH",
|
||||
"QUERY",
|
||||
];
|
||||
HTTPParser: unknown;
|
||||
HTTPParser: HTTPParserConstructor;
|
||||
ConnectionsList: unknown;
|
||||
};
|
||||
binding(m: string): object;
|
||||
@@ -270,6 +272,34 @@ declare global {
|
||||
interface ProcessVersions extends Dict<string> {
|
||||
bun: string;
|
||||
}
|
||||
|
||||
interface HTTPParserConstructor {
|
||||
new (): TODO;
|
||||
|
||||
REQUEST: 1;
|
||||
RESPONSE: 2;
|
||||
|
||||
kOnMessageBegin: 0;
|
||||
kOnHeaders: 1;
|
||||
kOnHeadersComplete: 2;
|
||||
kOnBody: 3;
|
||||
kOnMessageComplete: 4;
|
||||
kOnExecute: 5;
|
||||
kOnTimeout: 6;
|
||||
|
||||
kLenientNone: 0;
|
||||
kLenientHeaders: 1;
|
||||
kLenientChunkedLength: 2;
|
||||
kLenientKeepAlive: 4;
|
||||
kLenientTransferEncoding: 8;
|
||||
kLenientVersion: 16;
|
||||
kLenientDataAfterClose: 32;
|
||||
kLenientOptionalLFAfterCR: 64;
|
||||
kLenientOptionalCRLFAfterChunk: 128;
|
||||
kLenientOptionalCRBeforeLF: 256;
|
||||
kLenientSpacesAfterChunkSize: 512;
|
||||
kLenientAll: 1023;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -303,6 +303,8 @@ public:
|
||||
auto context = (struct us_socket_context_t *)this->httpContext;
|
||||
struct us_socket_t *s = context->head_sockets;
|
||||
while (s) {
|
||||
HttpResponseData<SSL> *httpResponseData = HttpResponse<SSL>::getHttpResponseDataS(s);
|
||||
httpResponseData->shouldCloseOnceIdle = true;
|
||||
// no matter the type of socket will always contain the AsyncSocketData
|
||||
auto *data = ((AsyncSocket<SSL> *) s)->getAsyncSocketData();
|
||||
struct us_socket_t *next = s->next;
|
||||
|
||||
@@ -195,7 +195,7 @@ private:
|
||||
|
||||
/* Call filter */
|
||||
HttpContextData<SSL> *httpContextData = getSocketContextDataS(s);
|
||||
|
||||
|
||||
if(httpResponseData && httpResponseData->isConnectRequest) {
|
||||
if (httpResponseData->socketData && httpContextData->onSocketData) {
|
||||
httpContextData->onSocketData(httpResponseData->socketData, SSL, s, "", 0, true);
|
||||
@@ -203,7 +203,7 @@ private:
|
||||
if(httpResponseData->inStream) {
|
||||
httpResponseData->inStream(reinterpret_cast<HttpResponse<SSL> *>(s), "", 0, true, httpResponseData->userData);
|
||||
httpResponseData->inStream = nullptr;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -253,7 +253,7 @@ private:
|
||||
/* Mark that we are inside the parser now */
|
||||
httpContextData->flags.isParsingHttp = true;
|
||||
httpResponseData->isIdle = false;
|
||||
|
||||
|
||||
// clients need to know the cursor after http parse, not servers!
|
||||
// how far did we read then? we need to know to continue with websocket parsing data? or?
|
||||
|
||||
@@ -266,7 +266,7 @@ private:
|
||||
|
||||
auto result = httpResponseData->consumePostPadded(httpContextData->maxHeaderSize, httpResponseData->isConnectRequest, httpContextData->flags.requireHostHeader,httpContextData->flags.useStrictMethodValidation, data, (unsigned int) length, s, proxyParser, [httpContextData](void *s, HttpRequest *httpRequest) -> void * {
|
||||
|
||||
|
||||
|
||||
/* For every request we reset the timeout and hang until user makes action */
|
||||
/* Warning: if we are in shutdown state, resetting the timer is a security issue! */
|
||||
us_socket_timeout(SSL, (us_socket_t *) s, 0);
|
||||
@@ -391,6 +391,7 @@ private:
|
||||
|
||||
/* Mark that we are no longer parsing Http */
|
||||
httpContextData->flags.isParsingHttp = false;
|
||||
|
||||
/* If we got fullptr that means the parser wants us to close the socket from error (same as calling the errorHandler) */
|
||||
if (httpErrorStatusCode) {
|
||||
if(httpContextData->onClientError) {
|
||||
@@ -425,9 +426,20 @@ private:
|
||||
/* We need to force close after sending FIN since we want to hinder
|
||||
* clients from keeping to send their huge data */
|
||||
((AsyncSocket<SSL> *) s)->close();
|
||||
return (us_socket_t *) returnedData;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* Check if we should gracefully close the socket after parsing HTTP */
|
||||
if (httpResponseData->shouldCloseOnceIdle && !(httpResponseData->state & HttpResponseData<SSL>::HTTP_RESPONSE_PENDING)) {
|
||||
/* Gracefully close the socket by shutting down and then closing */
|
||||
if (!us_socket_is_closed(SSL, s) && !us_socket_is_shut_down(SSL, s)) {
|
||||
us_socket_shutdown(SSL, s);
|
||||
us_socket_shutdown_read(SSL, s);
|
||||
}
|
||||
}
|
||||
|
||||
return (us_socket_t *) returnedData;
|
||||
}
|
||||
|
||||
@@ -466,7 +478,7 @@ private:
|
||||
us_socket_context_on_writable(SSL, getSocketContext(), [](us_socket_t *s) {
|
||||
auto *asyncSocket = reinterpret_cast<AsyncSocket<SSL> *>(s);
|
||||
auto *httpResponseData = reinterpret_cast<HttpResponseData<SSL> *>(asyncSocket->getAsyncSocketData());
|
||||
|
||||
|
||||
/* Attempt to drain the socket buffer before triggering onWritable callback */
|
||||
size_t bufferedAmount = asyncSocket->getBufferedAmount();
|
||||
if (bufferedAmount > 0) {
|
||||
@@ -537,7 +549,7 @@ private:
|
||||
us_socket_context_on_end(SSL, getSocketContext(), [](us_socket_t *s) {
|
||||
auto *asyncSocket = reinterpret_cast<AsyncSocket<SSL> *>(s);
|
||||
asyncSocket->uncorkWithoutSending();
|
||||
|
||||
|
||||
/* We do not care for half closed sockets */
|
||||
return asyncSocket->close();
|
||||
});
|
||||
|
||||
@@ -38,6 +38,11 @@
|
||||
#include "ProxyParser.h"
|
||||
#include "QueryParser.h"
|
||||
#include "HttpErrors.h"
|
||||
|
||||
#if defined(_WIN32)
|
||||
#define strncasecmp _strnicmp
|
||||
#endif
|
||||
|
||||
extern "C" size_t BUN_DEFAULT_MAX_HTTP_HEADER_SIZE;
|
||||
extern "C" int16_t Bun__HTTPMethod__from(const char *str, size_t len);
|
||||
|
||||
@@ -232,11 +237,11 @@ namespace uWS
|
||||
TransferEncoding getTransferEncoding()
|
||||
{
|
||||
TransferEncoding te;
|
||||
|
||||
|
||||
if (!bf.mightHave("transfer-encoding")) {
|
||||
return te;
|
||||
}
|
||||
|
||||
|
||||
for (Header *h = headers; (++h)->key.length();) {
|
||||
if (h->key.length() == 17 && !strncmp(h->key.data(), "transfer-encoding", 17)) {
|
||||
// Parse comma-separated values, ensuring "chunked" is last if present
|
||||
@@ -244,33 +249,33 @@ namespace uWS
|
||||
size_t pos = 0;
|
||||
size_t lastTokenStart = 0;
|
||||
size_t lastTokenLen = 0;
|
||||
|
||||
|
||||
while (pos < value.length()) {
|
||||
// Skip leading whitespace
|
||||
while (pos < value.length() && (value[pos] == ' ' || value[pos] == '\t')) {
|
||||
pos++;
|
||||
}
|
||||
|
||||
|
||||
// Remember start of this token
|
||||
size_t tokenStart = pos;
|
||||
|
||||
|
||||
// Find end of token (until comma or end)
|
||||
while (pos < value.length() && value[pos] != ',') {
|
||||
pos++;
|
||||
}
|
||||
|
||||
|
||||
// Trim trailing whitespace from token
|
||||
size_t tokenEnd = pos;
|
||||
while (tokenEnd > tokenStart && (value[tokenEnd - 1] == ' ' || value[tokenEnd - 1] == '\t')) {
|
||||
tokenEnd--;
|
||||
}
|
||||
|
||||
|
||||
size_t tokenLen = tokenEnd - tokenStart;
|
||||
if (tokenLen > 0) {
|
||||
lastTokenStart = tokenStart;
|
||||
lastTokenLen = tokenLen;
|
||||
}
|
||||
|
||||
|
||||
// Move past comma if present
|
||||
if (pos < value.length() && value[pos] == ',') {
|
||||
pos++;
|
||||
@@ -283,12 +288,12 @@ namespace uWS
|
||||
}
|
||||
|
||||
te.has = lastTokenLen > 0;
|
||||
|
||||
|
||||
// Check if the last token is "chunked"
|
||||
if (lastTokenLen == 7 && !strncmp(value.data() + lastTokenStart, "chunked", 7)) [[likely]] {
|
||||
if (lastTokenLen == 7 && strncasecmp(value.data() + lastTokenStart, "chunked", 7) == 0) [[likely]] {
|
||||
te.chunked = true;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -852,7 +857,7 @@ namespace uWS
|
||||
* ought to be handled as an error. */
|
||||
const std::string_view contentLengthString = req->getHeader("content-length");
|
||||
const auto contentLengthStringLen = contentLengthString.length();
|
||||
|
||||
|
||||
/* Check Transfer-Encoding header validity and conflicts */
|
||||
HttpRequest::TransferEncoding transferEncoding = req->getTransferEncoding();
|
||||
|
||||
@@ -962,7 +967,7 @@ public:
|
||||
data = (char *) dataToConsume.data();
|
||||
length = (unsigned int) dataToConsume.length();
|
||||
} else {
|
||||
|
||||
|
||||
// this is exactly the same as below!
|
||||
// todo: refactor this
|
||||
if (remainingStreamingBytes >= length) {
|
||||
|
||||
@@ -125,6 +125,10 @@ public:
|
||||
}
|
||||
}
|
||||
|
||||
if (httpResponseData->state & HttpResponseData<SSL>::HTTP_WROTE_TRANSFER_ENCODING_HEADER) {
|
||||
allowContentLength = false;
|
||||
}
|
||||
|
||||
/* if write was called and there was previously no Content-Length header set */
|
||||
if (httpResponseData->state & HttpResponseData<SSL>::HTTP_WRITE_CALLED && !(httpResponseData->state & HttpResponseData<SSL>::HTTP_WROTE_CONTENT_LENGTH_HEADER) && !httpResponseData->fromAncientRequest) {
|
||||
|
||||
|
||||
@@ -87,6 +87,7 @@ struct HttpResponseData : AsyncSocketData<SSL>, HttpParser {
|
||||
HTTP_CONNECTION_CLOSE = 16, // used
|
||||
HTTP_WROTE_CONTENT_LENGTH_HEADER = 32, // used
|
||||
HTTP_WROTE_DATE_HEADER = 64, // used
|
||||
HTTP_WROTE_TRANSFER_ENCODING_HEADER = 128, // used
|
||||
};
|
||||
|
||||
/* Shared context pointer */
|
||||
@@ -109,6 +110,7 @@ struct HttpResponseData : AsyncSocketData<SSL>, HttpParser {
|
||||
uint8_t idleTimeout = 10; // default HTTP_TIMEOUT 10 seconds
|
||||
bool fromAncientRequest = false;
|
||||
bool isConnectRequest = false;
|
||||
bool shouldCloseOnceIdle = false;
|
||||
|
||||
#ifdef UWS_WITH_PROXY
|
||||
ProxyParser proxyParser;
|
||||
|
||||
@@ -81,6 +81,7 @@ function getNodeParallelTestTimeout(testPath) {
|
||||
return 90_000;
|
||||
}
|
||||
if (!isCI) return 60_000; // everything slower in debug mode
|
||||
if (options["step"]?.includes("-asan-")) return 60_000;
|
||||
return 20_000;
|
||||
}
|
||||
|
||||
@@ -202,12 +203,12 @@ if (isBuildkite) {
|
||||
const doc = await res.json();
|
||||
console.log(`-> page ${i}, found ${doc.length} items`);
|
||||
if (doc.length === 0) break;
|
||||
if (doc.length < per_page) break;
|
||||
for (const { filename, status } of doc) {
|
||||
prFileCount += 1;
|
||||
if (status !== "added") continue;
|
||||
newFiles.push(filename);
|
||||
}
|
||||
if (doc.length < per_page) break;
|
||||
}
|
||||
console.log(`- PR ${process.env.BUILDKITE_PULL_REQUEST}, ${prFileCount} files, ${newFiles.length} new files`);
|
||||
} catch (e) {
|
||||
@@ -1579,8 +1580,7 @@ function isJavaScriptTest(path) {
|
||||
* @returns {boolean}
|
||||
*/
|
||||
function isNodeTest(path) {
|
||||
// Do not run node tests on macOS x64 in CI
|
||||
// TODO: Unclear why we decided to do this?
|
||||
// Do not run node tests on macOS x64 in CI, those machines are slow.
|
||||
if (isCI && isMacOS && isX64) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -7,6 +7,11 @@ const char* __asan_default_options(void)
|
||||
// which breaks some JSC classes that have to be on the stack:
|
||||
// ASSERTION FAILED: Thread::currentSingleton().stack().contains(this)
|
||||
// cache/webkit-eda8b0fb4fb1aa23/include/JavaScriptCore/JSGlobalObjectInlines.h(63) : JSC::JSGlobalObject::GlobalPropertyInfo::GlobalPropertyInfo(const Identifier &, JSValue, unsigned int)
|
||||
return "detect_stack_use_after_return=0";
|
||||
|
||||
// > https://clang.llvm.org/docs/AddressSanitizer.html#memory-leak-detection
|
||||
// > The leak detection is turned on by default on Linux, and can be enabled using ASAN_OPTIONS=detect_leaks=1 on macOS.
|
||||
// we want it to always be opt-in
|
||||
|
||||
return "detect_stack_use_after_return=0:detect_leaks=0";
|
||||
}
|
||||
#endif
|
||||
|
||||
@@ -687,7 +687,7 @@ pub fn NewSocket(comptime ssl: bool) type {
|
||||
}
|
||||
|
||||
pub fn getListener(this: *This, _: *jsc.JSGlobalObject) JSValue {
|
||||
const handlers = this.getHandlers();
|
||||
const handlers = this.handlers orelse return .js_undefined;
|
||||
|
||||
if (!handlers.is_server or this.socket.isDetached()) {
|
||||
return .js_undefined;
|
||||
|
||||
@@ -566,12 +566,19 @@ pub fn NewServer(protocol_enum: enum { http, https }, development_kind: enum { d
|
||||
inspector_server_id: jsc.Debugger.DebuggerId = .init(0),
|
||||
|
||||
pub const doStop = host_fn.wrapInstanceMethod(ThisServer, "stopFromJS", false);
|
||||
|
||||
pub const dispose = host_fn.wrapInstanceMethod(ThisServer, "disposeFromJS", false);
|
||||
|
||||
pub const doUpgrade = host_fn.wrapInstanceMethod(ThisServer, "onUpgrade", false);
|
||||
|
||||
pub const doPublish = host_fn.wrapInstanceMethod(ThisServer, "publish", false);
|
||||
|
||||
pub const doReload = onReload;
|
||||
|
||||
pub const doFetch = onFetch;
|
||||
|
||||
pub const doRequestIP = host_fn.wrapInstanceMethod(ThisServer, "requestIP", false);
|
||||
|
||||
pub const doTimeout = timeout;
|
||||
|
||||
pub const UserRoute = struct {
|
||||
|
||||
@@ -2375,6 +2375,26 @@ JSC_DEFINE_HOST_FUNCTION(Bun::jsFunctionMakeErrorWithCode, (JSC::JSGlobalObject
|
||||
return JSC::JSValue::encode(createError(globalObject, ErrorCode::ERR_ZSTD_INVALID_PARAM, message));
|
||||
}
|
||||
|
||||
case Bun::ErrorCode::ERR_HTTP_CONTENT_LENGTH_MISMATCH: {
|
||||
auto arg0 = callFrame->argument(1);
|
||||
auto str0 = arg0.toWTFString(globalObject);
|
||||
RETURN_IF_EXCEPTION(scope, {});
|
||||
auto arg1 = callFrame->argument(2);
|
||||
auto str1 = arg1.toWTFString(globalObject);
|
||||
RETURN_IF_EXCEPTION(scope, {});
|
||||
auto message = makeString("Response body's content-length of "_s, str0, " byte(s) does not match the content-length of "_s, str1, " byte(s) set in header"_s);
|
||||
return JSC::JSValue::encode(createError(globalObject, ErrorCode::ERR_HTTP_CONTENT_LENGTH_MISMATCH, message));
|
||||
}
|
||||
|
||||
case ErrorCode::ERR_SSL_NO_CIPHER_MATCH: {
|
||||
auto arg0 = callFrame->argument(1);
|
||||
auto err = createError(globalObject, ErrorCode::ERR_SSL_NO_CIPHER_MATCH, "No cipher match"_s);
|
||||
err->putDirect(vm, Identifier::fromString(vm, "reason"_s), JSC::jsString(vm, WTF::String("no cipher match"_s)));
|
||||
err->putDirect(vm, Identifier::fromString(vm, "library"_s), JSC::jsString(vm, WTF::String("SSL routines"_s)));
|
||||
err->putDirect(vm, Identifier::fromString(vm, "cipher"_s), arg0);
|
||||
return JSC::JSValue::encode(err);
|
||||
}
|
||||
|
||||
case ErrorCode::ERR_IPC_DISCONNECTED:
|
||||
return JSC::JSValue::encode(createError(globalObject, ErrorCode::ERR_IPC_DISCONNECTED, "IPC channel is already disconnected"_s));
|
||||
case ErrorCode::ERR_SERVER_NOT_RUNNING:
|
||||
@@ -2435,17 +2455,6 @@ JSC_DEFINE_HOST_FUNCTION(Bun::jsFunctionMakeErrorWithCode, (JSC::JSGlobalObject
|
||||
return JSC::JSValue::encode(createError(globalObject, ErrorCode::ERR_TLS_CERT_ALTNAME_FORMAT, "Invalid subject alternative name string"_s));
|
||||
case ErrorCode::ERR_TLS_SNI_FROM_SERVER:
|
||||
return JSC::JSValue::encode(createError(globalObject, ErrorCode::ERR_TLS_SNI_FROM_SERVER, "Cannot issue SNI from a TLS server-side socket"_s));
|
||||
case ErrorCode::ERR_SSL_NO_CIPHER_MATCH: {
|
||||
auto err = createError(globalObject, ErrorCode::ERR_SSL_NO_CIPHER_MATCH, "No cipher match"_s);
|
||||
|
||||
auto reason = JSC::jsString(vm, WTF::String("no cipher match"_s));
|
||||
err->putDirect(vm, Identifier::fromString(vm, "reason"_s), reason);
|
||||
|
||||
auto library = JSC::jsString(vm, WTF::String("SSL routines"_s));
|
||||
err->putDirect(vm, Identifier::fromString(vm, "library"_s), library);
|
||||
|
||||
return JSC::JSValue::encode(err);
|
||||
}
|
||||
case ErrorCode::ERR_INVALID_URI:
|
||||
return JSC::JSValue::encode(createError(globalObject, ErrorCode::ERR_INVALID_URI, "URI malformed"_s));
|
||||
case ErrorCode::ERR_HTTP2_PSEUDOHEADER_NOT_ALLOWED:
|
||||
@@ -2508,6 +2517,12 @@ JSC_DEFINE_HOST_FUNCTION(Bun::jsFunctionMakeErrorWithCode, (JSC::JSGlobalObject
|
||||
return JSC::JSValue::encode(createError(globalObject, ErrorCode::ERR_VM_MODULE_DIFFERENT_CONTEXT, "Linked modules must use the same context"_s));
|
||||
case ErrorCode::ERR_VM_DYNAMIC_IMPORT_CALLBACK_MISSING:
|
||||
return JSC::JSValue::encode(createError(globalObject, ErrorCode::ERR_VM_DYNAMIC_IMPORT_CALLBACK_MISSING, "A dynamic import callback was not specified."_s));
|
||||
case ErrorCode::ERR_HTTP_TRAILER_INVALID:
|
||||
return JSC::JSValue::encode(createError(globalObject, ErrorCode::ERR_HTTP_TRAILER_INVALID, "Trailers are invalid with this transfer encoding"_s));
|
||||
case ErrorCode::ERR_HTTP_SOCKET_ENCODING:
|
||||
return JSC::JSValue::encode(createError(globalObject, ErrorCode::ERR_HTTP_SOCKET_ENCODING, "Changing the socket encoding is not allowed per RFC7230 Section 3."_s));
|
||||
case ErrorCode::ERR_HTTP_REQUEST_TIMEOUT:
|
||||
return JSC::JSValue::encode(createError(globalObject, ErrorCode::ERR_HTTP_REQUEST_TIMEOUT, "Request timeout"_s));
|
||||
case ErrorCode::ERR_TLS_ALPN_CALLBACK_WITH_PROTOCOLS:
|
||||
return JSC::JSValue::encode(createError(globalObject, ErrorCode::ERR_TLS_ALPN_CALLBACK_WITH_PROTOCOLS, "The ALPNCallback and ALPNProtocols TLS options are mutually exclusive"_s));
|
||||
case ErrorCode::ERR_HTTP2_TOO_MANY_CUSTOM_SETTINGS:
|
||||
|
||||
@@ -87,7 +87,9 @@ const errors: ErrorCodeMapping = [
|
||||
["ERR_HTTP_INVALID_HEADER_VALUE", TypeError],
|
||||
["ERR_HTTP_INVALID_STATUS_CODE", RangeError],
|
||||
["ERR_HTTP_TRAILER_INVALID", Error],
|
||||
["ERR_HTTP_REQUEST_TIMEOUT", Error],
|
||||
["ERR_HTTP_SOCKET_ASSIGNED", Error],
|
||||
["ERR_HTTP_SOCKET_ENCODING", Error],
|
||||
["ERR_HTTP2_ALTSVC_INVALID_ORIGIN", TypeError],
|
||||
["ERR_HTTP2_ALTSVC_LENGTH", TypeError],
|
||||
["ERR_HTTP2_CONNECT_AUTHORITY", Error],
|
||||
|
||||
@@ -132,13 +132,12 @@ static void assignHeadersFromUWebSocketsForCall(uWS::HttpRequest* request, JSVal
|
||||
size++;
|
||||
}
|
||||
|
||||
JSC::JSObject* headersObject = JSC::constructEmptyObject(globalObject, globalObject->objectPrototype(), std::min(size, static_cast<size_t>(JSFinalObject::maxInlineCapacity)));
|
||||
RETURN_IF_EXCEPTION(scope, void());
|
||||
JSC::JSArray* setCookiesHeaderArray = nullptr;
|
||||
JSC::JSString* setCookiesHeaderString = nullptr;
|
||||
MarkedArgumentBuffer arrayValues;
|
||||
|
||||
args.append(headersObject);
|
||||
HashMap<RefPtr<UniquedStringImpl>, JSValue, IdentifierRepHash, HashTraits<RefPtr<UniquedStringImpl>>> headersMap;
|
||||
headersMap.reserveInitialCapacity(std::min(size, static_cast<size_t>(JSFinalObject::maxInlineCapacity)));
|
||||
|
||||
for (auto it = request->begin(); it != request->end(); ++it) {
|
||||
auto pair = *it;
|
||||
@@ -170,22 +169,38 @@ static void assignHeadersFromUWebSocketsForCall(uWS::HttpRequest* request, JSVal
|
||||
setCookiesHeaderArray = constructEmptyArray(globalObject, nullptr);
|
||||
RETURN_IF_EXCEPTION(scope, );
|
||||
setCookiesHeaderString = nameString;
|
||||
headersObject->putDirect(vm, nameIdentifier, setCookiesHeaderArray, 0);
|
||||
headersMap.set(nameIdentifier.impl(), setCookiesHeaderArray);
|
||||
RETURN_IF_EXCEPTION(scope, void());
|
||||
}
|
||||
arrayValues.append(setCookiesHeaderString);
|
||||
arrayValues.append(jsValue);
|
||||
setCookiesHeaderArray->push(globalObject, jsValue);
|
||||
RETURN_IF_EXCEPTION(scope, void());
|
||||
|
||||
} else {
|
||||
headersObject->putDirectMayBeIndex(globalObject, nameIdentifier, jsValue);
|
||||
RETURN_IF_EXCEPTION(scope, void());
|
||||
if (auto prev = headersMap.get(nameIdentifier.impl())) {
|
||||
if (WTF::equal(nameIdentifier.impl(), "host"_s)) {
|
||||
continue;
|
||||
}
|
||||
headersMap.set(nameIdentifier.impl(), JSValue(jsString(vm, makeString(prev.getString(globalObject), ", "_s, jsValue))));
|
||||
arrayValues.append(nameString);
|
||||
arrayValues.append(jsValue);
|
||||
continue;
|
||||
}
|
||||
headersMap.set(nameIdentifier.impl(), JSValue(jsValue));
|
||||
arrayValues.append(nameString);
|
||||
arrayValues.append(jsValue);
|
||||
RETURN_IF_EXCEPTION(scope, void());
|
||||
}
|
||||
}
|
||||
{
|
||||
JSC::JSObject* headersObject = JSC::constructEmptyObject(globalObject, globalObject->objectPrototype(), std::min(size, static_cast<size_t>(JSFinalObject::maxInlineCapacity)));
|
||||
RETURN_IF_EXCEPTION(scope, );
|
||||
for (auto& entry : headersMap) {
|
||||
Identifier identifier = Identifier::fromUid(vm, entry.key.get());
|
||||
headersObject->putDirectMayBeIndex(globalObject, identifier, entry.value);
|
||||
RETURN_IF_EXCEPTION(scope, );
|
||||
}
|
||||
args.append(headersObject);
|
||||
}
|
||||
|
||||
JSC::JSArray* array;
|
||||
{
|
||||
@@ -557,6 +572,10 @@ static void writeFetchHeadersToUWSResponse(WebCore::FetchHeaders& headers, uWS::
|
||||
}
|
||||
}
|
||||
|
||||
if (header.key == WebCore::HTTPHeaderName::TransferEncoding) {
|
||||
data->state |= uWS::HttpResponseData<isSSL>::HTTP_WROTE_TRANSFER_ENCODING_HEADER;
|
||||
}
|
||||
|
||||
// Prevent automatic Date header insertion when user provides one
|
||||
if (header.key == WebCore::HTTPHeaderName::Date) {
|
||||
data->state |= uWS::HttpResponseData<isSSL>::HTTP_WROTE_DATE_HEADER;
|
||||
|
||||
@@ -71,7 +71,7 @@ struct StringPtr {
|
||||
{
|
||||
auto& vm = globalObject->vm();
|
||||
if (m_size != 0) {
|
||||
return JSC::jsString(vm, WTF::String::fromUTF8({ m_str, m_size }));
|
||||
return JSC::jsString(vm, WTF::String({ m_str, m_size }));
|
||||
}
|
||||
return jsEmptyString(vm);
|
||||
}
|
||||
|
||||
6
src/js/builtins.d.ts
vendored
6
src/js/builtins.d.ts
vendored
@@ -766,6 +766,8 @@ declare function $ERR_ASYNC_CALLBACK(name): TypeError;
|
||||
declare function $ERR_AMBIGUOUS_ARGUMENT(arg, message): TypeError;
|
||||
declare function $ERR_INVALID_FD_TYPE(type): TypeError;
|
||||
declare function $ERR_IP_BLOCKED(ip): Error;
|
||||
declare function $ERR_HTTP_CONTENT_LENGTH_MISMATCH(body, header);
|
||||
declare function $ERR_SSL_NO_CIPHER_MATCH(cipher): Error;
|
||||
|
||||
declare function $ERR_IPC_DISCONNECTED(): Error;
|
||||
declare function $ERR_SERVER_NOT_RUNNING(): Error;
|
||||
@@ -795,7 +797,6 @@ declare function $ERR_TLS_RENEGOTIATION_DISABLED(): Error;
|
||||
declare function $ERR_UNAVAILABLE_DURING_EXIT(): Error;
|
||||
declare function $ERR_TLS_CERT_ALTNAME_FORMAT(): SyntaxError;
|
||||
declare function $ERR_TLS_SNI_FROM_SERVER(): Error;
|
||||
declare function $ERR_SSL_NO_CIPHER_MATCH(): Error;
|
||||
declare function $ERR_INVALID_URI(): URIError;
|
||||
declare function $ERR_HTTP2_PSEUDOHEADER_NOT_ALLOWED(): TypeError;
|
||||
declare function $ERR_HTTP2_INFO_STATUS_NOT_ALLOWED(): RangeError;
|
||||
@@ -830,6 +831,9 @@ declare function $ERR_VM_MODULE_CANNOT_CREATE_CACHED_DATA(): Error;
|
||||
declare function $ERR_VM_MODULE_NOT_MODULE(): Error;
|
||||
declare function $ERR_VM_MODULE_DIFFERENT_CONTEXT(): Error;
|
||||
declare function $ERR_VM_MODULE_LINK_FAILURE(message: string, cause: Error): Error;
|
||||
declare function $ERR_HTTP_TRAILER_INVALID(): Error;
|
||||
declare function $ERR_HTTP_SOCKET_ENCODING(): Error;
|
||||
declare function $ERR_HTTP_REQUEST_TIMEOUT(): Error;
|
||||
declare function $ERR_TLS_ALPN_CALLBACK_WITH_PROTOCOLS(): TypeError;
|
||||
declare function $ERR_HTTP2_TOO_MANY_CUSTOM_SETTINGS(): Error;
|
||||
declare function $ERR_HTTP2_CONNECT_AUTHORITY(): Error;
|
||||
|
||||
@@ -199,14 +199,6 @@ function validateMsecs(numberlike: any, field: string) {
|
||||
return numberlike;
|
||||
}
|
||||
|
||||
class ConnResetException extends Error {
|
||||
constructor(msg) {
|
||||
super(msg);
|
||||
this.code = "ECONNRESET";
|
||||
this.name = "ConnResetException";
|
||||
}
|
||||
}
|
||||
|
||||
const METHODS = [
|
||||
"ACL",
|
||||
"BIND",
|
||||
@@ -348,6 +340,20 @@ function hasServerResponseFinished(self, chunk, callback) {
|
||||
return false;
|
||||
}
|
||||
|
||||
let utcCache;
|
||||
function utcDate() {
|
||||
if (!utcCache) cache();
|
||||
return utcCache;
|
||||
}
|
||||
function cache() {
|
||||
const d = new Date();
|
||||
utcCache = d.toUTCString();
|
||||
setTimeout(resetCache, 1000 - d.getMilliseconds()).unref();
|
||||
}
|
||||
function resetCache() {
|
||||
utcCache = undefined;
|
||||
}
|
||||
|
||||
function emitErrorNt(msg, err, callback) {
|
||||
if ($isCallable(callback)) {
|
||||
callback(err);
|
||||
@@ -359,9 +365,10 @@ function emitErrorNt(msg, err, callback) {
|
||||
const setMaxHTTPHeaderSize = $newZigFunction("node_http_binding.zig", "setMaxHTTPHeaderSize", 1);
|
||||
const getMaxHTTPHeaderSize = $newZigFunction("node_http_binding.zig", "getMaxHTTPHeaderSize", 0);
|
||||
const kOutHeaders = Symbol("kOutHeaders");
|
||||
const kNeedDrain = Symbol("kNeedDrain");
|
||||
const kBunServer = Symbol("kBunServer");
|
||||
|
||||
export {
|
||||
ConnResetException,
|
||||
Headers,
|
||||
METHODS,
|
||||
STATUS_CODES,
|
||||
@@ -393,6 +400,7 @@ export {
|
||||
kAbortController,
|
||||
kAgent,
|
||||
kBodyChunks,
|
||||
kBunServer,
|
||||
kClearTimeout,
|
||||
kCloseCallback,
|
||||
kDeferredTimeouts,
|
||||
@@ -406,6 +414,7 @@ export {
|
||||
kMaxHeaderSize,
|
||||
kMaxHeadersCount,
|
||||
kMethod,
|
||||
kNeedDrain,
|
||||
kOptions,
|
||||
kOutHeaders,
|
||||
kParser,
|
||||
@@ -439,6 +448,7 @@ export {
|
||||
timeoutTimerSymbol,
|
||||
tlsSymbol,
|
||||
typeSymbol,
|
||||
utcDate,
|
||||
validateMsecs,
|
||||
webRequestOrResponse,
|
||||
webRequestOrResponseHasBodyValue,
|
||||
|
||||
@@ -17,6 +17,9 @@ class NotImplementedError extends Error {
|
||||
// in the definition so that it isn't bundled unless used
|
||||
hideFromStack(NotImplementedError);
|
||||
}
|
||||
get ["constructor"]() {
|
||||
return Error;
|
||||
}
|
||||
}
|
||||
|
||||
function throwNotImplemented(feature: string, issue?: number, extra?: string): never {
|
||||
@@ -78,6 +81,9 @@ class ExceptionWithHostPort extends Error {
|
||||
this.port = port;
|
||||
}
|
||||
}
|
||||
get ["constructor"]() {
|
||||
return Error;
|
||||
}
|
||||
}
|
||||
|
||||
class NodeAggregateError extends AggregateError {
|
||||
@@ -85,12 +91,21 @@ class NodeAggregateError extends AggregateError {
|
||||
super(new SafeArrayIterator(errors), message);
|
||||
this.code = errors[0]?.code;
|
||||
}
|
||||
|
||||
get ["constructor"]() {
|
||||
return AggregateError;
|
||||
}
|
||||
}
|
||||
|
||||
class ConnResetException extends Error {
|
||||
constructor(msg) {
|
||||
super(msg);
|
||||
this.code = "ECONNRESET";
|
||||
}
|
||||
get ["constructor"]() {
|
||||
return Error;
|
||||
}
|
||||
}
|
||||
|
||||
class ErrnoException extends Error {
|
||||
errno: number;
|
||||
syscall: string;
|
||||
@@ -106,13 +121,12 @@ class ErrnoException extends Error {
|
||||
this.code = code;
|
||||
this.syscall = syscall;
|
||||
}
|
||||
|
||||
get ["constructor"]() {
|
||||
return Error;
|
||||
}
|
||||
}
|
||||
|
||||
function once(callback, { preserveReturnValue = false } = kEmptyObject) {
|
||||
function once(callback, { preserveReturnValue = false } = kEmptyObject as any) {
|
||||
let called = false;
|
||||
let returnValue;
|
||||
return function (...args) {
|
||||
@@ -135,6 +149,7 @@ export default {
|
||||
warnNotImplementedOnce,
|
||||
ExceptionWithHostPort,
|
||||
NodeAggregateError,
|
||||
ConnResetException,
|
||||
ErrnoException,
|
||||
once,
|
||||
|
||||
|
||||
@@ -18,7 +18,7 @@ function highWaterMarkFrom(
|
||||
return options.highWaterMark != null ? options.highWaterMark : isDuplex ? options[duplexKey] : null;
|
||||
}
|
||||
|
||||
function getDefaultHighWaterMark(objectMode?: boolean): number {
|
||||
function getDefaultHighWaterMark(objectMode: boolean): number {
|
||||
return objectMode ? defaultHighWaterMarkObjectMode : defaultHighWaterMarkBytes;
|
||||
}
|
||||
|
||||
|
||||
@@ -19,6 +19,11 @@ function urlToHttpOptions(url) {
|
||||
return options;
|
||||
}
|
||||
|
||||
function isURL(self) {
|
||||
return Boolean(self?.href && self.protocol && self.auth === undefined && self.path === undefined);
|
||||
}
|
||||
|
||||
export default {
|
||||
urlToHttpOptions,
|
||||
isURL,
|
||||
};
|
||||
|
||||
@@ -1,153 +1,487 @@
|
||||
const EventEmitter: typeof import("node:events").EventEmitter = require("node:events");
|
||||
"use strict";
|
||||
|
||||
const { kEmptyObject } = require("internal/http");
|
||||
const net = require("node:net");
|
||||
const EventEmitter = require("node:events");
|
||||
// const { AsyncResource } = require("async_hooks");
|
||||
// const { async_id_symbol } = require("internal/async_hooks").symbols;
|
||||
const { kEmptyObject, once } = require("internal/shared");
|
||||
const { validateNumber, validateOneOf, validateString } = require("internal/validators");
|
||||
|
||||
const { FakeSocket } = require("internal/http/FakeSocket");
|
||||
const NumberParseInt = Number.parseInt;
|
||||
const ObjectKeys = Object.keys;
|
||||
const ObjectValues = Object.values;
|
||||
|
||||
const ObjectDefineProperty = Object.defineProperty;
|
||||
const kOnKeylog = Symbol("onkeylog");
|
||||
const kRequestOptions = Symbol("requestOptions");
|
||||
const kRequestAsyncResource = Symbol("requestAsyncResource");
|
||||
|
||||
const kfakeSocket = Symbol("kfakeSocket");
|
||||
// TODO(): make this configurable
|
||||
const HTTP_AGENT_KEEP_ALIVE_TIMEOUT_BUFFER = 1000;
|
||||
// New Agent code.
|
||||
|
||||
const NODE_HTTP_WARNING =
|
||||
"WARN: Agent is mostly unused in Bun's implementation of http. If you see strange behavior, this is probably the cause.";
|
||||
// The largest departure from the previous implementation is that
|
||||
// an Agent instance holds connections for a variable number of host:ports.
|
||||
// Surprisingly, this is still API compatible as far as third parties are
|
||||
// concerned. The only code that really notices the difference is the
|
||||
// request object.
|
||||
|
||||
// Define Agent interface
|
||||
interface Agent extends InstanceType<typeof EventEmitter> {
|
||||
defaultPort: number;
|
||||
protocol: string;
|
||||
options: any;
|
||||
requests: Record<string, any>;
|
||||
sockets: Record<string, any>;
|
||||
freeSockets: Record<string, any>;
|
||||
keepAliveMsecs: number;
|
||||
keepAlive: boolean;
|
||||
maxSockets: number;
|
||||
maxFreeSockets: number;
|
||||
scheduling: string;
|
||||
maxTotalSockets: any;
|
||||
totalSocketCount: number;
|
||||
[kfakeSocket]?: any;
|
||||
// Another departure is that all code related to HTTP parsing is in
|
||||
// ClientRequest.onSocket(). The Agent is now *strictly*
|
||||
// concerned with managing a connection pool.
|
||||
|
||||
createConnection(): any;
|
||||
getName(options?: any): string;
|
||||
addRequest(): void;
|
||||
createSocket(req: any, options: any, cb: (err: any, socket: any) => void): void;
|
||||
removeSocket(): void;
|
||||
keepSocketAlive(): boolean;
|
||||
reuseSocket(): void;
|
||||
destroy(): void;
|
||||
class ReusedHandle {
|
||||
type;
|
||||
handle;
|
||||
constructor(type, handle) {
|
||||
this.type = type;
|
||||
this.handle = handle;
|
||||
}
|
||||
}
|
||||
|
||||
// Define the constructor interface
|
||||
interface AgentConstructor {
|
||||
new (options?: any): Agent;
|
||||
(options?: any): Agent;
|
||||
defaultMaxSockets: number;
|
||||
globalAgent: Agent;
|
||||
prototype: Agent;
|
||||
function freeSocketErrorListener(err) {
|
||||
const socket = this;
|
||||
$debug("SOCKET ERROR on FREE socket:", err.message, err.stack);
|
||||
socket.destroy();
|
||||
socket.emit("agentRemove");
|
||||
}
|
||||
|
||||
function Agent(options = kEmptyObject) {
|
||||
function Agent(options): void {
|
||||
if (!(this instanceof Agent)) return new Agent(options);
|
||||
|
||||
EventEmitter.$apply(this, []);
|
||||
EventEmitter.$call(this);
|
||||
|
||||
this.defaultPort = 80;
|
||||
this.protocol = "http:";
|
||||
|
||||
this.options = options = { ...options, path: null };
|
||||
if (options.noDelay === undefined) options.noDelay = true;
|
||||
this.options = { __proto__: null, ...options };
|
||||
|
||||
if (this.options.noDelay === undefined) this.options.noDelay = true;
|
||||
|
||||
// Don't confuse net and make it think that we're connecting to a pipe
|
||||
this.requests = Object.create(null);
|
||||
this.sockets = Object.create(null);
|
||||
this.freeSockets = Object.create(null);
|
||||
|
||||
this.keepAliveMsecs = options.keepAliveMsecs || 1000;
|
||||
this.keepAlive = options.keepAlive || false;
|
||||
this.maxSockets = options.maxSockets || Agent.defaultMaxSockets;
|
||||
this.maxFreeSockets = options.maxFreeSockets || 256;
|
||||
this.scheduling = options.scheduling || "lifo";
|
||||
this.maxTotalSockets = options.maxTotalSockets;
|
||||
this.options.path = null;
|
||||
this.requests = { __proto__: null };
|
||||
this.sockets = { __proto__: null };
|
||||
this.freeSockets = { __proto__: null };
|
||||
this.keepAliveMsecs = this.options.keepAliveMsecs || 1000;
|
||||
this.keepAlive = this.options.keepAlive || false;
|
||||
this.maxSockets = this.options.maxSockets || Agent.defaultMaxSockets;
|
||||
this.maxFreeSockets = this.options.maxFreeSockets || 256;
|
||||
this.scheduling = this.options.scheduling || "lifo";
|
||||
this.maxTotalSockets = this.options.maxTotalSockets;
|
||||
this.totalSocketCount = 0;
|
||||
this.defaultPort = options.defaultPort || 80;
|
||||
this.protocol = options.protocol || "http:";
|
||||
|
||||
validateOneOf(this.scheduling, "scheduling", ["fifo", "lifo"]);
|
||||
|
||||
if (this.maxTotalSockets !== undefined) {
|
||||
validateNumber(this.maxTotalSockets, "maxTotalSockets", 1);
|
||||
} else {
|
||||
this.maxTotalSockets = Infinity;
|
||||
}
|
||||
|
||||
this.on("free", (socket, options) => {
|
||||
const name = this.getName(options);
|
||||
$debug("agent.on(free)", name);
|
||||
|
||||
// TODO(ronag): socket.destroy(err) might have been called
|
||||
// before coming here and have an 'error' scheduled. In the
|
||||
// case of socket.destroy() below this 'error' has no handler
|
||||
// and could cause unhandled exception.
|
||||
|
||||
if (!socket.writable) {
|
||||
socket.destroy();
|
||||
return;
|
||||
}
|
||||
|
||||
const requests = this.requests[name];
|
||||
if (requests?.length) {
|
||||
const req = requests.shift();
|
||||
const reqAsyncRes = req[kRequestAsyncResource];
|
||||
if (reqAsyncRes) {
|
||||
// Run request within the original async context.
|
||||
reqAsyncRes.runInAsyncScope(() => {
|
||||
asyncResetHandle(socket);
|
||||
setRequestSocket(this, req, socket);
|
||||
});
|
||||
req[kRequestAsyncResource] = null;
|
||||
} else {
|
||||
setRequestSocket(this, req, socket);
|
||||
}
|
||||
if (requests.length === 0) {
|
||||
delete this.requests[name];
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// If there are no pending requests, then put it in
|
||||
// the freeSockets pool, but only if we're allowed to do so.
|
||||
const req = socket._httpMessage;
|
||||
if (!req || !req.shouldKeepAlive || !this.keepAlive) {
|
||||
socket.destroy();
|
||||
return;
|
||||
}
|
||||
|
||||
const freeSockets = this.freeSockets[name] || [];
|
||||
const freeLen = freeSockets.length;
|
||||
let count = freeLen;
|
||||
if (this.sockets[name]) count += this.sockets[name].length;
|
||||
|
||||
if (
|
||||
this.totalSocketCount > this.maxTotalSockets ||
|
||||
count > this.maxSockets ||
|
||||
freeLen >= this.maxFreeSockets ||
|
||||
!this.keepSocketAlive(socket)
|
||||
) {
|
||||
socket.destroy();
|
||||
return;
|
||||
}
|
||||
|
||||
this.freeSockets[name] = freeSockets;
|
||||
// socket[async_id_symbol] = -1;
|
||||
socket._httpMessage = null;
|
||||
this.removeSocket(socket, options);
|
||||
|
||||
socket.once("error", freeSocketErrorListener);
|
||||
freeSockets.push(socket);
|
||||
});
|
||||
|
||||
// Don't emit keylog events unless there is a listener for them.
|
||||
this.on("newListener", maybeEnableKeylog);
|
||||
}
|
||||
$toClass(Agent, "Agent", EventEmitter);
|
||||
|
||||
// Type assertion to help TypeScript understand Agent has static properties
|
||||
const AgentClass = Agent as unknown as AgentConstructor;
|
||||
function maybeEnableKeylog(eventName) {
|
||||
if (eventName === "keylog") {
|
||||
this.removeListener("newListener", maybeEnableKeylog);
|
||||
// Future sockets will listen on keylog at creation.
|
||||
const agent = this;
|
||||
this[kOnKeylog] = function onkeylog(keylog) {
|
||||
agent.emit("keylog", keylog, this);
|
||||
};
|
||||
// Existing sockets will start listening on keylog now.
|
||||
const sockets = ObjectValues(this.sockets);
|
||||
for (let i = 0; i < sockets.length; i++) {
|
||||
sockets[i].on("keylog", this[kOnKeylog]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ObjectDefineProperty(AgentClass, "globalAgent", {
|
||||
get: function () {
|
||||
return globalAgent;
|
||||
},
|
||||
});
|
||||
Agent.defaultMaxSockets = Infinity;
|
||||
|
||||
ObjectDefineProperty(AgentClass, "defaultMaxSockets", {
|
||||
get: function () {
|
||||
return Infinity;
|
||||
},
|
||||
});
|
||||
Agent.prototype.createConnection = net.createConnection;
|
||||
|
||||
Agent.prototype.createConnection = function () {
|
||||
$debug(`${NODE_HTTP_WARNING}\n`, "WARN: Agent.createConnection is a no-op, returns fake socket");
|
||||
return (this[kfakeSocket] ??= new FakeSocket());
|
||||
};
|
||||
|
||||
Agent.prototype.getName = function (options = kEmptyObject) {
|
||||
// Get the key for a given set of request options
|
||||
Agent.prototype.getName = function getName(options = kEmptyObject as any) {
|
||||
let name = options.host || "localhost";
|
||||
|
||||
name += ":";
|
||||
if (options.port) {
|
||||
name += options.port;
|
||||
}
|
||||
if (options.port) name += options.port;
|
||||
|
||||
name += ":";
|
||||
if (options.localAddress) {
|
||||
name += options.localAddress;
|
||||
}
|
||||
if (options.localAddress) name += options.localAddress;
|
||||
|
||||
// Pacify parallel/test-http-agent-getname by only appending
|
||||
// the ':' when options.family is set.
|
||||
if (options.family === 4 || options.family === 6) {
|
||||
name += `:${options.family}`;
|
||||
}
|
||||
if (options.socketPath) {
|
||||
name += `:${options.socketPath}`;
|
||||
}
|
||||
if (options.family === 4 || options.family === 6) name += `:${options.family}`;
|
||||
|
||||
if (options.socketPath) name += `:${options.socketPath}`;
|
||||
|
||||
return name;
|
||||
};
|
||||
|
||||
Agent.prototype.addRequest = function () {
|
||||
$debug(`${NODE_HTTP_WARNING}\n`, "WARN: Agent.addRequest is a no-op");
|
||||
Agent.prototype.addRequest = function addRequest(req, options, port /* legacy */, localAddress /* legacy */) {
|
||||
// Legacy API: addRequest(req, host, port, localAddress)
|
||||
if (typeof options === "string") {
|
||||
options = {
|
||||
__proto__: null,
|
||||
host: options,
|
||||
port,
|
||||
localAddress,
|
||||
};
|
||||
}
|
||||
|
||||
options = { __proto__: null, ...options, ...this.options };
|
||||
if (options.socketPath) options.path = options.socketPath;
|
||||
|
||||
normalizeServerName(options, req);
|
||||
|
||||
const name = this.getName(options);
|
||||
this.sockets[name] ||= [];
|
||||
|
||||
const freeSockets = this.freeSockets[name];
|
||||
let socket;
|
||||
if (freeSockets) {
|
||||
while (freeSockets.length && freeSockets[0].destroyed) {
|
||||
freeSockets.shift();
|
||||
}
|
||||
socket = this.scheduling === "fifo" ? freeSockets.shift() : freeSockets.pop();
|
||||
if (!freeSockets.length) delete this.freeSockets[name];
|
||||
}
|
||||
|
||||
const freeLen = freeSockets ? freeSockets.length : 0;
|
||||
const sockLen = freeLen + this.sockets[name].length;
|
||||
|
||||
if (socket) {
|
||||
asyncResetHandle(socket);
|
||||
this.reuseSocket(socket, req);
|
||||
setRequestSocket(this, req, socket);
|
||||
this.sockets[name].push(socket);
|
||||
} else if (sockLen < this.maxSockets && this.totalSocketCount < this.maxTotalSockets) {
|
||||
$debug("call onSocket", sockLen, freeLen);
|
||||
// If we are under maxSockets create a new one.
|
||||
this.createSocket(req, options, (err, socket) => {
|
||||
if (err) req.onSocket(socket, err);
|
||||
else setRequestSocket(this, req, socket);
|
||||
});
|
||||
} else {
|
||||
$debug("wait for socket");
|
||||
// We are over limit so we'll add it to the queue.
|
||||
this.requests[name] ||= [];
|
||||
|
||||
// Used to create sockets for pending requests from different origin
|
||||
req[kRequestOptions] = options;
|
||||
// Used to capture the original async context.
|
||||
// req[kRequestAsyncResource] = new AsyncResource("QueuedRequest");
|
||||
|
||||
this.requests[name].push(req);
|
||||
}
|
||||
};
|
||||
|
||||
Agent.prototype.createSocket = function (req, options, cb) {
|
||||
$debug(`${NODE_HTTP_WARNING}\n`, "WARN: Agent.createSocket returns fake socket");
|
||||
cb(null, (this[kfakeSocket] ??= new FakeSocket()));
|
||||
Agent.prototype.createSocket = function createSocket(req, options, cb) {
|
||||
options = { __proto__: null, ...options, ...this.options };
|
||||
if (options.socketPath) options.path = options.socketPath;
|
||||
|
||||
normalizeServerName(options, req);
|
||||
|
||||
const name = this.getName(options);
|
||||
options._agentKey = name;
|
||||
options.encoding = null;
|
||||
|
||||
const oncreate = once((err, s) => {
|
||||
if (err) return cb(err);
|
||||
this.sockets[name] ||= [];
|
||||
this.sockets[name].push(s);
|
||||
this.totalSocketCount++;
|
||||
$debug("sockets", name, this.sockets[name].length, this.totalSocketCount);
|
||||
installListeners(this, s, options);
|
||||
cb(null, s);
|
||||
});
|
||||
// When keepAlive is true, pass the related options to createConnection
|
||||
if (this.keepAlive) {
|
||||
options.keepAlive = this.keepAlive;
|
||||
options.keepAliveInitialDelay = this.keepAliveMsecs;
|
||||
}
|
||||
// console.log(Agent.prototype.createConnection, this.createConnection, this instanceof Agent); //??
|
||||
const newSocket = this.createConnection(options, oncreate);
|
||||
if (newSocket) oncreate(null, newSocket);
|
||||
};
|
||||
|
||||
Agent.prototype.removeSocket = function () {
|
||||
$debug(`${NODE_HTTP_WARNING}\n`, "WARN: Agent.removeSocket is a no-op");
|
||||
function normalizeServerName(options, req) {
|
||||
if (!options.servername && options.servername !== "") options.servername = calculateServerName(options, req);
|
||||
}
|
||||
|
||||
function calculateServerName(options, req) {
|
||||
let servername = options.host;
|
||||
const hostHeader = req.getHeader("host");
|
||||
if (hostHeader) {
|
||||
validateString(hostHeader, "options.headers.host");
|
||||
|
||||
// abc => abc
|
||||
// abc:123 => abc
|
||||
// [::1] => ::1
|
||||
// [::1]:123 => ::1
|
||||
if (hostHeader[0] === "[") {
|
||||
const index = hostHeader.indexOf("]");
|
||||
if (index === -1) {
|
||||
// Leading '[', but no ']'. Need to do something...
|
||||
servername = hostHeader;
|
||||
} else {
|
||||
servername = hostHeader.substring(1, index);
|
||||
}
|
||||
} else {
|
||||
servername = hostHeader.split(":", 1)[0];
|
||||
}
|
||||
}
|
||||
// Don't implicitly set invalid (IP) servernames.
|
||||
if (net.isIP(servername)) servername = "";
|
||||
return servername;
|
||||
}
|
||||
|
||||
function installListeners(agent, s, options) {
|
||||
function onFree() {
|
||||
$debug("CLIENT socket onFree");
|
||||
agent.emit("free", s, options);
|
||||
}
|
||||
s.on("free", onFree);
|
||||
|
||||
function onClose(err) {
|
||||
$debug("CLIENT socket onClose");
|
||||
// This is the only place where sockets get removed from the Agent.
|
||||
// If you want to remove a socket from the pool, just close it.
|
||||
// All socket errors end in a close event anyway.
|
||||
agent.totalSocketCount--;
|
||||
agent.removeSocket(s, options);
|
||||
}
|
||||
s.on("close", onClose);
|
||||
|
||||
function onTimeout() {
|
||||
$debug("CLIENT socket onTimeout");
|
||||
|
||||
// Destroy if in free list.
|
||||
// TODO(ronag): Always destroy, even if not in free list.
|
||||
const sockets = agent.freeSockets;
|
||||
if (ObjectKeys(sockets).some(name => sockets[name].includes(s))) {
|
||||
return s.destroy();
|
||||
}
|
||||
}
|
||||
s.on("timeout", onTimeout);
|
||||
|
||||
function onRemove() {
|
||||
// We need this function for cases like HTTP 'upgrade'
|
||||
// (defined by WebSockets) where we need to remove a socket from the
|
||||
// pool because it'll be locked up indefinitely
|
||||
$debug("CLIENT socket onRemove");
|
||||
agent.totalSocketCount--;
|
||||
agent.removeSocket(s, options);
|
||||
s.removeListener("close", onClose);
|
||||
s.removeListener("free", onFree);
|
||||
s.removeListener("timeout", onTimeout);
|
||||
s.removeListener("agentRemove", onRemove);
|
||||
}
|
||||
s.on("agentRemove", onRemove);
|
||||
|
||||
if (agent[kOnKeylog]) {
|
||||
s.on("keylog", agent[kOnKeylog]);
|
||||
}
|
||||
}
|
||||
|
||||
Agent.prototype.removeSocket = function removeSocket(s, options) {
|
||||
const name = this.getName(options);
|
||||
$debug("removeSocket", name, "writable:", s.writable);
|
||||
const sets = [this.sockets];
|
||||
|
||||
// If the socket was destroyed, remove it from the free buffers too.
|
||||
if (!s.writable) sets.push(this.freeSockets);
|
||||
|
||||
for (let sk = 0; sk < sets.length; sk++) {
|
||||
const sockets = sets[sk];
|
||||
|
||||
if (sockets[name]) {
|
||||
const index = sockets[name].indexOf(s);
|
||||
if (index !== -1) {
|
||||
sockets[name].splice(index, 1);
|
||||
if (sockets[name].length === 0) delete sockets[name];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let req;
|
||||
if (this.requests[name]?.length) {
|
||||
$debug("removeSocket, have a request, make a socket");
|
||||
req = this.requests[name][0];
|
||||
} else {
|
||||
// TODO(rickyes): this logic will not be FIFO across origins.
|
||||
// There might be older requests in a different origin, but
|
||||
// if the origin which releases the socket has pending requests
|
||||
// that will be prioritized.
|
||||
const keys = ObjectKeys(this.requests);
|
||||
for (let i = 0; i < keys.length; i++) {
|
||||
const prop = keys[i];
|
||||
// Check whether this specific origin is already at maxSockets
|
||||
if (this.sockets[prop]?.length) break;
|
||||
$debug("removeSocket, have a request with different origin," + " make a socket");
|
||||
req = this.requests[prop][0];
|
||||
options = req[kRequestOptions];
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (req && options) {
|
||||
req[kRequestOptions] = undefined;
|
||||
// If we have pending requests and a socket gets closed make a new one
|
||||
this.createSocket(req, options, (err, socket) => {
|
||||
if (err) req.onSocket(socket, err);
|
||||
else socket.emit("free");
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
Agent.prototype.keepSocketAlive = function () {
|
||||
$debug(`${NODE_HTTP_WARNING}\n`, "WARN: Agent.keepSocketAlive is a no-op");
|
||||
return true;
|
||||
Agent.prototype.keepSocketAlive = function keepSocketAlive(socket) {
|
||||
socket.setKeepAlive(true, this.keepAliveMsecs);
|
||||
socket.unref();
|
||||
|
||||
let agentTimeout = this.options.timeout || 0;
|
||||
let canKeepSocketAlive = true;
|
||||
|
||||
if (socket._httpMessage?.res) {
|
||||
const keepAliveHint = socket._httpMessage.res.headers["keep-alive"];
|
||||
|
||||
if (keepAliveHint) {
|
||||
const hint = /^timeout=(\d+)/.exec(keepAliveHint)?.[1];
|
||||
|
||||
if (hint) {
|
||||
// Let the timer expire before the announced timeout to reduce
|
||||
// the likelihood of ECONNRESET errors
|
||||
let serverHintTimeout = NumberParseInt(hint) * 1000 - HTTP_AGENT_KEEP_ALIVE_TIMEOUT_BUFFER;
|
||||
serverHintTimeout = serverHintTimeout > 0 ? serverHintTimeout : 0;
|
||||
if (serverHintTimeout === 0) {
|
||||
// Cannot safely reuse the socket because the server timeout is
|
||||
// too short
|
||||
canKeepSocketAlive = false;
|
||||
} else if (serverHintTimeout < agentTimeout) {
|
||||
agentTimeout = serverHintTimeout;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (socket.timeout !== agentTimeout) {
|
||||
socket.setTimeout(agentTimeout);
|
||||
}
|
||||
|
||||
return canKeepSocketAlive;
|
||||
};
|
||||
|
||||
Agent.prototype.reuseSocket = function () {
|
||||
$debug(`${NODE_HTTP_WARNING}\n`, "WARN: Agent.reuseSocket is a no-op");
|
||||
Agent.prototype.reuseSocket = function reuseSocket(socket, req) {
|
||||
$debug("have free socket");
|
||||
socket.removeListener("error", freeSocketErrorListener);
|
||||
req.reusedSocket = true;
|
||||
socket.ref();
|
||||
};
|
||||
|
||||
Agent.prototype.destroy = function () {
|
||||
$debug(`${NODE_HTTP_WARNING}\n`, "WARN: Agent.destroy is a no-op");
|
||||
Agent.prototype.destroy = function destroy() {
|
||||
const sets = [this.freeSockets, this.sockets];
|
||||
for (let s = 0; s < sets.length; s++) {
|
||||
const set = sets[s];
|
||||
const keys = ObjectKeys(set);
|
||||
for (let v = 0; v < keys.length; v++) {
|
||||
const setName = set[keys[v]];
|
||||
for (let n = 0; n < setName.length; n++) {
|
||||
setName[n].destroy();
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
var globalAgent = new Agent();
|
||||
function setRequestSocket(agent, req, socket) {
|
||||
req.onSocket(socket);
|
||||
const agentTimeout = agent.options.timeout || 0;
|
||||
if (req.timeout === undefined || req.timeout === agentTimeout) {
|
||||
return;
|
||||
}
|
||||
socket.setTimeout(req.timeout);
|
||||
}
|
||||
|
||||
const http_agent_exports = {
|
||||
Agent: AgentClass,
|
||||
globalAgent,
|
||||
NODE_HTTP_WARNING,
|
||||
function asyncResetHandle(socket) {
|
||||
// Guard against an uninitialized or user supplied Socket.
|
||||
const handle = socket._handle;
|
||||
if (handle && typeof handle.asyncReset === "function") {
|
||||
// Assign the handle a new asyncId and run any destroy()/init() hooks.
|
||||
handle.asyncReset(new ReusedHandle(handle.getProviderType(), handle));
|
||||
// socket[async_id_symbol] = handle.getAsyncId();
|
||||
}
|
||||
}
|
||||
|
||||
export default {
|
||||
Agent,
|
||||
globalAgent: new Agent({ keepAlive: true, scheduling: "lifo", timeout: 5000 }),
|
||||
};
|
||||
|
||||
export default http_agent_exports;
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,9 +1,7 @@
|
||||
const { checkIsHttpToken } = require("internal/validators");
|
||||
const FreeList = require("internal/freelist");
|
||||
const { methods, allMethods, HTTPParser } = process.binding("http_parser");
|
||||
const incoming = require("node:_http_incoming");
|
||||
|
||||
const { IncomingMessage, readStart, readStop } = incoming;
|
||||
const { IncomingMessage, readStart, readStop } = require("node:_http_incoming");
|
||||
|
||||
const RegExpPrototypeExec = RegExp.prototype.exec;
|
||||
|
||||
@@ -132,7 +130,7 @@ function parserOnBody(b) {
|
||||
// Pretend this was the result of a stream._read call.
|
||||
if (!stream._dumped) {
|
||||
const ret = stream.push(b);
|
||||
if (!ret) readStop(this.socket);
|
||||
// if (!ret) readStop(this.socket);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -226,7 +224,6 @@ function prepareError(err, parser, rawPacket) {
|
||||
}
|
||||
|
||||
let warnedLenient = false;
|
||||
|
||||
function isLenient() {
|
||||
if (insecureHTTPParser && !warnedLenient) {
|
||||
warnedLenient = true;
|
||||
|
||||
@@ -1,122 +1,65 @@
|
||||
const { Readable } = require("internal/streams/readable");
|
||||
|
||||
// Hardcoded module "node:_http_incoming"
|
||||
const { Readable, finished } = require("node:stream");
|
||||
const {
|
||||
kHandle,
|
||||
kEmptyObject,
|
||||
STATUS_CODES,
|
||||
abortedSymbol,
|
||||
eofInProgress,
|
||||
kHandle,
|
||||
noBodySymbol,
|
||||
typeSymbol,
|
||||
NodeHTTPIncomingRequestType,
|
||||
noBodySymbol,
|
||||
fakeSocketSymbol,
|
||||
webRequestOrResponse,
|
||||
setRequestTimeout,
|
||||
emitEOFIncomingMessage,
|
||||
NodeHTTPBodyReadState,
|
||||
bodyStreamSymbol,
|
||||
getCompleteWebRequestOrResponseBodyValueAsArrayBuffer,
|
||||
isAbortError,
|
||||
emitErrorNextTickIfErrorListenerNT,
|
||||
kEmptyObject,
|
||||
getIsNextIncomingMessageHTTPS,
|
||||
setIsNextIncomingMessageHTTPS,
|
||||
NodeHTTPBodyReadState,
|
||||
emitEOFIncomingMessage,
|
||||
bodyStreamSymbol,
|
||||
statusMessageSymbol,
|
||||
statusCodeSymbol,
|
||||
webRequestOrResponse,
|
||||
statusMessageSymbol,
|
||||
NodeHTTPResponseAbortEvent,
|
||||
STATUS_CODES,
|
||||
assignHeadersFast,
|
||||
setRequestTimeout,
|
||||
headersTuple,
|
||||
webRequestOrResponseHasBodyValue,
|
||||
getCompleteWebRequestOrResponseBodyValueAsArrayBuffer,
|
||||
kBunServer,
|
||||
kAbortController,
|
||||
} = require("internal/http");
|
||||
|
||||
const { FakeSocket } = require("internal/http/FakeSocket");
|
||||
|
||||
var defaultIncomingOpts = { type: "request" };
|
||||
const ObjectDefineProperty = Object.defineProperty;
|
||||
|
||||
const kHeaders = Symbol("kHeaders");
|
||||
const kHeadersDistinct = Symbol("kHeadersDistinct");
|
||||
const kHeadersCount = Symbol("kHeadersCount");
|
||||
const kTrailers = Symbol("kTrailers");
|
||||
const kTrailersDistinct = Symbol("kTrailersDistinct");
|
||||
const kTrailersCount = Symbol("kTrailersCount");
|
||||
|
||||
const nop = () => {};
|
||||
|
||||
function assignHeadersSlow(object, req) {
|
||||
const headers = req.headers;
|
||||
var outHeaders = Object.create(null);
|
||||
const rawHeaders: string[] = [];
|
||||
var i = 0;
|
||||
for (let key in headers) {
|
||||
var originalKey = key;
|
||||
var value = headers[originalKey];
|
||||
|
||||
key = key.toLowerCase();
|
||||
|
||||
if (key !== "set-cookie") {
|
||||
value = String(value);
|
||||
$putByValDirect(rawHeaders, i++, originalKey);
|
||||
$putByValDirect(rawHeaders, i++, value);
|
||||
outHeaders[key] = value;
|
||||
} else {
|
||||
if ($isJSArray(value)) {
|
||||
outHeaders[key] = value.slice();
|
||||
|
||||
for (let entry of value) {
|
||||
$putByValDirect(rawHeaders, i++, originalKey);
|
||||
$putByValDirect(rawHeaders, i++, entry);
|
||||
}
|
||||
} else {
|
||||
value = String(value);
|
||||
outHeaders[key] = [value];
|
||||
$putByValDirect(rawHeaders, i++, originalKey);
|
||||
$putByValDirect(rawHeaders, i++, value);
|
||||
}
|
||||
}
|
||||
}
|
||||
object.headers = outHeaders;
|
||||
object.rawHeaders = rawHeaders;
|
||||
function readStart(socket) {
|
||||
if (socket && !socket._paused && socket.readable) socket.resume();
|
||||
}
|
||||
|
||||
function assignHeaders(object, req) {
|
||||
// This fast path is an 8% speedup for a "hello world" node:http server, and a 7% speedup for a "hello world" express server
|
||||
if (assignHeadersFast(req, object, headersTuple)) {
|
||||
const headers = $getInternalField(headersTuple, 0);
|
||||
const rawHeaders = $getInternalField(headersTuple, 1);
|
||||
$putInternalField(headersTuple, 0, undefined);
|
||||
$putInternalField(headersTuple, 1, undefined);
|
||||
object.headers = headers;
|
||||
object.rawHeaders = rawHeaders;
|
||||
return true;
|
||||
} else {
|
||||
assignHeadersSlow(object, req);
|
||||
return false;
|
||||
}
|
||||
function readStop(socket) {
|
||||
if (socket) socket.pause();
|
||||
}
|
||||
|
||||
function onIncomingMessagePauseNodeHTTPResponse(this: IncomingMessage) {
|
||||
const handle = this[kHandle];
|
||||
if (handle && !this.destroyed) {
|
||||
handle.pause();
|
||||
}
|
||||
}
|
||||
/* Abstract base class for ServerRequest and ClientResponse. */
|
||||
function IncomingMessage(socket) {
|
||||
this[Symbol.for("meghan.kind")] = "_http_incoming";
|
||||
|
||||
function onIncomingMessageResumeNodeHTTPResponse(this: IncomingMessage) {
|
||||
const handle = this[kHandle];
|
||||
if (handle && !this.destroyed) {
|
||||
const resumed = handle.resume();
|
||||
if (resumed && resumed !== true) {
|
||||
const bodyReadState = handle.hasBody;
|
||||
if ((bodyReadState & NodeHTTPBodyReadState.done) !== 0) {
|
||||
emitEOFIncomingMessage(this);
|
||||
}
|
||||
this.push(resumed);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function IncomingMessage(req, options = defaultIncomingOpts) {
|
||||
this[abortedSymbol] = false;
|
||||
this[eofInProgress] = false;
|
||||
this._consuming = false;
|
||||
this._dumped = false;
|
||||
this.complete = false;
|
||||
this._closed = false;
|
||||
|
||||
// (url, method, headers, rawHeaders, handle, hasBody)
|
||||
if (req === kHandle) {
|
||||
// BUN: server
|
||||
// (symbol, url, method, headers, rawHeaders, handle, hasBody)
|
||||
if (socket === kHandle) {
|
||||
this[kBunServer] = true;
|
||||
this[abortedSymbol] = false;
|
||||
this[eofInProgress] = false;
|
||||
this._consuming = false;
|
||||
this._dumped = false;
|
||||
this.complete = false;
|
||||
this._closed = false;
|
||||
this[typeSymbol] = NodeHTTPIncomingRequestType.NodeHTTPResponse;
|
||||
this.url = arguments[1];
|
||||
this.method = arguments[2];
|
||||
@@ -127,99 +70,186 @@ function IncomingMessage(req, options = defaultIncomingOpts) {
|
||||
this[fakeSocketSymbol] = arguments[7];
|
||||
Readable.$call(this);
|
||||
|
||||
// If there's a body, pay attention to pause/resume events
|
||||
if (arguments[6]) {
|
||||
this.on("pause", onIncomingMessagePauseNodeHTTPResponse);
|
||||
this.on("resume", onIncomingMessageResumeNodeHTTPResponse);
|
||||
}
|
||||
} else {
|
||||
this[noBodySymbol] = false;
|
||||
Readable.$call(this);
|
||||
var { [typeSymbol]: type } = options || {};
|
||||
|
||||
this[webRequestOrResponse] = req;
|
||||
this[typeSymbol] = type;
|
||||
this[bodyStreamSymbol] = undefined;
|
||||
const statusText = (req as Response)?.statusText;
|
||||
this[statusMessageSymbol] = statusText !== "" ? statusText || null : "";
|
||||
this[statusCodeSymbol] = (req as Response)?.status || 200;
|
||||
this._readableState.readingMore = true;
|
||||
|
||||
if (type === NodeHTTPIncomingRequestType.FetchRequest || type === NodeHTTPIncomingRequestType.FetchResponse) {
|
||||
if (!assignHeaders(this, req)) {
|
||||
this[fakeSocketSymbol] = req;
|
||||
}
|
||||
} else {
|
||||
// Node defaults url and method to null.
|
||||
this.url = "";
|
||||
this.method = null;
|
||||
this.rawHeaders = [];
|
||||
}
|
||||
|
||||
this[noBodySymbol] =
|
||||
type === NodeHTTPIncomingRequestType.FetchRequest // TODO: Add logic for checking for body on response
|
||||
? requestHasNoBody(this.method, this)
|
||||
: false;
|
||||
|
||||
if (getIsNextIncomingMessageHTTPS()) {
|
||||
this.socket.encrypted = true;
|
||||
setIsNextIncomingMessageHTTPS(false);
|
||||
}
|
||||
}
|
||||
|
||||
this._readableState.readingMore = true;
|
||||
}
|
||||
|
||||
function onDataIncomingMessage(
|
||||
this: import("node:http").IncomingMessage,
|
||||
chunk,
|
||||
isLast,
|
||||
aborted: NodeHTTPResponseAbortEvent,
|
||||
) {
|
||||
if (aborted === NodeHTTPResponseAbortEvent.abort) {
|
||||
this.destroy();
|
||||
this.httpVersion = "1.1";
|
||||
this.httpVersionMajor = 1;
|
||||
this.httpVersionMinor = 1;
|
||||
return;
|
||||
}
|
||||
|
||||
if (chunk && !this._dumped) this.push(chunk);
|
||||
this[kBunServer] = false;
|
||||
let streamOptions;
|
||||
|
||||
if (isLast) {
|
||||
emitEOFIncomingMessage(this);
|
||||
if (socket) {
|
||||
streamOptions = {
|
||||
highWaterMark: socket.readableHighWaterMark,
|
||||
};
|
||||
}
|
||||
|
||||
Readable.$call(this, streamOptions);
|
||||
|
||||
this._readableState.readingMore = true;
|
||||
|
||||
this.socket = socket;
|
||||
|
||||
this.httpVersionMajor = null;
|
||||
this.httpVersionMinor = null;
|
||||
this.httpVersion = null;
|
||||
this.complete = false;
|
||||
this[kHeaders] = null;
|
||||
this[kHeadersCount] = 0;
|
||||
this.rawHeaders = [];
|
||||
this[kTrailers] = null;
|
||||
this[kTrailersCount] = 0;
|
||||
this.rawTrailers = [];
|
||||
this.joinDuplicateHeaders = false;
|
||||
this.aborted = false;
|
||||
|
||||
this.upgrade = null;
|
||||
|
||||
// request (server) only
|
||||
this.url = "";
|
||||
this.method = null;
|
||||
|
||||
// response (client) only
|
||||
this.statusCode = null;
|
||||
this.statusMessage = null;
|
||||
this.client = socket;
|
||||
|
||||
this._consuming = false;
|
||||
// Flag for when we decide that this message cannot possibly be
|
||||
// read by the user, so there's no point continuing to handle it.
|
||||
this._dumped = false;
|
||||
}
|
||||
$toClass(IncomingMessage, "IncomingMessage", Readable);
|
||||
|
||||
const IncomingMessagePrototype = {
|
||||
constructor: IncomingMessage,
|
||||
__proto__: Readable.prototype,
|
||||
httpVersion: "1.1",
|
||||
_construct(callback) {
|
||||
// TODO: streaming
|
||||
const type = this[typeSymbol];
|
||||
ObjectDefineProperty(IncomingMessage.prototype, "connection", {
|
||||
__proto__: null,
|
||||
get: function () {
|
||||
if (this[kBunServer]) {
|
||||
return (this[fakeSocketSymbol] ??= new FakeSocket(this));
|
||||
}
|
||||
return this.socket;
|
||||
},
|
||||
set: function (val) {
|
||||
if (this[kBunServer]) {
|
||||
this[fakeSocketSymbol] = val;
|
||||
return;
|
||||
}
|
||||
this.socket = val;
|
||||
},
|
||||
});
|
||||
|
||||
if (type === NodeHTTPIncomingRequestType.FetchResponse) {
|
||||
if (!webRequestOrResponseHasBodyValue(this[webRequestOrResponse])) {
|
||||
this.complete = true;
|
||||
this.push(null);
|
||||
ObjectDefineProperty(IncomingMessage.prototype, "headers", {
|
||||
__proto__: null,
|
||||
get: function () {
|
||||
if (!this[kHeaders]) {
|
||||
this[kHeaders] = {};
|
||||
|
||||
const src = this.rawHeaders;
|
||||
const dst = this[kHeaders];
|
||||
|
||||
for (let n = 0; n < this[kHeadersCount]; n += 2) {
|
||||
this._addHeaderLine(src[n + 0], src[n + 1], dst);
|
||||
}
|
||||
}
|
||||
return this[kHeaders];
|
||||
},
|
||||
set: function (val) {
|
||||
this[kHeaders] = val;
|
||||
},
|
||||
});
|
||||
|
||||
callback();
|
||||
},
|
||||
// Call this instead of resume() if we want to just
|
||||
// dump all the data to /dev/null
|
||||
_dump() {
|
||||
if (!this._dumped) {
|
||||
this._dumped = true;
|
||||
// If there is buffered data, it may trigger 'data' events.
|
||||
// Remove 'data' event listeners explicitly.
|
||||
this.removeAllListeners("data");
|
||||
const handle = this[kHandle];
|
||||
if (handle) {
|
||||
handle.ondata = undefined;
|
||||
ObjectDefineProperty(IncomingMessage.prototype, "headersDistinct", {
|
||||
__proto__: null,
|
||||
get: function () {
|
||||
if (!this[kHeadersDistinct]) {
|
||||
this[kHeadersDistinct] = {};
|
||||
|
||||
const src = this.rawHeaders;
|
||||
const dst = this[kHeadersDistinct];
|
||||
|
||||
for (let n = 0; n < this[kHeadersCount]; n += 2) {
|
||||
this._addHeaderLineDistinct(src[n + 0], src[n + 1], dst);
|
||||
}
|
||||
this.resume();
|
||||
}
|
||||
return this[kHeadersDistinct];
|
||||
},
|
||||
_read(_size) {
|
||||
set: function (val) {
|
||||
this[kHeadersDistinct] = val;
|
||||
},
|
||||
});
|
||||
|
||||
ObjectDefineProperty(IncomingMessage.prototype, "trailers", {
|
||||
__proto__: null,
|
||||
get: function () {
|
||||
if (this[kBunServer]) {
|
||||
return kEmptyObject;
|
||||
}
|
||||
if (!this[kTrailers]) {
|
||||
this[kTrailers] = {};
|
||||
|
||||
const src = this.rawTrailers;
|
||||
const dst = this[kTrailers];
|
||||
|
||||
for (let n = 0; n < this[kTrailersCount]; n += 2) {
|
||||
this._addHeaderLine(src[n + 0], src[n + 1], dst);
|
||||
}
|
||||
}
|
||||
return this[kTrailers];
|
||||
},
|
||||
set: function (val) {
|
||||
if (this[kBunServer]) {
|
||||
return;
|
||||
}
|
||||
this[kTrailers] = val;
|
||||
},
|
||||
});
|
||||
|
||||
ObjectDefineProperty(IncomingMessage.prototype, "trailersDistinct", {
|
||||
__proto__: null,
|
||||
get: function () {
|
||||
if (!this[kTrailersDistinct]) {
|
||||
this[kTrailersDistinct] = {};
|
||||
|
||||
const src = this.rawTrailers;
|
||||
const dst = this[kTrailersDistinct];
|
||||
|
||||
for (let n = 0; n < this[kTrailersCount]; n += 2) {
|
||||
this._addHeaderLineDistinct(src[n + 0], src[n + 1], dst);
|
||||
}
|
||||
}
|
||||
return this[kTrailersDistinct];
|
||||
},
|
||||
set: function (val) {
|
||||
this[kTrailersDistinct] = val;
|
||||
},
|
||||
});
|
||||
|
||||
IncomingMessage.prototype.setTimeout = function setTimeout(msecs, callback) {
|
||||
if (this[kBunServer]) {
|
||||
this.take;
|
||||
const req = this[kHandle] || this[webRequestOrResponse];
|
||||
if (req) {
|
||||
setRequestTimeout(req, Math.ceil(msecs / 1000));
|
||||
typeof callback === "function" && this.once("timeout", callback);
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
if (callback) this.on("timeout", callback);
|
||||
this.socket.setTimeout(msecs);
|
||||
return this;
|
||||
};
|
||||
|
||||
IncomingMessage.prototype._read = function _read(n) {
|
||||
if (this[kBunServer]) {
|
||||
if (!this._consuming) {
|
||||
this._readableState.readingMore = false;
|
||||
this._consuming = true;
|
||||
@@ -288,13 +318,25 @@ const IncomingMessagePrototype = {
|
||||
this[bodyStreamSymbol] = reader;
|
||||
consumeStream(this, reader);
|
||||
}
|
||||
|
||||
return;
|
||||
},
|
||||
_finish() {
|
||||
this.emit("prefinish");
|
||||
},
|
||||
_destroy: function IncomingMessage_destroy(err, cb) {
|
||||
}
|
||||
|
||||
if (!this._consuming) {
|
||||
this._readableState.readingMore = false;
|
||||
this._consuming = true;
|
||||
}
|
||||
|
||||
// We actually do almost nothing here, because the parserOnBody
|
||||
// function fills up our internal buffer directly. However, we
|
||||
// do need to unpause the underlying socket so that it flows.
|
||||
if (this.socket.readable) readStart(this.socket);
|
||||
};
|
||||
|
||||
// It's possible that the socket will be destroyed, and removed from
|
||||
// any messages, before ever calling this. In that case, just skip
|
||||
// it, since something else is destroying this connection anyway.
|
||||
IncomingMessage.prototype._destroy = function _destroy(err, cb) {
|
||||
if (this[kBunServer]) {
|
||||
const shouldEmitAborted = !this.readableEnded || !this.complete;
|
||||
|
||||
if (shouldEmitAborted) {
|
||||
@@ -341,95 +383,369 @@ const IncomingMessagePrototype = {
|
||||
if ($isCallable(cb)) {
|
||||
emitErrorNextTickIfErrorListenerNT(this, err, cb);
|
||||
}
|
||||
},
|
||||
get aborted() {
|
||||
return this[abortedSymbol];
|
||||
},
|
||||
set aborted(value) {
|
||||
this[abortedSymbol] = value;
|
||||
},
|
||||
get connection() {
|
||||
return (this[fakeSocketSymbol] ??= new FakeSocket(this));
|
||||
},
|
||||
get statusCode() {
|
||||
return this[statusCodeSymbol];
|
||||
},
|
||||
set statusCode(value) {
|
||||
if (!(value in STATUS_CODES)) return;
|
||||
this[statusCodeSymbol] = value;
|
||||
},
|
||||
get statusMessage() {
|
||||
return this[statusMessageSymbol];
|
||||
},
|
||||
set statusMessage(value) {
|
||||
this[statusMessageSymbol] = value;
|
||||
},
|
||||
get httpVersionMajor() {
|
||||
const version = this.httpVersion;
|
||||
if (version.startsWith("1.")) {
|
||||
return 1;
|
||||
}
|
||||
return 0;
|
||||
},
|
||||
set httpVersionMajor(value) {
|
||||
// noop
|
||||
},
|
||||
get httpVersionMinor() {
|
||||
const version = this.httpVersion;
|
||||
if (version.endsWith(".1")) {
|
||||
return 1;
|
||||
}
|
||||
return 0;
|
||||
},
|
||||
set httpVersionMinor(value) {
|
||||
// noop
|
||||
},
|
||||
get rawTrailers() {
|
||||
return [];
|
||||
},
|
||||
set rawTrailers(value) {
|
||||
// noop
|
||||
},
|
||||
get trailers() {
|
||||
return kEmptyObject;
|
||||
},
|
||||
set trailers(value) {
|
||||
// noop
|
||||
},
|
||||
setTimeout(msecs, callback) {
|
||||
void this.take;
|
||||
const req = this[kHandle] || this[webRequestOrResponse];
|
||||
return;
|
||||
}
|
||||
|
||||
if (req) {
|
||||
setRequestTimeout(req, Math.ceil(msecs / 1000));
|
||||
if (typeof callback === "function") this.once("timeout", callback);
|
||||
if (!this.readableEnded || !this.complete) {
|
||||
this.aborted = true;
|
||||
this.emit("aborted");
|
||||
}
|
||||
|
||||
// If aborted and the underlying socket is not already destroyed,
|
||||
// destroy it.
|
||||
// We have to check if the socket is already destroyed because finished
|
||||
// does not call the callback when this method is invoked from `_http_client`
|
||||
// in `test/parallel/test-http-client-spurious-aborted.js`
|
||||
if (this.socket && !this.socket.destroyed && this.aborted) {
|
||||
this.socket.destroy(err);
|
||||
const cleanup = finished(this.socket, e => {
|
||||
if (e?.code === "ERR_STREAM_PREMATURE_CLOSE") {
|
||||
e = null;
|
||||
}
|
||||
cleanup();
|
||||
process.nextTick(onError, this, e || err, cb);
|
||||
});
|
||||
} else {
|
||||
process.nextTick(onError, this, err, cb);
|
||||
}
|
||||
};
|
||||
|
||||
IncomingMessage.prototype._addHeaderLines = function (headers, n) {
|
||||
$assert(!this[kBunServer]);
|
||||
|
||||
if (headers?.length) {
|
||||
let dest;
|
||||
if (this.complete) {
|
||||
this.rawTrailers = headers;
|
||||
this[kTrailersCount] = n;
|
||||
dest = this[kTrailers];
|
||||
} else {
|
||||
this.rawHeaders = headers;
|
||||
this[kHeadersCount] = n;
|
||||
dest = this[kHeaders];
|
||||
}
|
||||
return this;
|
||||
},
|
||||
get socket() {
|
||||
return (this[fakeSocketSymbol] ??= new FakeSocket(this));
|
||||
},
|
||||
set socket(value) {
|
||||
this[fakeSocketSymbol] = value;
|
||||
},
|
||||
} satisfies typeof import("node:http").IncomingMessage.prototype;
|
||||
IncomingMessage.prototype = IncomingMessagePrototype;
|
||||
$setPrototypeDirect.$call(IncomingMessage, Readable);
|
||||
|
||||
function requestHasNoBody(method, req) {
|
||||
if ("GET" === method || "HEAD" === method || "TRACE" === method || "CONNECT" === method || "OPTIONS" === method)
|
||||
return true;
|
||||
const headers = req?.headers;
|
||||
const contentLength = headers?.["content-length"];
|
||||
if (!parseInt(contentLength, 10)) return true;
|
||||
if (dest) {
|
||||
for (let i = 0; i < n; i += 2) {
|
||||
this._addHeaderLine(headers[i], headers[i + 1], dest);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
return false;
|
||||
// This function is used to help avoid the lowercasing of a field name if it
|
||||
// matches a 'traditional cased' version of a field name. It then returns the
|
||||
// lowercased name to both avoid calling toLowerCase() a second time and to
|
||||
// indicate whether the field was a 'no duplicates' field. If a field is not a
|
||||
// 'no duplicates' field, a `0` byte is prepended as a flag. The one exception
|
||||
// to this is the Set-Cookie header which is indicated by a `1` byte flag, since
|
||||
// it is an 'array' field and thus is treated differently in _addHeaderLines().
|
||||
// TODO: perhaps http_parser could be returning both raw and lowercased versions
|
||||
// of known header names to avoid us having to call toLowerCase() for those
|
||||
// headers.
|
||||
function matchKnownFields(field, lowercased = false) {
|
||||
switch (field.length) {
|
||||
case 3:
|
||||
if (field === "Age" || field === "age") return "age";
|
||||
break;
|
||||
case 4:
|
||||
if (field === "Host" || field === "host") return "host";
|
||||
if (field === "From" || field === "from") return "from";
|
||||
if (field === "ETag" || field === "etag") return "etag";
|
||||
if (field === "Date" || field === "date") return "\u0000date";
|
||||
if (field === "Vary" || field === "vary") return "\u0000vary";
|
||||
break;
|
||||
case 6:
|
||||
if (field === "Server" || field === "server") return "server";
|
||||
if (field === "Cookie" || field === "cookie") return "\u0002cookie";
|
||||
if (field === "Origin" || field === "origin") return "\u0000origin";
|
||||
if (field === "Expect" || field === "expect") return "\u0000expect";
|
||||
if (field === "Accept" || field === "accept") return "\u0000accept";
|
||||
break;
|
||||
case 7:
|
||||
if (field === "Referer" || field === "referer") return "referer";
|
||||
if (field === "Expires" || field === "expires") return "expires";
|
||||
if (field === "Upgrade" || field === "upgrade") return "\u0000upgrade";
|
||||
break;
|
||||
case 8:
|
||||
if (field === "Location" || field === "location") return "location";
|
||||
if (field === "If-Match" || field === "if-match") return "\u0000if-match";
|
||||
break;
|
||||
case 10:
|
||||
if (field === "User-Agent" || field === "user-agent") return "user-agent";
|
||||
if (field === "Set-Cookie" || field === "set-cookie") return "\u0001";
|
||||
if (field === "Connection" || field === "connection") return "\u0000connection";
|
||||
break;
|
||||
case 11:
|
||||
if (field === "Retry-After" || field === "retry-after") return "retry-after";
|
||||
break;
|
||||
case 12:
|
||||
if (field === "Content-Type" || field === "content-type") return "content-type";
|
||||
if (field === "Max-Forwards" || field === "max-forwards") return "max-forwards";
|
||||
break;
|
||||
case 13:
|
||||
if (field === "Authorization" || field === "authorization") return "authorization";
|
||||
if (field === "Last-Modified" || field === "last-modified") return "last-modified";
|
||||
if (field === "Cache-Control" || field === "cache-control") return "\u0000cache-control";
|
||||
if (field === "If-None-Match" || field === "if-none-match") return "\u0000if-none-match";
|
||||
break;
|
||||
case 14:
|
||||
if (field === "Content-Length" || field === "content-length") return "content-length";
|
||||
break;
|
||||
case 15:
|
||||
if (field === "Accept-Encoding" || field === "accept-encoding") return "\u0000accept-encoding";
|
||||
if (field === "Accept-Language" || field === "accept-language") return "\u0000accept-language";
|
||||
if (field === "X-Forwarded-For" || field === "x-forwarded-for") return "\u0000x-forwarded-for";
|
||||
break;
|
||||
case 16:
|
||||
if (field === "Content-Encoding" || field === "content-encoding") return "\u0000content-encoding";
|
||||
if (field === "X-Forwarded-Host" || field === "x-forwarded-host") return "\u0000x-forwarded-host";
|
||||
break;
|
||||
case 17:
|
||||
if (field === "If-Modified-Since" || field === "if-modified-since") return "if-modified-since";
|
||||
if (field === "Transfer-Encoding" || field === "transfer-encoding") return "\u0000transfer-encoding";
|
||||
if (field === "X-Forwarded-Proto" || field === "x-forwarded-proto") return "\u0000x-forwarded-proto";
|
||||
break;
|
||||
case 19:
|
||||
if (field === "Proxy-Authorization" || field === "proxy-authorization") return "proxy-authorization";
|
||||
if (field === "If-Unmodified-Since" || field === "if-unmodified-since") return "if-unmodified-since";
|
||||
break;
|
||||
}
|
||||
if (lowercased) {
|
||||
return "\u0000" + field;
|
||||
}
|
||||
return matchKnownFields(field.toLowerCase(), true);
|
||||
}
|
||||
|
||||
// Add the given (field, value) pair to the message
|
||||
//
|
||||
// Per RFC2616, section 4.2 it is acceptable to join multiple instances of the
|
||||
// same header with a ', ' if the header in question supports specification of
|
||||
// multiple values this way. The one exception to this is the Cookie header,
|
||||
// which has multiple values joined with a '; ' instead. If a header's values
|
||||
// cannot be joined in either of these ways, we declare the first instance the
|
||||
// winner and drop the second. Extended header fields (those beginning with
|
||||
// 'x-') are always joined.
|
||||
IncomingMessage.prototype._addHeaderLine = function (field, value, dest) {
|
||||
$assert(!this[kBunServer]);
|
||||
|
||||
field = matchKnownFields(field);
|
||||
const flag = field.charCodeAt(0);
|
||||
if (flag === 0 || flag === 2) {
|
||||
field = field.slice(1);
|
||||
// Make a delimited list
|
||||
if (typeof dest[field] === "string") {
|
||||
dest[field] += (flag === 0 ? ", " : "; ") + value;
|
||||
} else {
|
||||
dest[field] = value;
|
||||
}
|
||||
} else if (flag === 1) {
|
||||
// Array header -- only Set-Cookie at the moment
|
||||
if (dest["set-cookie"] !== undefined) {
|
||||
dest["set-cookie"].push(value);
|
||||
} else {
|
||||
dest["set-cookie"] = [value];
|
||||
}
|
||||
} else if (this.joinDuplicateHeaders) {
|
||||
// RFC 9110 https://www.rfc-editor.org/rfc/rfc9110#section-5.2
|
||||
// https://github.com/nodejs/node/issues/45699
|
||||
// allow authorization multiple fields
|
||||
// Make a delimited list
|
||||
if (dest[field] === undefined) {
|
||||
dest[field] = value;
|
||||
} else {
|
||||
dest[field] += ", " + value;
|
||||
}
|
||||
} else if (dest[field] === undefined) {
|
||||
// Drop duplicates
|
||||
dest[field] = value;
|
||||
}
|
||||
};
|
||||
|
||||
IncomingMessage.prototype._addHeaderLineDistinct = function (field, value, dest) {
|
||||
$assert(!this[kBunServer]);
|
||||
|
||||
field = field.toLowerCase();
|
||||
if (!dest[field]) {
|
||||
dest[field] = [value];
|
||||
} else {
|
||||
dest[field].push(value);
|
||||
}
|
||||
};
|
||||
|
||||
// Call this instead of resume() if we want to just
|
||||
// dump all the data to /dev/null
|
||||
IncomingMessage.prototype._dump = function _dump() {
|
||||
if (this[kBunServer]) {
|
||||
if (!this._dumped) {
|
||||
this._dumped = true;
|
||||
// If there is buffered data, it may trigger 'data' events.
|
||||
// Remove 'data' event listeners explicitly.
|
||||
this.removeAllListeners("data");
|
||||
const handle = this[kHandle];
|
||||
if (handle) {
|
||||
handle.ondata = undefined;
|
||||
}
|
||||
this.resume();
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (!this._dumped) {
|
||||
this._dumped = true;
|
||||
// If there is buffered data, it may trigger 'data' events.
|
||||
// Remove 'data' event listeners explicitly.
|
||||
this.removeAllListeners("data");
|
||||
this.resume();
|
||||
}
|
||||
};
|
||||
|
||||
function onError(self, error, cb) {
|
||||
// This is to keep backward compatible behavior.
|
||||
// An error is emitted only if there are listeners attached to the event.
|
||||
if (self.listenerCount("error") === 0) {
|
||||
cb();
|
||||
} else {
|
||||
cb(error);
|
||||
}
|
||||
}
|
||||
|
||||
// BUN: server extras
|
||||
ObjectDefineProperty(IncomingMessage.prototype, "socket", {
|
||||
get() {
|
||||
if (this[kBunServer]) {
|
||||
return (this[fakeSocketSymbol] ??= new FakeSocket(this));
|
||||
}
|
||||
return this.__socket;
|
||||
},
|
||||
set(value) {
|
||||
if (this[kBunServer]) {
|
||||
this[fakeSocketSymbol] = value;
|
||||
return;
|
||||
}
|
||||
this.__socket = value;
|
||||
return;
|
||||
},
|
||||
});
|
||||
|
||||
ObjectDefineProperty(IncomingMessage.prototype, "rawTrailers", {
|
||||
get() {
|
||||
if (this[kBunServer]) {
|
||||
return [];
|
||||
}
|
||||
return this.__rawTrailers;
|
||||
},
|
||||
set(value) {
|
||||
if (this[kBunServer]) {
|
||||
return;
|
||||
}
|
||||
this.__rawTrailers = value;
|
||||
return;
|
||||
},
|
||||
});
|
||||
|
||||
ObjectDefineProperty(IncomingMessage.prototype, "aborted", {
|
||||
get() {
|
||||
if (this[kBunServer]) {
|
||||
return this[abortedSymbol];
|
||||
}
|
||||
return this.__aborted;
|
||||
},
|
||||
set(value) {
|
||||
if (this[kBunServer]) {
|
||||
this[abortedSymbol] = value;
|
||||
return;
|
||||
}
|
||||
this.__aborted = value;
|
||||
return;
|
||||
},
|
||||
});
|
||||
|
||||
ObjectDefineProperty(IncomingMessage.prototype, "statusCode", {
|
||||
get() {
|
||||
if (this[kBunServer]) {
|
||||
return this[statusCodeSymbol];
|
||||
}
|
||||
return this.__statusCode;
|
||||
},
|
||||
set(value) {
|
||||
if (this[kBunServer]) {
|
||||
if (!(value in STATUS_CODES)) return;
|
||||
this[statusCodeSymbol] = value;
|
||||
return;
|
||||
}
|
||||
this.__statusCode = value;
|
||||
return;
|
||||
},
|
||||
});
|
||||
|
||||
ObjectDefineProperty(IncomingMessage.prototype, "statusMessage", {
|
||||
get() {
|
||||
if (this[kBunServer]) {
|
||||
return this[statusMessageSymbol];
|
||||
}
|
||||
return this.__statusMessage;
|
||||
},
|
||||
set(value) {
|
||||
if (this[kBunServer]) {
|
||||
this[statusMessageSymbol] = value;
|
||||
return;
|
||||
}
|
||||
this.__statusMessage = value;
|
||||
return;
|
||||
},
|
||||
});
|
||||
|
||||
IncomingMessage.prototype._construct = function (callback) {
|
||||
if (this[kBunServer]) {
|
||||
// TODO: streaming
|
||||
const type = this[typeSymbol];
|
||||
if (type === NodeHTTPIncomingRequestType.FetchResponse) {
|
||||
if (!webRequestOrResponseHasBodyValue(this[webRequestOrResponse])) {
|
||||
this.complete = true;
|
||||
this.push(null);
|
||||
}
|
||||
}
|
||||
callback();
|
||||
return;
|
||||
}
|
||||
callback();
|
||||
};
|
||||
|
||||
function onIncomingMessagePauseNodeHTTPResponse(this: import("node:http").IncomingMessage) {
|
||||
const handle = this[kHandle];
|
||||
if (handle && !this.destroyed) {
|
||||
handle.pause();
|
||||
}
|
||||
}
|
||||
|
||||
function onIncomingMessageResumeNodeHTTPResponse(this: import("node:http").IncomingMessage) {
|
||||
const handle = this[kHandle];
|
||||
if (handle && !this.destroyed) {
|
||||
const resumed = handle.resume();
|
||||
if (resumed && resumed !== true) {
|
||||
const bodyReadState = handle.hasBody;
|
||||
if ((bodyReadState & NodeHTTPBodyReadState.done) !== 0) {
|
||||
emitEOFIncomingMessage(this);
|
||||
}
|
||||
this.push(resumed);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function onDataIncomingMessage(this: import("node:http").IncomingMessage, chunk, isLast, aborted) {
|
||||
if (aborted === NodeHTTPResponseAbortEvent.abort) {
|
||||
this.destroy();
|
||||
return;
|
||||
}
|
||||
if (chunk && !this._dumped) this.push(chunk);
|
||||
if (isLast) emitEOFIncomingMessage(this);
|
||||
}
|
||||
|
||||
async function consumeStream(self, reader: ReadableStreamDefaultReader) {
|
||||
var done = false,
|
||||
value,
|
||||
aborted = false;
|
||||
var done = false;
|
||||
var value;
|
||||
var aborted = false;
|
||||
try {
|
||||
while (true) {
|
||||
const result = reader.readMany();
|
||||
@@ -438,19 +754,9 @@ async function consumeStream(self, reader: ReadableStreamDefaultReader) {
|
||||
} else {
|
||||
({ done, value } = result);
|
||||
}
|
||||
|
||||
if (self.destroyed || (aborted = self[abortedSymbol])) {
|
||||
break;
|
||||
}
|
||||
if (!self._dumped) {
|
||||
for (var v of value) {
|
||||
self.push(v);
|
||||
}
|
||||
}
|
||||
|
||||
if (self.destroyed || (aborted = self[abortedSymbol]) || done) {
|
||||
break;
|
||||
}
|
||||
if (self.destroyed || (aborted = self[abortedSymbol])) break;
|
||||
if (!self._dumped) for (var v of value) self.push(v);
|
||||
if (self.destroyed || (aborted = self[abortedSymbol]) || done) break;
|
||||
}
|
||||
} catch (err) {
|
||||
if (aborted || self.destroyed) return;
|
||||
@@ -458,22 +764,13 @@ async function consumeStream(self, reader: ReadableStreamDefaultReader) {
|
||||
} finally {
|
||||
reader?.cancel?.().catch?.(nop);
|
||||
}
|
||||
|
||||
if (!self.complete) {
|
||||
emitEOFIncomingMessage(self);
|
||||
}
|
||||
}
|
||||
|
||||
function readStart(socket) {
|
||||
if (socket && !socket._paused && socket.readable) {
|
||||
socket.resume();
|
||||
}
|
||||
}
|
||||
|
||||
function readStop(socket) {
|
||||
if (socket) {
|
||||
socket.pause();
|
||||
}
|
||||
}
|
||||
|
||||
export { IncomingMessage, readStart, readStop };
|
||||
export default {
|
||||
IncomingMessage,
|
||||
readStart,
|
||||
readStop,
|
||||
};
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -27,7 +27,6 @@ const {
|
||||
setIsNextIncomingMessageHTTPS,
|
||||
callCloseCallback,
|
||||
emitCloseNT,
|
||||
ConnResetException,
|
||||
NodeHTTPResponseAbortEvent,
|
||||
STATUS_CODES,
|
||||
isTlsSymbol,
|
||||
@@ -43,25 +42,27 @@ const {
|
||||
setServerIdleTimeout,
|
||||
setServerCustomOptions,
|
||||
getMaxHTTPHeaderSize,
|
||||
kBunServer,
|
||||
} = require("internal/http");
|
||||
const NumberIsNaN = Number.isNaN;
|
||||
|
||||
const { format } = require("internal/util/inspect");
|
||||
const { ConnResetException } = require("internal/shared");
|
||||
|
||||
const { IncomingMessage } = require("node:_http_incoming");
|
||||
const { OutgoingMessage } = require("node:_http_outgoing");
|
||||
const { kIncomingMessage } = require("node:_http_common");
|
||||
const kConnectionsCheckingInterval = Symbol("http.server.connectionsCheckingInterval");
|
||||
const { kIncomingMessage, chunkExpression } = require("node:_http_common");
|
||||
|
||||
const getBunServerAllClosedPromise = $newZigFunction("node_http_binding.zig", "getBunServerAllClosedPromise", 1);
|
||||
const sendHelper = $newZigFunction("node_cluster_binding.zig", "sendHelperChild", 3);
|
||||
|
||||
const kServerResponse = Symbol("ServerResponse");
|
||||
const kRejectNonStandardBodyWrites = Symbol("kRejectNonStandardBodyWrites");
|
||||
const kConnectionsCheckingInterval = Symbol("http.server.connectionsCheckingInterval");
|
||||
const GlobalPromise = globalThis.Promise;
|
||||
const kEmptyBuffer = Buffer.alloc(0);
|
||||
const ObjectKeys = Object.keys;
|
||||
const MathMin = Math.min;
|
||||
const NumberIsNaN = Number.isNaN;
|
||||
|
||||
let cluster;
|
||||
|
||||
@@ -85,7 +86,7 @@ function setCloseCallback(self, callback) {
|
||||
|
||||
function assignSocketInternal(self, socket) {
|
||||
if (socket._httpMessage) {
|
||||
throw $ERR_HTTP_SOCKET_ASSIGNED("Socket already assigned");
|
||||
throw $ERR_HTTP_SOCKET_ASSIGNED();
|
||||
}
|
||||
socket._httpMessage = self;
|
||||
setCloseCallback(socket, onServerResponseClose);
|
||||
@@ -193,6 +194,7 @@ function emitListeningNextTick(self, hostname, port) {
|
||||
}
|
||||
}
|
||||
|
||||
type Server = import("node:http").Server;
|
||||
function Server(options, callback): void {
|
||||
if (!(this instanceof Server)) return new Server(options, callback);
|
||||
EventEmitter.$call(this);
|
||||
@@ -548,6 +550,7 @@ Server.prototype[kRealListen] = function (tls, port, host, socketPath, reusePort
|
||||
socket[kEnableStreaming](false);
|
||||
|
||||
const http_res = new ResponseClass(http_req, {
|
||||
[kBunServer]: true,
|
||||
[kHandle]: handle,
|
||||
[kRejectNonStandardBodyWrites]: server.rejectNonStandardBodyWrites,
|
||||
});
|
||||
@@ -754,6 +757,7 @@ function onServerRequestEvent(this: NodeHTTPServerSocket, event: NodeHTTPRespons
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// uWS::HttpParserError
|
||||
enum HttpParserError {
|
||||
HTTP_PARSER_ERROR_NONE = 0,
|
||||
@@ -773,10 +777,10 @@ function onServerClientError(ssl: boolean, socket: unknown, errorCode: number, r
|
||||
let err;
|
||||
switch (errorCode) {
|
||||
case HttpParserError.HTTP_PARSER_ERROR_INVALID_CONTENT_LENGTH:
|
||||
err = $HPE_UNEXPECTED_CONTENT_LENGTH("Parse Error");
|
||||
err = $HPE_UNEXPECTED_CONTENT_LENGTH("Parse Error: Invalid Content-Length");
|
||||
break;
|
||||
case HttpParserError.HTTP_PARSER_ERROR_INVALID_TRANSFER_ENCODING:
|
||||
err = $HPE_INVALID_TRANSFER_ENCODING("Parse Error");
|
||||
err = $HPE_INVALID_TRANSFER_ENCODING("Parse Error: Invalid Transfer-Encoding");
|
||||
break;
|
||||
case HttpParserError.HTTP_PARSER_ERROR_INVALID_EOF:
|
||||
err = $HPE_INVALID_EOF_STATE("Parse Error");
|
||||
@@ -1148,7 +1152,7 @@ function _writeHead(statusCode, reason, obj, response) {
|
||||
(response.chunkedEncoding !== true || response.hasHeader("content-length")) &&
|
||||
(response._trailer || response.hasHeader("trailer"))
|
||||
) {
|
||||
throw $ERR_HTTP_TRAILER_INVALID("Trailers are invalid with this transfer encoding");
|
||||
throw $ERR_HTTP_TRAILER_INVALID();
|
||||
}
|
||||
// Headers in obj should override previous headers but still
|
||||
// allow explicit duplicates. To do so, we first remove any
|
||||
@@ -1184,7 +1188,7 @@ function _writeHead(statusCode, reason, obj, response) {
|
||||
} else {
|
||||
response.removeHeader("content-length");
|
||||
}
|
||||
throw $ERR_HTTP_TRAILER_INVALID("Trailers are invalid with this transfer encoding");
|
||||
throw $ERR_HTTP_TRAILER_INVALID();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1194,8 +1198,8 @@ function _writeHead(statusCode, reason, obj, response) {
|
||||
Object.defineProperty(NodeHTTPServerSocket, "name", { value: "Socket" });
|
||||
|
||||
function ServerResponse(req, options): void {
|
||||
if (!(this instanceof ServerResponse)) return new ServerResponse(req, options);
|
||||
OutgoingMessage.$call(this, options);
|
||||
this[Symbol.for("meghan.kind")] = "_http_server";
|
||||
OutgoingMessage.$call(this, { ...options, [kBunServer]: true });
|
||||
|
||||
this.useChunkedEncodingByDefault = true;
|
||||
|
||||
@@ -1206,21 +1210,17 @@ function ServerResponse(req, options): void {
|
||||
this.write = ServerResponse_writeDeprecated;
|
||||
this.end = ServerResponse_finalDeprecated;
|
||||
}
|
||||
|
||||
this.req = req;
|
||||
this.sendDate = true;
|
||||
this._sent100 = false;
|
||||
this[headerStateSymbol] = NodeHTTPHeaderState.none;
|
||||
this[kPendingCallbacks] = [];
|
||||
this.finished = false;
|
||||
|
||||
// this is matching node's behaviour
|
||||
// https://github.com/nodejs/node/blob/cf8c6994e0f764af02da4fa70bc5962142181bf3/lib/_http_server.js#L192
|
||||
if (req.method === "HEAD") this._hasBody = false;
|
||||
|
||||
if (options) {
|
||||
const handle = options[kHandle];
|
||||
|
||||
if (handle) {
|
||||
this[kHandle] = handle;
|
||||
} else {
|
||||
@@ -1297,6 +1297,7 @@ ServerResponse.prototype.writeProcessing = function (cb) {
|
||||
ServerResponse.prototype.writeContinue = function (cb) {
|
||||
this.socket[kHandle]?.response?.writeContinue();
|
||||
cb?.();
|
||||
this._sent100 = true;
|
||||
};
|
||||
|
||||
// This end method is actually on the OutgoingMessage prototype in Node.js
|
||||
@@ -1410,6 +1411,7 @@ Object.defineProperty(ServerResponse.prototype, "writable", {
|
||||
get() {
|
||||
return !this._ended || !hasServerResponseFinished(this);
|
||||
},
|
||||
set() {},
|
||||
});
|
||||
|
||||
ServerResponse.prototype.write = function (chunk, encoding, callback) {
|
||||
@@ -1514,13 +1516,10 @@ ServerResponse.prototype.detachSocket = function (socket) {
|
||||
socket.removeListener("close", onServerResponseClose);
|
||||
socket._httpMessage = null;
|
||||
}
|
||||
|
||||
this.socket = null;
|
||||
};
|
||||
|
||||
ServerResponse.prototype._implicitHeader = function () {
|
||||
if (this.headersSent) return;
|
||||
// @ts-ignore
|
||||
this.writeHead(this.statusCode);
|
||||
};
|
||||
|
||||
@@ -1573,18 +1572,19 @@ ServerResponse.prototype._send = function (data, encoding, callback, _byteLength
|
||||
|
||||
ServerResponse.prototype.writeHead = function (statusCode, statusMessage, headers) {
|
||||
if (this.headersSent) {
|
||||
throw $ERR_HTTP_HEADERS_SENT("writeHead");
|
||||
throw $ERR_HTTP_HEADERS_SENT("write");
|
||||
}
|
||||
_writeHead(statusCode, statusMessage, headers, this);
|
||||
|
||||
this[headerStateSymbol] = NodeHTTPHeaderState.assigned;
|
||||
this._header = " ";
|
||||
|
||||
return this;
|
||||
};
|
||||
|
||||
ServerResponse.prototype.assignSocket = function (socket) {
|
||||
if (socket._httpMessage) {
|
||||
throw $ERR_HTTP_SOCKET_ASSIGNED("Socket already assigned");
|
||||
throw $ERR_HTTP_SOCKET_ASSIGNED();
|
||||
}
|
||||
socket._httpMessage = this;
|
||||
socket.once("close", onServerResponseClose);
|
||||
|
||||
@@ -884,7 +884,7 @@ class Http2ServerResponse extends Stream {
|
||||
stream.additionalHeaders({
|
||||
...headers,
|
||||
[HTTP2_HEADER_STATUS]: HTTP_STATUS_EARLY_HINTS,
|
||||
"Link": linkHeaderValue,
|
||||
Link: linkHeaderValue,
|
||||
});
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -1,26 +1,175 @@
|
||||
// Hardcoded module "node:https"
|
||||
const http = require("node:http");
|
||||
const { urlToHttpOptions } = require("internal/url");
|
||||
const tls = require("node:tls");
|
||||
const { urlToHttpOptions, isURL } = require("internal/url");
|
||||
const { kEmptyObject } = require("internal/shared");
|
||||
|
||||
const JSONStringify = JSON.stringify;
|
||||
const ArrayPrototypeShift = Array.prototype.shift;
|
||||
const ObjectAssign = Object.assign;
|
||||
const ArrayPrototypePush = Array.prototype.push;
|
||||
const ArrayPrototypeIndexOf = Array.prototype.indexOf;
|
||||
const ArrayPrototypeSplice = Array.prototype.splice;
|
||||
const ArrayPrototypeUnshift = Array.prototype.unshift;
|
||||
const ObjectAssign = Object.assign;
|
||||
const ReflectConstruct = Reflect.construct;
|
||||
|
||||
function createConnection(port, host, options) {
|
||||
if (port !== null && typeof port === "object") {
|
||||
options = port;
|
||||
} else if (host !== null && typeof host === "object") {
|
||||
options = { ...host };
|
||||
} else if (options === null || typeof options !== "object") {
|
||||
options = {};
|
||||
} else {
|
||||
options = { ...options };
|
||||
}
|
||||
|
||||
if (typeof port === "number") {
|
||||
options.port = port;
|
||||
}
|
||||
|
||||
if (typeof host === "string") {
|
||||
options.host = host;
|
||||
}
|
||||
|
||||
$debug("createConnection", options);
|
||||
|
||||
if (options._agentKey) {
|
||||
const session = this._getSession(options._agentKey);
|
||||
if (session) {
|
||||
$debug("reuse session for %j", options._agentKey);
|
||||
options = {
|
||||
session,
|
||||
...options,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
const socket = tls.connect(options);
|
||||
|
||||
if (options._agentKey) {
|
||||
socket.on("session", session => {
|
||||
this._cacheSession(options._agentKey, session);
|
||||
});
|
||||
|
||||
socket.once("close", err => {
|
||||
if (err) this._evictSession(options._agentKey);
|
||||
});
|
||||
}
|
||||
|
||||
return socket;
|
||||
}
|
||||
|
||||
function Agent(options): void {
|
||||
if (!(this instanceof Agent)) return new Agent(options);
|
||||
|
||||
http.Agent.$call(this, options);
|
||||
this.defaultPort = 443;
|
||||
this.protocol = "https:";
|
||||
this.maxCachedSessions = this.options.maxCachedSessions;
|
||||
if (this.maxCachedSessions === undefined) this.maxCachedSessions = 100;
|
||||
|
||||
this._sessionCache = {
|
||||
map: {},
|
||||
list: [],
|
||||
};
|
||||
}
|
||||
$toClass(Agent, "Agent", http.Agent);
|
||||
|
||||
Agent.prototype.createConnection = createConnection;
|
||||
|
||||
Agent.prototype.getName = function (options = kEmptyObject as any) {
|
||||
let name = http.Agent.prototype.getName.$call(this, options);
|
||||
name += ":";
|
||||
if (options.ca) name += options.ca;
|
||||
name += ":";
|
||||
if (options.cert) name += options.cert;
|
||||
name += ":";
|
||||
if (options.clientCertEngine) name += options.clientCertEngine;
|
||||
name += ":";
|
||||
if (options.ciphers) name += options.ciphers;
|
||||
name += ":";
|
||||
if (options.key) name += options.key;
|
||||
name += ":";
|
||||
if (options.pfx) name += options.pfx;
|
||||
name += ":";
|
||||
if (options.rejectUnauthorized !== undefined) name += options.rejectUnauthorized;
|
||||
name += ":";
|
||||
if (options.servername && options.servername !== options.host) name += options.servername;
|
||||
name += ":";
|
||||
if (options.minVersion) name += options.minVersion;
|
||||
name += ":";
|
||||
if (options.maxVersion) name += options.maxVersion;
|
||||
name += ":";
|
||||
if (options.secureProtocol) name += options.secureProtocol;
|
||||
name += ":";
|
||||
if (options.crl) name += options.crl;
|
||||
name += ":";
|
||||
if (options.honorCipherOrder !== undefined) name += options.honorCipherOrder;
|
||||
name += ":";
|
||||
if (options.ecdhCurve) name += options.ecdhCurve;
|
||||
name += ":";
|
||||
if (options.dhparam) name += options.dhparam;
|
||||
name += ":";
|
||||
if (options.secureOptions !== undefined) name += options.secureOptions;
|
||||
name += ":";
|
||||
if (options.sessionIdContext) name += options.sessionIdContext;
|
||||
name += ":";
|
||||
if (options.sigalgs) name += JSONStringify(options.sigalgs);
|
||||
name += ":";
|
||||
if (options.privateKeyIdentifier) name += options.privateKeyIdentifier;
|
||||
name += ":";
|
||||
if (options.privateKeyEngine) name += options.privateKeyEngine;
|
||||
return name;
|
||||
};
|
||||
|
||||
Agent.prototype._getSession = function _getSession(key) {
|
||||
return this._sessionCache.map[key];
|
||||
};
|
||||
|
||||
Agent.prototype._cacheSession = function _cacheSession(key, session) {
|
||||
if (this.maxCachedSessions === 0) return;
|
||||
|
||||
if (this._sessionCache.map[key]) {
|
||||
this._sessionCache.map[key] = session;
|
||||
return;
|
||||
}
|
||||
|
||||
if (this._sessionCache.list.length >= this.maxCachedSessions) {
|
||||
const oldKey = ArrayPrototypeShift.$call(this._sessionCache.list);
|
||||
$debug("evicting %j", oldKey);
|
||||
delete this._sessionCache.map[oldKey];
|
||||
}
|
||||
|
||||
ArrayPrototypePush.$call(this._sessionCache.list, key);
|
||||
this._sessionCache.map[key] = session;
|
||||
};
|
||||
|
||||
Agent.prototype._evictSession = function _evictSession(key) {
|
||||
const index = ArrayPrototypeIndexOf.$call(this._sessionCache.list, key);
|
||||
if (index === -1) return;
|
||||
|
||||
ArrayPrototypeSplice.$call(this._sessionCache.list, index, 1);
|
||||
delete this._sessionCache.map[key];
|
||||
};
|
||||
|
||||
const globalAgent = new Agent({ keepAlive: true, scheduling: "lifo", timeout: 5000 });
|
||||
|
||||
function request(...args) {
|
||||
let options = {};
|
||||
let options: any = {};
|
||||
|
||||
if (typeof args[0] === "string") {
|
||||
const urlStr = ArrayPrototypeShift.$call(args);
|
||||
options = urlToHttpOptions(new URL(urlStr));
|
||||
} else if (args[0] instanceof URL) {
|
||||
} else if (isURL(args[0])) {
|
||||
options = urlToHttpOptions(ArrayPrototypeShift.$call(args));
|
||||
}
|
||||
|
||||
if (args[0] && typeof args[0] !== "function") {
|
||||
ObjectAssign.$call(null, options, ArrayPrototypeShift.$call(args));
|
||||
ObjectAssign(options, ArrayPrototypeShift.$call(args));
|
||||
}
|
||||
|
||||
options._defaultAgent = https.globalAgent;
|
||||
options._defaultAgent = globalAgent;
|
||||
ArrayPrototypeUnshift.$call(args, options);
|
||||
|
||||
return new http.ClientRequest(...args);
|
||||
@@ -32,24 +181,11 @@ function get(input, options, cb) {
|
||||
return req;
|
||||
}
|
||||
|
||||
function Agent(options) {
|
||||
if (!(this instanceof Agent)) return new Agent(options);
|
||||
|
||||
http.Agent.$apply(this, [options]);
|
||||
this.defaultPort = 443;
|
||||
this.protocol = "https:";
|
||||
this.maxCachedSessions = this.options.maxCachedSessions;
|
||||
if (this.maxCachedSessions === undefined) this.maxCachedSessions = 100;
|
||||
}
|
||||
$toClass(Agent, "Agent", http.Agent);
|
||||
Agent.prototype.createConnection = http.createConnection;
|
||||
|
||||
var https = {
|
||||
export default {
|
||||
Agent,
|
||||
globalAgent: new Agent({ keepAlive: true, scheduling: "lifo", timeout: 5000 }),
|
||||
globalAgent,
|
||||
Server: http.Server,
|
||||
createServer: http.createServer,
|
||||
get,
|
||||
request,
|
||||
};
|
||||
export default https;
|
||||
|
||||
@@ -1231,21 +1231,21 @@ Object.defineProperty(Socket.prototype, "pending", {
|
||||
|
||||
Socket.prototype.resume = function resume() {
|
||||
if (!this.connecting) {
|
||||
this._handle?.resume();
|
||||
this._handle?.resume?.();
|
||||
}
|
||||
return Duplex.prototype.resume.$call(this);
|
||||
};
|
||||
|
||||
Socket.prototype.pause = function pause() {
|
||||
if (!this.destroyed) {
|
||||
this._handle?.pause();
|
||||
this._handle?.pause?.();
|
||||
}
|
||||
return Duplex.prototype.pause.$call(this);
|
||||
};
|
||||
|
||||
Socket.prototype.read = function read(size) {
|
||||
if (!this.connecting) {
|
||||
this._handle?.resume();
|
||||
this._handle?.resume?.();
|
||||
}
|
||||
return Duplex.prototype.read.$call(this, size);
|
||||
};
|
||||
@@ -1255,7 +1255,7 @@ Socket.prototype._read = function _read(size) {
|
||||
if (this.connecting || !socket) {
|
||||
this.once("connect", () => this._read(size));
|
||||
} else {
|
||||
socket?.resume();
|
||||
socket?.resume?.();
|
||||
}
|
||||
};
|
||||
|
||||
@@ -2095,7 +2095,7 @@ function Server(options?, connectionListener?) {
|
||||
allowHalfOpen = false,
|
||||
keepAlive = false,
|
||||
keepAliveInitialDelay = 0,
|
||||
highWaterMark = getDefaultHighWaterMark(),
|
||||
highWaterMark = getDefaultHighWaterMark(false),
|
||||
pauseOnConnect = false,
|
||||
noDelay = false,
|
||||
} = options;
|
||||
@@ -2624,7 +2624,7 @@ function initSocketHandle(self) {
|
||||
function closeSocketHandle(self, isException, isCleanupPending = false) {
|
||||
$debug("closeSocketHandle", isException, isCleanupPending, !!self._handle);
|
||||
if (self._handle) {
|
||||
self._handle.close();
|
||||
self._handle.close?.();
|
||||
setImmediate(() => {
|
||||
$debug("emit close", isCleanupPending);
|
||||
self.emit("close", isException);
|
||||
|
||||
@@ -172,19 +172,101 @@ function getValidCiphersSet() {
|
||||
"TLS_AES_128_GCM_SHA256",
|
||||
"TLS_AES_256_GCM_SHA384",
|
||||
"TLS_CHACHA20_POLY1305_SHA256",
|
||||
|
||||
// Configurations include in the default cipher list
|
||||
"HIGH",
|
||||
"!aNULL",
|
||||
"!eNULL",
|
||||
"!EXPORT",
|
||||
"!DES",
|
||||
"!RC4",
|
||||
"!MD5",
|
||||
"!PSK",
|
||||
"!SRP",
|
||||
"!CAMELLIA",
|
||||
]);
|
||||
|
||||
// https://github.com/openssl/openssl/blob/openssl-3.5.2/include/openssl/ssl.h.in#L76
|
||||
const txt = [
|
||||
"LOW",
|
||||
"MEDIUM",
|
||||
"HIGH",
|
||||
"FIPS",
|
||||
"aNULL",
|
||||
"eNULL",
|
||||
"NULL",
|
||||
"kRSA",
|
||||
"kDHr",
|
||||
"kDHd",
|
||||
"kDH",
|
||||
"kEDH",
|
||||
"kDHE",
|
||||
"kECDHr",
|
||||
"kECDHe",
|
||||
"kECDH",
|
||||
"kEECDH",
|
||||
"kECDHE",
|
||||
"kPSK",
|
||||
"kRSAPSK",
|
||||
"kECDHEPSK",
|
||||
"kDHEPSK",
|
||||
"kGOST",
|
||||
"kGOST18",
|
||||
"kSRP",
|
||||
"aRSA",
|
||||
"aDSS",
|
||||
"aDH",
|
||||
"aECDH",
|
||||
"aECDSA",
|
||||
"aPSK",
|
||||
"aGOST94",
|
||||
"aGOST01",
|
||||
"aGOST12",
|
||||
"aGOST",
|
||||
"aSRP",
|
||||
"DSS",
|
||||
"DH",
|
||||
"DHE",
|
||||
"EDH",
|
||||
"ADH",
|
||||
"RSA",
|
||||
"ECDH",
|
||||
"EECDH",
|
||||
"ECDHE",
|
||||
"AECDH",
|
||||
"ECDSA",
|
||||
"PSK",
|
||||
"SRP",
|
||||
"DES",
|
||||
"3DES",
|
||||
"RC4",
|
||||
"RC2",
|
||||
"IDEA",
|
||||
"SEED",
|
||||
"AES128",
|
||||
"AES256",
|
||||
"AES",
|
||||
"AESGCM",
|
||||
"AESCCM",
|
||||
"AESCCM8",
|
||||
"CAMELLIA128",
|
||||
"CAMELLIA256",
|
||||
"CAMELLIA",
|
||||
"CHACHA20",
|
||||
"GOST89",
|
||||
"ARIA",
|
||||
"ARIAGCM",
|
||||
"ARIA128",
|
||||
"ARIA256",
|
||||
"GOST2012-GOST8912-GOST8912",
|
||||
"CBC",
|
||||
"MD5",
|
||||
"SHA1",
|
||||
"SHA",
|
||||
"GOST94",
|
||||
"GOST89MAC",
|
||||
"GOST12",
|
||||
"GOST89MAC12",
|
||||
"SHA256",
|
||||
"SHA384",
|
||||
"SSLv3",
|
||||
"TLSv1",
|
||||
"TLSv1.1",
|
||||
"TLSv1.2",
|
||||
"ALL",
|
||||
];
|
||||
for (const c of txt) _VALID_CIPHERS_SET.$add(c);
|
||||
for (const c of txt) _VALID_CIPHERS_SET.$add("!" + c);
|
||||
_VALID_CIPHERS_SET.$add("!EXPORT");
|
||||
_VALID_CIPHERS_SET.$add("!SSLv2");
|
||||
}
|
||||
return _VALID_CIPHERS_SET;
|
||||
}
|
||||
@@ -199,10 +281,11 @@ function validateCiphers(ciphers: string, name: string = "options") {
|
||||
// TODO: right now we need this because we dont create the CTX before listening/connecting
|
||||
// we need to change that in the future and let BoringSSL do the validation
|
||||
const ciphersSet = getValidCiphersSet();
|
||||
ciphersSet.add("DEFAULT");
|
||||
const requested = ciphers.split(":");
|
||||
for (const r of requested) {
|
||||
if (r && !ciphersSet.has(r)) {
|
||||
throw $ERR_SSL_NO_CIPHER_MATCH();
|
||||
throw $ERR_SSL_NO_CIPHER_MATCH(r);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,7 +4,7 @@ declare const self: typeof globalThis;
|
||||
type WebWorker = InstanceType<typeof globalThis.Worker>;
|
||||
|
||||
const EventEmitter = require("node:events");
|
||||
const { Readable } = require("node:stream");
|
||||
const Readable = require("internal/streams/readable");
|
||||
const { throwNotImplemented, warnNotImplementedOnce } = require("internal/shared");
|
||||
|
||||
const {
|
||||
|
||||
7
src/js/private.d.ts
vendored
7
src/js/private.d.ts
vendored
@@ -265,3 +265,10 @@ declare module "node:net" {
|
||||
_connections: number;
|
||||
}
|
||||
}
|
||||
|
||||
import "node:http";
|
||||
declare module "node:http" {
|
||||
interface IncomingMessage {
|
||||
_dumped: boolean;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1646,7 +1646,7 @@ export class VerdaccioRegistry {
|
||||
this.process = fork(require.resolve("verdaccio/bin/verdaccio"), ["-c", this.configPath, "-l", `${this.port}`], {
|
||||
silent,
|
||||
// Prefer using a release build of Bun since it's faster
|
||||
execPath: isCI ? bunExe() : Bun.which("bun") || bunExe(),
|
||||
execPath: bunExe(),
|
||||
env: {
|
||||
...(bunEnv as any),
|
||||
NODE_NO_WARNINGS: "1",
|
||||
@@ -1845,7 +1845,7 @@ export const exampleHtml = Buffer.from(
|
||||
*
|
||||
* @example Using disposal pattern
|
||||
* ```ts
|
||||
* using site = exampleSite();
|
||||
* await using site = exampleSite();
|
||||
* await fetch(site.url, { tls: { ca: site.ca } });
|
||||
* // server automatically stopped when scope exits
|
||||
* ```
|
||||
@@ -1868,12 +1868,10 @@ export function exampleSite(protocol: "https" | "http" = "https") {
|
||||
ca: protocol === "https" ? tls.cert : undefined,
|
||||
server,
|
||||
stop() {
|
||||
server.stop();
|
||||
return server.stop();
|
||||
},
|
||||
[Symbol.dispose]() {
|
||||
try {
|
||||
server.stop();
|
||||
} catch {}
|
||||
async [Symbol.asyncDispose]() {
|
||||
await server.stop();
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
@@ -248,7 +248,7 @@ test("unsupported protocol", async () => {
|
||||
);
|
||||
});
|
||||
|
||||
test("axios with https-proxy-agent", async () => {
|
||||
test.todo("axios with https-proxy-agent", async () => {
|
||||
httpProxyServer.log.length = 0;
|
||||
const httpsAgent = new HttpsProxyAgent(httpProxyServer.url, {
|
||||
rejectUnauthorized: false, // this should work with self-signed certs
|
||||
|
||||
@@ -273,7 +273,7 @@ const IS_UV_FS_COPYFILE_DISABLED =
|
||||
|
||||
it("Bun.file -> Response", async () => {
|
||||
using tmpbase = tempDir("bun-file-to-response", {});
|
||||
using server = exampleSite("https");
|
||||
await using server = exampleSite("https");
|
||||
// ensure the file doesn't already exist
|
||||
try {
|
||||
fs.unlinkSync(tmpbase + "fetch.js.out");
|
||||
|
||||
@@ -3,7 +3,7 @@ import https from "node:https";
|
||||
const { expect } = createTest(import.meta.path);
|
||||
|
||||
// TODO: today we use a workaround to continue event, we need to fix it in the future.
|
||||
using server = exampleSite();
|
||||
const server = exampleSite();
|
||||
let receivedContinue = false;
|
||||
const req = https.request(
|
||||
server.url,
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { createTest, exampleSite } from "node-harness";
|
||||
import https from "node:https";
|
||||
const { expect } = createTest(import.meta.path);
|
||||
using server = exampleSite();
|
||||
const server = exampleSite();
|
||||
let receivedContinue = false;
|
||||
const req = https.request(server.url, { ca: server.ca, headers: { "accept-encoding": "identity" } }, res => {
|
||||
let data = "";
|
||||
|
||||
@@ -7,8 +7,8 @@ const { expect } = createTest(import.meta.path);
|
||||
|
||||
await using server = createServer((req, res) => {
|
||||
expect(req.url).toBe("/hello");
|
||||
res.writeHead(200);
|
||||
res.setHeader("content-encoding", "br");
|
||||
res.writeHead(200);
|
||||
|
||||
const inputStream = new stream.Readable();
|
||||
inputStream.push("Hello World");
|
||||
|
||||
@@ -7,8 +7,8 @@ const { expect } = createTest(import.meta.path);
|
||||
|
||||
await using server = createServer((req, res) => {
|
||||
expect(req.url).toBe("/hello");
|
||||
res.writeHead(200);
|
||||
res.setHeader("content-encoding", "br");
|
||||
res.writeHead(200);
|
||||
|
||||
const inputStream = new stream.Readable();
|
||||
inputStream.push("Hello World");
|
||||
|
||||
@@ -7,8 +7,8 @@ const { expect } = createTest(import.meta.path);
|
||||
|
||||
await using server = createServer((req, res) => {
|
||||
expect(req.url).toBe("/hello");
|
||||
res.writeHead(200);
|
||||
res.setHeader("content-encoding", "deflate");
|
||||
res.writeHead(200);
|
||||
|
||||
const inputStream = new stream.Readable();
|
||||
inputStream.push("Hello World");
|
||||
|
||||
@@ -7,8 +7,8 @@ const { expect } = createTest(import.meta.path);
|
||||
|
||||
await using server = createServer((req, res) => {
|
||||
expect(req.url).toBe("/hello");
|
||||
res.writeHead(200);
|
||||
res.setHeader("content-encoding", "gzip");
|
||||
res.writeHead(200);
|
||||
|
||||
const inputStream = new stream.Readable();
|
||||
inputStream.push("Hello World");
|
||||
|
||||
@@ -2,6 +2,7 @@ import { createTest } from "node-harness";
|
||||
import { once } from "node:events";
|
||||
import http from "node:http";
|
||||
const { expect } = createTest(import.meta.path);
|
||||
process.exit(0); // TODO: BUN does not pass in node
|
||||
|
||||
await using server = http.createServer((req, res) => {
|
||||
if (req.headers["transfer-encoding"] === "chunked") {
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { createTest, exampleSite } from "node-harness";
|
||||
import http from "node:http";
|
||||
const { expect } = createTest(import.meta.path);
|
||||
using server = exampleSite("https");
|
||||
await using server = exampleSite("https");
|
||||
expect(() => http.request(server.url)).toThrow(TypeError);
|
||||
expect(() => http.request(server.url)).toThrow({
|
||||
code: "ERR_INVALID_PROTOCOL",
|
||||
|
||||
@@ -6,5 +6,5 @@ const agent = new http.Agent();
|
||||
const { promise, resolve } = Promise.withResolvers();
|
||||
http.get({ agent, hostname: "google.com" }, resolve);
|
||||
const response = await promise;
|
||||
expect(response.req.port).toBe(80);
|
||||
expect(response.req.agent.defaultPort).toBe(80);
|
||||
expect(response.req.protocol).toBe("http:");
|
||||
|
||||
@@ -3,7 +3,9 @@ import http from "node:http";
|
||||
const { expect } = createTest(import.meta.path);
|
||||
|
||||
const { promise, resolve } = Promise.withResolvers();
|
||||
http.request("http://google.com/", resolve).end();
|
||||
const req = http.request("http://google.com/", resolve);
|
||||
req.end();
|
||||
const response = await promise;
|
||||
expect(response.req.port).toBe(80);
|
||||
expect(response.req.agent.defaultPort).toBe(80);
|
||||
expect(response.req.protocol).toBe("http:");
|
||||
req.destroy();
|
||||
|
||||
@@ -19,7 +19,7 @@ const req = http.get(
|
||||
resolve,
|
||||
);
|
||||
|
||||
const { socket } = req;
|
||||
await promise;
|
||||
const socket = req.res.socket;
|
||||
expect(socket._httpMessage).toBe(req);
|
||||
socket.destroy();
|
||||
|
||||
@@ -23,11 +23,10 @@ expect(
|
||||
["req", "finish"],
|
||||
["req", "response"],
|
||||
"STATUS: 200",
|
||||
// TODO: not totally right:
|
||||
["res", "resume"],
|
||||
["req", "close"],
|
||||
["res", "readable"],
|
||||
["res", "end"],
|
||||
["req", "close"],
|
||||
["res", "close"],
|
||||
]);
|
||||
expect(await exited).toBe(0);
|
||||
|
||||
@@ -6,5 +6,5 @@ const agent = new https.Agent();
|
||||
const { promise, resolve } = Promise.withResolvers();
|
||||
https.get({ agent, hostname: "google.com" }, resolve);
|
||||
const response = await promise;
|
||||
expect(response.req.port).toBe(443);
|
||||
expect(response.req.agent.defaultPort).toBe(443);
|
||||
expect(response.req.protocol).toBe("https:");
|
||||
|
||||
@@ -1,9 +0,0 @@
|
||||
import { createTest } from "node-harness";
|
||||
import https from "node:https";
|
||||
const { expect } = createTest(import.meta.path);
|
||||
|
||||
const { promise, resolve } = Promise.withResolvers();
|
||||
https.request("https://google.com/", resolve).end();
|
||||
const response = await promise;
|
||||
expect(response.req.port).toBe(443);
|
||||
expect(response.req.protocol).toBe("https:");
|
||||
@@ -3,7 +3,8 @@ import { once } from "node:events";
|
||||
import { createServer, request } from "node:http";
|
||||
|
||||
describe("node:http client timeout", () => {
|
||||
it("should emit timeout event when timeout is reached", async () => {
|
||||
// test passes but hangs, as it does in node. rework to exit gracefully
|
||||
it.todo("should emit timeout event when timeout is reached", async () => {
|
||||
const server = createServer((req, res) => {
|
||||
// Intentionally not sending response to trigger timeout
|
||||
}).listen(0);
|
||||
@@ -20,14 +21,14 @@ describe("node:http client timeout", () => {
|
||||
});
|
||||
|
||||
let timeoutEventEmitted = false;
|
||||
let destroyCalled = false;
|
||||
let closeCalled = false;
|
||||
|
||||
req.on("timeout", () => {
|
||||
timeoutEventEmitted = true;
|
||||
});
|
||||
|
||||
req.on("close", () => {
|
||||
destroyCalled = true;
|
||||
closeCalled = true;
|
||||
});
|
||||
|
||||
req.end();
|
||||
@@ -36,8 +37,8 @@ describe("node:http client timeout", () => {
|
||||
await new Promise(resolve => setTimeout(resolve, 100));
|
||||
|
||||
expect(timeoutEventEmitted).toBe(true);
|
||||
expect(destroyCalled).toBe(true);
|
||||
expect(req.destroyed).toBe(true);
|
||||
expect(closeCalled).toBe(false);
|
||||
expect(req.destroyed).toBe(false);
|
||||
} finally {
|
||||
server.close();
|
||||
}
|
||||
@@ -73,7 +74,7 @@ describe("node:http client timeout", () => {
|
||||
await new Promise(resolve => setTimeout(resolve, 100));
|
||||
|
||||
expect(timeoutEventEmitted).toBe(false);
|
||||
expect(req.destroyed).toBe(false);
|
||||
expect(req.destroyed).toBe(true);
|
||||
} finally {
|
||||
server.close();
|
||||
}
|
||||
|
||||
@@ -4,7 +4,7 @@ import { createServer, request } from "node:http";
|
||||
|
||||
export async function run() {
|
||||
const { promise, resolve, reject } = Promise.withResolvers();
|
||||
using server = exampleSite("http");
|
||||
await using server = exampleSite("http");
|
||||
const proxyServer = createServer(function (req, res) {
|
||||
// Use URL object instead of deprecated url.parse
|
||||
const parsedUrl = new URL(req.url, `http://${req.headers.host}`);
|
||||
|
||||
@@ -852,18 +852,17 @@ describe("node:http", () => {
|
||||
it("should emit a socket event when connecting", async done => {
|
||||
runTest(done, async (server, serverPort, done) => {
|
||||
const req = request(`http://localhost:${serverPort}`, {});
|
||||
req.on("socket", function onRequestSocket(socket) {
|
||||
req.destroy();
|
||||
done();
|
||||
});
|
||||
const { promise, resolve } = Promise.withResolvers();
|
||||
req.on("socket", resolve);
|
||||
req.end();
|
||||
await promise;
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("https.request with custom tls options", () => {
|
||||
it("supports custom tls args", async () => {
|
||||
using httpsServer = exampleSite();
|
||||
await using httpsServer = exampleSite();
|
||||
|
||||
const { promise, resolve, reject } = Promise.withResolvers();
|
||||
const options: https.RequestOptions = {
|
||||
@@ -979,7 +978,7 @@ describe("node:http", () => {
|
||||
});
|
||||
|
||||
describe("ClientRequest.signal", () => {
|
||||
it("should attempt to make a standard GET request and abort", async () => {
|
||||
it.todo("should attempt to make a standard GET request and abort", async () => {
|
||||
let server_port;
|
||||
let server_host;
|
||||
const {
|
||||
|
||||
50
test/js/node/test/parallel/test-http-abort-client.js
Normal file
50
test/js/node/test/parallel/test-http-abort-client.js
Normal file
@@ -0,0 +1,50 @@
|
||||
// Copyright Joyent, Inc. and other Node contributors.
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a
|
||||
// copy of this software and associated documentation files (the
|
||||
// "Software"), to deal in the Software without restriction, including
|
||||
// without limitation the rights to use, copy, modify, merge, publish,
|
||||
// distribute, sublicense, and/or sell copies of the Software, and to permit
|
||||
// persons to whom the Software is furnished to do so, subject to the
|
||||
// following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be included
|
||||
// in all copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
|
||||
// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN
|
||||
// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
|
||||
// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
|
||||
// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
|
||||
// USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
|
||||
'use strict';
|
||||
const common = require('../common');
|
||||
const http = require('http');
|
||||
|
||||
let serverRes;
|
||||
const server = http.Server(common.mustCall((req, res) => {
|
||||
serverRes = res;
|
||||
res.writeHead(200);
|
||||
res.write('Part of my res.');
|
||||
}));
|
||||
|
||||
server.listen(0, common.mustCall(() => {
|
||||
http.get({
|
||||
port: server.address().port,
|
||||
headers: { connection: 'keep-alive' }
|
||||
}, common.mustCall((res) => {
|
||||
server.close();
|
||||
serverRes.destroy();
|
||||
|
||||
res.resume();
|
||||
res.on('end', common.mustNotCall());
|
||||
res.on('aborted', common.mustCall());
|
||||
res.on('error', common.expectsError({
|
||||
code: 'ECONNRESET'
|
||||
}));
|
||||
res.on('close', common.mustCall());
|
||||
res.socket.on('close', common.mustCall());
|
||||
}));
|
||||
}));
|
||||
95
test/js/node/test/parallel/test-http-abort-queued.js
Normal file
95
test/js/node/test/parallel/test-http-abort-queued.js
Normal file
@@ -0,0 +1,95 @@
|
||||
// Copyright Joyent, Inc. and other Node contributors.
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a
|
||||
// copy of this software and associated documentation files (the
|
||||
// "Software"), to deal in the Software without restriction, including
|
||||
// without limitation the rights to use, copy, modify, merge, publish,
|
||||
// distribute, sublicense, and/or sell copies of the Software, and to permit
|
||||
// persons to whom the Software is furnished to do so, subject to the
|
||||
// following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be included
|
||||
// in all copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
|
||||
// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN
|
||||
// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
|
||||
// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
|
||||
// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
|
||||
// USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
|
||||
'use strict';
|
||||
const common = require('../common');
|
||||
const assert = require('assert');
|
||||
const http = require('http');
|
||||
|
||||
let complete;
|
||||
|
||||
const server = http.createServer(common.mustCall((req, res) => {
|
||||
// We should not see the queued /thatotherone request within the server
|
||||
// as it should be aborted before it is sent.
|
||||
assert.strictEqual(req.url, '/');
|
||||
|
||||
res.writeHead(200);
|
||||
res.write('foo');
|
||||
|
||||
complete ??= function() {
|
||||
res.end();
|
||||
};
|
||||
}));
|
||||
|
||||
server.listen(0, common.mustCall(() => {
|
||||
const agent = new http.Agent({ maxSockets: 1 });
|
||||
assert.strictEqual(Object.keys(agent.sockets).length, 0);
|
||||
|
||||
const options = {
|
||||
hostname: 'localhost',
|
||||
port: server.address().port,
|
||||
method: 'GET',
|
||||
path: '/',
|
||||
agent: agent
|
||||
};
|
||||
|
||||
const req1 = http.request(options);
|
||||
req1.on('response', (res1) => {
|
||||
assert.strictEqual(Object.keys(agent.sockets).length, 1);
|
||||
assert.strictEqual(Object.keys(agent.requests).length, 0);
|
||||
|
||||
const req2 = http.request({
|
||||
method: 'GET',
|
||||
host: 'localhost',
|
||||
port: server.address().port,
|
||||
path: '/thatotherone',
|
||||
agent: agent
|
||||
});
|
||||
assert.strictEqual(Object.keys(agent.sockets).length, 1);
|
||||
assert.strictEqual(Object.keys(agent.requests).length, 1);
|
||||
|
||||
// TODO(jasnell): This event does not appear to currently be triggered.
|
||||
// is this handler actually required?
|
||||
req2.on('error', (err) => {
|
||||
// This is expected in response to our explicit abort call
|
||||
assert.strictEqual(err.code, 'ECONNRESET');
|
||||
});
|
||||
|
||||
req2.end();
|
||||
req2.abort();
|
||||
|
||||
assert.strictEqual(Object.keys(agent.sockets).length, 1);
|
||||
assert.strictEqual(Object.keys(agent.requests).length, 1);
|
||||
|
||||
res1.on('data', (chunk) => complete());
|
||||
|
||||
res1.on('end', common.mustCall(() => {
|
||||
setTimeout(common.mustCall(() => {
|
||||
assert.strictEqual(Object.keys(agent.sockets).length, 0);
|
||||
assert.strictEqual(Object.keys(agent.requests).length, 0);
|
||||
|
||||
server.close();
|
||||
}), 100);
|
||||
}));
|
||||
});
|
||||
|
||||
req1.end();
|
||||
}));
|
||||
@@ -0,0 +1,37 @@
|
||||
'use strict';
|
||||
require('../common');
|
||||
|
||||
// This test ensures that `addRequest`'s Legacy API accepts `localAddress`
|
||||
// correctly instead of accepting `path`.
|
||||
// https://github.com/nodejs/node/issues/5051
|
||||
|
||||
const assert = require('assert');
|
||||
const agent = require('http').globalAgent;
|
||||
|
||||
// Small stub just so we can call addRequest directly
|
||||
const req = {
|
||||
getHeader: () => {}
|
||||
};
|
||||
|
||||
agent.maxSockets = 0;
|
||||
|
||||
// `localAddress` is used when naming requests / sockets while using the Legacy
|
||||
// API. Port 8080 is hardcoded since this does not create a network connection.
|
||||
agent.addRequest(req, 'localhost', 8080, '127.0.0.1');
|
||||
assert.strictEqual(Object.keys(agent.requests).length, 1);
|
||||
assert.strictEqual(
|
||||
Object.keys(agent.requests)[0],
|
||||
'localhost:8080:127.0.0.1');
|
||||
|
||||
// `path` is *not* used when naming requests / sockets.
|
||||
// Port 8080 is hardcoded since this does not create a network connection
|
||||
agent.addRequest(req, {
|
||||
host: 'localhost',
|
||||
port: 8080,
|
||||
localAddress: '127.0.0.1',
|
||||
path: '/foo'
|
||||
});
|
||||
assert.strictEqual(Object.keys(agent.requests).length, 1);
|
||||
assert.strictEqual(
|
||||
Object.keys(agent.requests)[0],
|
||||
'localhost:8080:127.0.0.1');
|
||||
@@ -0,0 +1,73 @@
|
||||
'use strict';
|
||||
const common = require('../common');
|
||||
const assert = require('assert');
|
||||
const http = require('http');
|
||||
const Agent = http.Agent;
|
||||
const { getEventListeners, once } = require('events');
|
||||
const agent = new Agent();
|
||||
const server = http.createServer();
|
||||
|
||||
server.listen(0, common.mustCall(async () => {
|
||||
const port = server.address().port;
|
||||
const host = 'localhost';
|
||||
const options = {
|
||||
port: port,
|
||||
host: host,
|
||||
_agentKey: agent.getName({ port, host })
|
||||
};
|
||||
|
||||
async function postCreateConnection() {
|
||||
const ac = new AbortController();
|
||||
const { signal } = ac;
|
||||
const connection = agent.createConnection({ ...options, signal });
|
||||
assert.strictEqual(getEventListeners(signal, 'abort').length, 1);
|
||||
ac.abort();
|
||||
const [err] = await once(connection, 'error');
|
||||
assert.strictEqual(err?.name, 'AbortError');
|
||||
}
|
||||
|
||||
async function preCreateConnection() {
|
||||
const ac = new AbortController();
|
||||
const { signal } = ac;
|
||||
ac.abort();
|
||||
const connection = agent.createConnection({ ...options, signal });
|
||||
const [err] = await once(connection, 'error');
|
||||
assert.strictEqual(err?.name, 'AbortError');
|
||||
}
|
||||
|
||||
async function agentAsParam() {
|
||||
const ac = new AbortController();
|
||||
const { signal } = ac;
|
||||
const request = http.get({
|
||||
port: server.address().port,
|
||||
path: '/hello',
|
||||
agent: agent,
|
||||
signal,
|
||||
});
|
||||
assert.strictEqual(getEventListeners(signal, 'abort').length, 1);
|
||||
ac.abort();
|
||||
const [err] = await once(request, 'error');
|
||||
assert.strictEqual(err?.name, 'AbortError');
|
||||
}
|
||||
|
||||
async function agentAsParamPreAbort() {
|
||||
const ac = new AbortController();
|
||||
const { signal } = ac;
|
||||
ac.abort();
|
||||
const request = http.get({
|
||||
port: server.address().port,
|
||||
path: '/hello',
|
||||
agent: agent,
|
||||
signal,
|
||||
});
|
||||
assert.strictEqual(getEventListeners(signal, 'abort').length, 0);
|
||||
const [err] = await once(request, 'error');
|
||||
assert.strictEqual(err?.name, 'AbortError');
|
||||
}
|
||||
|
||||
await postCreateConnection();
|
||||
await preCreateConnection();
|
||||
await agentAsParam();
|
||||
await agentAsParamPreAbort();
|
||||
server.close();
|
||||
}));
|
||||
21
test/js/node/test/parallel/test-http-agent-close.js
Normal file
21
test/js/node/test/parallel/test-http-agent-close.js
Normal file
@@ -0,0 +1,21 @@
|
||||
'use strict';
|
||||
const common = require('../common');
|
||||
const assert = require('assert');
|
||||
const http = require('http');
|
||||
|
||||
const agent = new http.Agent();
|
||||
const _err = new Error('kaboom');
|
||||
agent.createSocket = function(req, options, cb) {
|
||||
cb(_err);
|
||||
};
|
||||
|
||||
const req = http
|
||||
.request({
|
||||
agent
|
||||
})
|
||||
.on('error', common.mustCall((err) => {
|
||||
assert.strictEqual(err, _err);
|
||||
}))
|
||||
.on('close', common.mustCall(() => {
|
||||
assert.strictEqual(req.destroyed, true);
|
||||
}));
|
||||
@@ -0,0 +1,74 @@
|
||||
// Copyright Joyent, Inc. and other Node contributors.
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a
|
||||
// copy of this software and associated documentation files (the
|
||||
// "Software"), to deal in the Software without restriction, including
|
||||
// without limitation the rights to use, copy, modify, merge, publish,
|
||||
// distribute, sublicense, and/or sell copies of the Software, and to permit
|
||||
// persons to whom the Software is furnished to do so, subject to the
|
||||
// following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be included
|
||||
// in all copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
|
||||
// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN
|
||||
// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
|
||||
// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
|
||||
// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
|
||||
// USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
|
||||
'use strict';
|
||||
const common = require('../common');
|
||||
const assert = require('assert');
|
||||
const http = require('http');
|
||||
const Countdown = require('../common/countdown');
|
||||
|
||||
const server = http.createServer(common.mustCall((req, res) => {
|
||||
res.writeHead(200, { 'Content-Type': 'text/plain' });
|
||||
res.end('Hello World\n');
|
||||
}, 2)).listen(0, common.mustCall(() => {
|
||||
const agent = new http.Agent({ maxSockets: 1 });
|
||||
|
||||
agent.on('free', common.mustCall(3));
|
||||
|
||||
const requestOptions = {
|
||||
agent: agent,
|
||||
host: 'localhost',
|
||||
port: server.address().port,
|
||||
path: '/'
|
||||
};
|
||||
|
||||
const request1 = http.get(requestOptions, common.mustCall((response) => {
|
||||
// Assert request2 is queued in the agent
|
||||
const key = agent.getName(requestOptions);
|
||||
assert.strictEqual(agent.requests[key].length, 1);
|
||||
response.resume();
|
||||
response.on('end', common.mustCall(() => {
|
||||
request1.socket.destroy();
|
||||
|
||||
request1.socket.once('close', common.mustCall(() => {
|
||||
// Assert request2 was removed from the queue
|
||||
assert(!agent.requests[key]);
|
||||
process.nextTick(() => {
|
||||
// Assert that the same socket was not assigned to request2,
|
||||
// since it was destroyed.
|
||||
assert.notStrictEqual(request1.socket, request2.socket);
|
||||
assert(!request2.socket.destroyed, 'the socket is destroyed');
|
||||
});
|
||||
}));
|
||||
}));
|
||||
}));
|
||||
|
||||
const request2 = http.get(requestOptions, common.mustCall((response) => {
|
||||
assert(!request2.socket.destroyed);
|
||||
assert(request1.socket.destroyed);
|
||||
// Assert not reusing the same socket, since it was destroyed.
|
||||
assert.notStrictEqual(request1.socket, request2.socket);
|
||||
const countdown = new Countdown(2, () => server.close());
|
||||
request2.socket.on('close', common.mustCall(() => countdown.dec()));
|
||||
response.on('end', common.mustCall(() => countdown.dec()));
|
||||
response.resume();
|
||||
}));
|
||||
}));
|
||||
49
test/js/node/test/parallel/test-http-agent-error-on-idle.js
Normal file
49
test/js/node/test/parallel/test-http-agent-error-on-idle.js
Normal file
@@ -0,0 +1,49 @@
|
||||
'use strict';
|
||||
|
||||
const common = require('../common');
|
||||
const assert = require('assert');
|
||||
const http = require('http');
|
||||
const Agent = http.Agent;
|
||||
|
||||
const server = http.createServer(common.mustCall((req, res) => {
|
||||
res.end('hello world');
|
||||
}, 2));
|
||||
|
||||
server.listen(0, () => {
|
||||
const agent = new Agent({ keepAlive: true });
|
||||
|
||||
const requestParams = {
|
||||
host: 'localhost',
|
||||
port: server.address().port,
|
||||
agent: agent,
|
||||
path: '/'
|
||||
};
|
||||
|
||||
const socketKey = agent.getName(requestParams);
|
||||
|
||||
http.get(requestParams, common.mustCall((res) => {
|
||||
assert.strictEqual(res.statusCode, 200);
|
||||
res.resume();
|
||||
res.on('end', common.mustCall(() => {
|
||||
process.nextTick(common.mustCall(() => {
|
||||
const freeSockets = agent.freeSockets[socketKey];
|
||||
// Expect a free socket on socketKey
|
||||
assert.strictEqual(freeSockets.length, 1);
|
||||
|
||||
// Generate a random error on the free socket
|
||||
const freeSocket = freeSockets[0];
|
||||
freeSocket.emit('error', new Error('ECONNRESET: test'));
|
||||
|
||||
http.get(requestParams, done);
|
||||
}));
|
||||
}));
|
||||
}));
|
||||
|
||||
function done() {
|
||||
// Expect the freeSockets pool to be empty
|
||||
assert.strictEqual(Object.keys(agent.freeSockets).length, 0);
|
||||
|
||||
agent.destroy();
|
||||
server.close();
|
||||
}
|
||||
});
|
||||
@@ -0,0 +1,56 @@
|
||||
'use strict';
|
||||
const common = require('../common');
|
||||
const Countdown = require('../common/countdown');
|
||||
|
||||
// This test ensures that the `maxSockets` value for `http.Agent` is respected.
|
||||
// https://github.com/nodejs/node/issues/4050
|
||||
|
||||
const assert = require('assert');
|
||||
const http = require('http');
|
||||
|
||||
const MAX_SOCKETS = 2;
|
||||
|
||||
const agent = new http.Agent({
|
||||
keepAlive: true,
|
||||
keepAliveMsecs: 1000,
|
||||
maxSockets: MAX_SOCKETS,
|
||||
maxFreeSockets: 2
|
||||
});
|
||||
|
||||
const server = http.createServer(
|
||||
common.mustCall((req, res) => {
|
||||
res.end('hello world');
|
||||
}, 6)
|
||||
);
|
||||
|
||||
const countdown = new Countdown(6, () => server.close());
|
||||
|
||||
function get(path, callback) {
|
||||
return http.get(
|
||||
{
|
||||
host: 'localhost',
|
||||
port: server.address().port,
|
||||
agent: agent,
|
||||
path: path
|
||||
},
|
||||
callback
|
||||
);
|
||||
}
|
||||
|
||||
server.listen(
|
||||
0,
|
||||
common.mustCall(() => {
|
||||
for (let i = 0; i < 6; i++) {
|
||||
const request = get('/1', common.mustCall());
|
||||
request.on(
|
||||
'response',
|
||||
common.mustCall(() => {
|
||||
request.abort();
|
||||
const sockets = agent.sockets[Object.keys(agent.sockets)[0]];
|
||||
assert(sockets.length <= MAX_SOCKETS);
|
||||
countdown.dec();
|
||||
})
|
||||
);
|
||||
}
|
||||
})
|
||||
);
|
||||
50
test/js/node/test/parallel/test-http-agent-maxsockets.js
Normal file
50
test/js/node/test/parallel/test-http-agent-maxsockets.js
Normal file
@@ -0,0 +1,50 @@
|
||||
'use strict';
|
||||
const common = require('../common');
|
||||
const assert = require('assert');
|
||||
const http = require('http');
|
||||
const Countdown = require('../common/countdown');
|
||||
|
||||
const agent = new http.Agent({
|
||||
keepAlive: true,
|
||||
keepAliveMsecs: 1000,
|
||||
maxSockets: 2,
|
||||
maxFreeSockets: 2
|
||||
});
|
||||
|
||||
const server = http.createServer(common.mustCall((req, res) => {
|
||||
res.end('hello world');
|
||||
}, 2));
|
||||
|
||||
server.keepAliveTimeout = 0;
|
||||
|
||||
function get(path, callback) {
|
||||
return http.get({
|
||||
host: 'localhost',
|
||||
port: server.address().port,
|
||||
agent: agent,
|
||||
path: path
|
||||
}, callback);
|
||||
}
|
||||
|
||||
const countdown = new Countdown(2, () => {
|
||||
const freepool = agent.freeSockets[Object.keys(agent.freeSockets)[0]];
|
||||
assert.strictEqual(freepool.length, 2,
|
||||
`expect keep 2 free sockets, but got ${freepool.length}`);
|
||||
agent.destroy();
|
||||
server.close();
|
||||
});
|
||||
|
||||
function dec() {
|
||||
process.nextTick(() => countdown.dec());
|
||||
}
|
||||
|
||||
function onGet(res) {
|
||||
assert.strictEqual(res.statusCode, 200);
|
||||
res.resume();
|
||||
res.on('end', common.mustCall(dec));
|
||||
}
|
||||
|
||||
server.listen(0, common.mustCall(() => {
|
||||
get('/1', common.mustCall(onGet));
|
||||
get('/2', common.mustCall(onGet));
|
||||
}));
|
||||
111
test/js/node/test/parallel/test-http-agent-maxtotalsockets.js
Normal file
111
test/js/node/test/parallel/test-http-agent-maxtotalsockets.js
Normal file
@@ -0,0 +1,111 @@
|
||||
'use strict';
|
||||
|
||||
const common = require('../common');
|
||||
const assert = require('assert');
|
||||
const http = require('http');
|
||||
const Countdown = require('../common/countdown');
|
||||
|
||||
assert.throws(() => new http.Agent({
|
||||
maxTotalSockets: 'test',
|
||||
}), {
|
||||
code: 'ERR_INVALID_ARG_TYPE',
|
||||
name: 'TypeError',
|
||||
message: 'The "maxTotalSockets" argument must be of type number. ' +
|
||||
"Received type string ('test')",
|
||||
});
|
||||
|
||||
[-1, 0, NaN].forEach((item) => {
|
||||
assert.throws(() => new http.Agent({
|
||||
maxTotalSockets: item,
|
||||
}), {
|
||||
code: 'ERR_OUT_OF_RANGE',
|
||||
name: 'RangeError',
|
||||
});
|
||||
});
|
||||
|
||||
assert.ok(new http.Agent({
|
||||
maxTotalSockets: Infinity,
|
||||
}));
|
||||
|
||||
function start(param = {}) {
|
||||
const { maxTotalSockets, maxSockets } = param;
|
||||
|
||||
const agent = new http.Agent({
|
||||
keepAlive: true,
|
||||
keepAliveMsecs: 1000,
|
||||
maxTotalSockets,
|
||||
maxSockets,
|
||||
maxFreeSockets: 3
|
||||
});
|
||||
|
||||
const server = http.createServer(common.mustCall((req, res) => {
|
||||
res.end('hello world');
|
||||
}, 6));
|
||||
const server2 = http.createServer(common.mustCall((req, res) => {
|
||||
res.end('hello world');
|
||||
}, 6));
|
||||
|
||||
server.keepAliveTimeout = 0;
|
||||
server2.keepAliveTimeout = 0;
|
||||
|
||||
const countdown = new Countdown(12, () => {
|
||||
assert.strictEqual(getRequestCount(), 0);
|
||||
agent.destroy();
|
||||
server.close();
|
||||
server2.close();
|
||||
});
|
||||
|
||||
function handler(s) {
|
||||
for (let i = 0; i < 6; i++) {
|
||||
http.get({
|
||||
host: 'localhost',
|
||||
port: s.address().port,
|
||||
agent,
|
||||
path: `/${i}`,
|
||||
}, common.mustCall((res) => {
|
||||
assert.strictEqual(res.statusCode, 200);
|
||||
res.resume();
|
||||
res.on('end', common.mustCall(() => {
|
||||
for (const key of Object.keys(agent.sockets)) {
|
||||
assert(agent.sockets[key].length <= maxSockets);
|
||||
}
|
||||
assert(getTotalSocketsCount() <= maxTotalSockets);
|
||||
countdown.dec();
|
||||
}));
|
||||
}));
|
||||
}
|
||||
}
|
||||
|
||||
function getTotalSocketsCount() {
|
||||
let num = 0;
|
||||
for (const key of Object.keys(agent.sockets)) {
|
||||
num += agent.sockets[key].length;
|
||||
}
|
||||
return num;
|
||||
}
|
||||
|
||||
function getRequestCount() {
|
||||
let num = 0;
|
||||
for (const key of Object.keys(agent.requests)) {
|
||||
num += agent.requests[key].length;
|
||||
}
|
||||
return num;
|
||||
}
|
||||
|
||||
server.listen(0, common.mustCall(() => handler(server)));
|
||||
server2.listen(0, common.mustCall(() => handler(server2)));
|
||||
}
|
||||
|
||||
// If maxTotalSockets is larger than maxSockets,
|
||||
// then the origin check will be skipped
|
||||
// when the socket is removed.
|
||||
[{
|
||||
maxTotalSockets: 2,
|
||||
maxSockets: 3,
|
||||
}, {
|
||||
maxTotalSockets: 3,
|
||||
maxSockets: 2,
|
||||
}, {
|
||||
maxTotalSockets: 2,
|
||||
maxSockets: 2,
|
||||
}].forEach(start);
|
||||
@@ -0,0 +1,123 @@
|
||||
'use strict';
|
||||
const common = require('../common');
|
||||
if (common.isWindows) return; // TODO: BUN
|
||||
const assert = require('assert');
|
||||
const http = require('http');
|
||||
const net = require('net');
|
||||
|
||||
const agent = new http.Agent({
|
||||
keepAlive: true,
|
||||
maxFreeSockets: Infinity,
|
||||
maxSockets: Infinity,
|
||||
maxTotalSockets: Infinity,
|
||||
});
|
||||
|
||||
const server = net.createServer({
|
||||
pauseOnConnect: true,
|
||||
}, (sock) => {
|
||||
// Do not read anything from `sock`
|
||||
sock.pause();
|
||||
sock.write('HTTP/1.1 200 OK\r\nContent-Length: 0\r\nConnection: Keep-Alive\r\n\r\n');
|
||||
});
|
||||
|
||||
server.listen(0, common.mustCall(() => {
|
||||
sendFstReq(server.address().port);
|
||||
}));
|
||||
|
||||
function sendFstReq(serverPort) {
|
||||
const req = http.request({
|
||||
agent,
|
||||
host: '127.0.0.1',
|
||||
port: serverPort,
|
||||
}, (res) => {
|
||||
res.on('data', noop);
|
||||
res.on('end', common.mustCall(() => {
|
||||
// Agent's socket reusing code is registered to process.nextTick(),
|
||||
// and will be run after this function, make sure it take effect.
|
||||
setImmediate(sendSecReq, serverPort, req.socket.localPort);
|
||||
}));
|
||||
});
|
||||
|
||||
// Make the `req.socket` non drained, i.e. has some data queued to write to
|
||||
// and accept by the kernel. In Linux and Mac, we only need to call `req.end(aLargeBuffer)`.
|
||||
// However, in Windows, the mechanism of acceptance is loose, the following code is a workaround
|
||||
// for Windows.
|
||||
|
||||
/**
|
||||
* https://docs.microsoft.com/en-US/troubleshoot/windows/win32/data-segment-tcp-winsock says
|
||||
*
|
||||
* Winsock uses the following rules to indicate a send completion to the application
|
||||
* (depending on how the send is invoked, the completion notification could be the
|
||||
* function returning from a blocking call, signaling an event, or calling a notification
|
||||
* function, and so forth):
|
||||
* - If the socket is still within SO_SNDBUF quota, Winsock copies the data from the application
|
||||
* send and indicates the send completion to the application.
|
||||
* - If the socket is beyond SO_SNDBUF quota and there's only one previously buffered send still
|
||||
* in the stack kernel buffer, Winsock copies the data from the application send and indicates
|
||||
* the send completion to the application.
|
||||
* - If the socket is beyond SO_SNDBUF quota and there's more than one previously buffered send
|
||||
* in the stack kernel buffer, Winsock copies the data from the application send. Winsock doesn't
|
||||
* indicate the send completion to the application until the stack completes enough sends to put
|
||||
* back the socket within SO_SNDBUF quota or only one outstanding send condition.
|
||||
*/
|
||||
|
||||
req.on('socket', () => {
|
||||
req.socket.on('connect', () => {
|
||||
// Print tcp send buffer information
|
||||
console.log(process.report.getReport().libuv.filter((handle) => handle.type === 'tcp'));
|
||||
|
||||
const dataLargerThanTCPSendBuf = Buffer.alloc(1024 * 1024 * 64, 0);
|
||||
|
||||
req.write(dataLargerThanTCPSendBuf);
|
||||
req.uncork();
|
||||
if (process.platform === 'win32') {
|
||||
assert.ok(req.socket.writableLength === 0);
|
||||
}
|
||||
|
||||
req.write(dataLargerThanTCPSendBuf);
|
||||
req.uncork();
|
||||
if (process.platform === 'win32') {
|
||||
assert.ok(req.socket.writableLength === 0);
|
||||
}
|
||||
|
||||
req.end(dataLargerThanTCPSendBuf);
|
||||
assert.ok(req.socket.writableLength > 0);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function sendSecReq(serverPort, fstReqCliPort) {
|
||||
// Make the second request, which should be sent on a new socket
|
||||
// because the first socket is not drained and hence can not be reused
|
||||
const req = http.request({
|
||||
agent,
|
||||
host: '127.0.0.1',
|
||||
port: serverPort,
|
||||
}, (res) => {
|
||||
res.on('data', noop);
|
||||
res.on('end', common.mustCall(() => {
|
||||
setImmediate(sendThrReq, serverPort, req.socket.localPort);
|
||||
}));
|
||||
});
|
||||
|
||||
req.on('socket', common.mustCall((sock) => {
|
||||
assert.notStrictEqual(sock.localPort, fstReqCliPort);
|
||||
}));
|
||||
req.end();
|
||||
}
|
||||
|
||||
function sendThrReq(serverPort, secReqCliPort) {
|
||||
// Make the third request, the agent should reuse the second socket we just made
|
||||
const req = http.request({
|
||||
agent,
|
||||
host: '127.0.0.1',
|
||||
port: serverPort,
|
||||
}, noop);
|
||||
|
||||
req.on('socket', common.mustCall((sock) => {
|
||||
assert.strictEqual(sock.localPort, secReqCliPort);
|
||||
process.exit(0);
|
||||
}));
|
||||
}
|
||||
|
||||
function noop() { }
|
||||
149
test/js/node/test/parallel/test-http-agent-scheduling.js
Normal file
149
test/js/node/test/parallel/test-http-agent-scheduling.js
Normal file
@@ -0,0 +1,149 @@
|
||||
'use strict';
|
||||
|
||||
const common = require('../common');
|
||||
const assert = require('assert');
|
||||
const http = require('http');
|
||||
|
||||
function createServer(count) {
|
||||
return http.createServer(common.mustCallAtLeast((req, res) => {
|
||||
// Return the remote port number used for this connection.
|
||||
res.end(req.socket.remotePort.toString(10));
|
||||
}), count);
|
||||
}
|
||||
|
||||
function makeRequest(url, agent, callback) {
|
||||
http
|
||||
.request(url, { agent }, (res) => {
|
||||
let data = '';
|
||||
res.setEncoding('ascii');
|
||||
res.on('data', (c) => {
|
||||
data += c;
|
||||
});
|
||||
res.on('end', () => {
|
||||
process.nextTick(callback, data);
|
||||
});
|
||||
})
|
||||
.end();
|
||||
}
|
||||
|
||||
function bulkRequest(url, agent, done) {
|
||||
const ports = [];
|
||||
let count = agent.maxSockets;
|
||||
|
||||
for (let i = 0; i < agent.maxSockets; i++) {
|
||||
makeRequest(url, agent, callback);
|
||||
}
|
||||
|
||||
function callback(port) {
|
||||
count -= 1;
|
||||
ports.push(port);
|
||||
if (count === 0) {
|
||||
done(ports);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function defaultTest() {
|
||||
const server = createServer(8);
|
||||
server.listen(0, onListen);
|
||||
|
||||
function onListen() {
|
||||
const url = `http://localhost:${server.address().port}`;
|
||||
const agent = new http.Agent({
|
||||
keepAlive: true,
|
||||
maxSockets: 5
|
||||
});
|
||||
|
||||
bulkRequest(url, agent, (ports) => {
|
||||
makeRequest(url, agent, (port) => {
|
||||
assert.strictEqual(ports[ports.length - 1], port);
|
||||
makeRequest(url, agent, (port) => {
|
||||
assert.strictEqual(ports[ports.length - 1], port);
|
||||
makeRequest(url, agent, (port) => {
|
||||
assert.strictEqual(ports[ports.length - 1], port);
|
||||
server.close();
|
||||
agent.destroy();
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function fifoTest() {
|
||||
const server = createServer(8);
|
||||
server.listen(0, onListen);
|
||||
|
||||
function onListen() {
|
||||
const url = `http://localhost:${server.address().port}`;
|
||||
const agent = new http.Agent({
|
||||
keepAlive: true,
|
||||
maxSockets: 5,
|
||||
scheduling: 'fifo'
|
||||
});
|
||||
|
||||
bulkRequest(url, agent, (ports) => {
|
||||
makeRequest(url, agent, (port) => {
|
||||
assert.strictEqual(ports[0], port);
|
||||
makeRequest(url, agent, (port) => {
|
||||
assert.strictEqual(ports[1], port);
|
||||
makeRequest(url, agent, (port) => {
|
||||
assert.strictEqual(ports[2], port);
|
||||
server.close();
|
||||
agent.destroy();
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function lifoTest() {
|
||||
const server = createServer(8);
|
||||
server.listen(0, onListen);
|
||||
|
||||
function onListen() {
|
||||
const url = `http://localhost:${server.address().port}`;
|
||||
const agent = new http.Agent({
|
||||
keepAlive: true,
|
||||
maxSockets: 5,
|
||||
scheduling: 'lifo'
|
||||
});
|
||||
|
||||
bulkRequest(url, agent, (ports) => {
|
||||
makeRequest(url, agent, (port) => {
|
||||
assert.strictEqual(ports[ports.length - 1], port);
|
||||
makeRequest(url, agent, (port) => {
|
||||
assert.strictEqual(ports[ports.length - 1], port);
|
||||
makeRequest(url, agent, (port) => {
|
||||
assert.strictEqual(ports[ports.length - 1], port);
|
||||
server.close();
|
||||
agent.destroy();
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function badSchedulingOptionTest() {
|
||||
try {
|
||||
new http.Agent({
|
||||
keepAlive: true,
|
||||
maxSockets: 5,
|
||||
scheduling: 'filo'
|
||||
});
|
||||
} catch (err) {
|
||||
assert.strictEqual(err.code, 'ERR_INVALID_ARG_VALUE');
|
||||
assert.strictEqual(
|
||||
err.message,
|
||||
"The argument 'scheduling' must be one of: 'fifo', 'lifo'. " +
|
||||
"Received 'filo'"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
defaultTest();
|
||||
fifoTest();
|
||||
lifoTest();
|
||||
badSchedulingOptionTest();
|
||||
23
test/js/node/test/parallel/test-http-agent-timeout-option.js
Normal file
23
test/js/node/test/parallel/test-http-agent-timeout-option.js
Normal file
@@ -0,0 +1,23 @@
|
||||
'use strict';
|
||||
|
||||
const { mustCall } = require('../common');
|
||||
const { strictEqual } = require('assert');
|
||||
const { Agent, get } = require('http');
|
||||
|
||||
// Test that the listener that forwards the `'timeout'` event from the socket to
|
||||
// the `ClientRequest` instance is added to the socket when the `timeout` option
|
||||
// of the `Agent` is set.
|
||||
|
||||
const request = get({
|
||||
agent: new Agent({ timeout: 50 }),
|
||||
lookup: () => {}
|
||||
});
|
||||
|
||||
request.on('socket', mustCall((socket) => {
|
||||
strictEqual(socket.timeout, 50);
|
||||
|
||||
const listeners = socket.listeners('timeout');
|
||||
|
||||
strictEqual(listeners.length, 2);
|
||||
strictEqual(listeners[1], request.timeoutCb);
|
||||
}));
|
||||
134
test/js/node/test/parallel/test-http-agent-timeout.js
Normal file
134
test/js/node/test/parallel/test-http-agent-timeout.js
Normal file
@@ -0,0 +1,134 @@
|
||||
'use strict';
|
||||
|
||||
const common = require('../common');
|
||||
const assert = require('assert');
|
||||
const http = require('http');
|
||||
|
||||
{
|
||||
// Ensure reuse of successful sockets.
|
||||
|
||||
const agent = new http.Agent({ keepAlive: true });
|
||||
|
||||
const server = http.createServer((req, res) => {
|
||||
res.end();
|
||||
});
|
||||
|
||||
server.listen(0, common.mustCall(() => {
|
||||
let socket;
|
||||
http.get({ port: server.address().port, agent })
|
||||
.on('response', common.mustCall((res) => {
|
||||
socket = res.socket;
|
||||
assert(socket);
|
||||
res.resume();
|
||||
socket.on('free', common.mustCall(() => {
|
||||
http.get({ port: server.address().port, agent })
|
||||
.on('response', common.mustCall((res) => {
|
||||
assert.strictEqual(socket, res.socket);
|
||||
assert(socket);
|
||||
agent.destroy();
|
||||
server.close();
|
||||
}));
|
||||
}));
|
||||
}));
|
||||
}));
|
||||
}
|
||||
|
||||
{
|
||||
// Ensure that timed-out sockets are not reused.
|
||||
|
||||
const agent = new http.Agent({ keepAlive: true, timeout: 50 });
|
||||
|
||||
const server = http.createServer((req, res) => {
|
||||
res.end();
|
||||
});
|
||||
|
||||
server.listen(0, common.mustCall(() => {
|
||||
http.get({ port: server.address().port, agent })
|
||||
.on('response', common.mustCall((res) => {
|
||||
const socket = res.socket;
|
||||
assert(socket);
|
||||
res.resume();
|
||||
socket.on('free', common.mustCall(() => {
|
||||
socket.on('timeout', common.mustCall(() => {
|
||||
http.get({ port: server.address().port, agent })
|
||||
.on('response', common.mustCall((res) => {
|
||||
assert.notStrictEqual(socket, res.socket);
|
||||
assert.strictEqual(socket.destroyed, true);
|
||||
agent.destroy();
|
||||
server.close();
|
||||
}));
|
||||
}));
|
||||
}));
|
||||
}));
|
||||
}));
|
||||
}
|
||||
|
||||
{
|
||||
// Ensure that destroyed sockets are not reused.
|
||||
|
||||
const agent = new http.Agent({ keepAlive: true });
|
||||
|
||||
const server = http.createServer((req, res) => {
|
||||
res.end();
|
||||
});
|
||||
|
||||
server.listen(0, common.mustCall(() => {
|
||||
let socket;
|
||||
http.get({ port: server.address().port, agent })
|
||||
.on('response', common.mustCall((res) => {
|
||||
socket = res.socket;
|
||||
assert(socket);
|
||||
res.resume();
|
||||
socket.on('free', common.mustCall(() => {
|
||||
socket.destroy();
|
||||
http.get({ port: server.address().port, agent })
|
||||
.on('response', common.mustCall((res) => {
|
||||
assert.notStrictEqual(socket, res.socket);
|
||||
assert(socket);
|
||||
agent.destroy();
|
||||
server.close();
|
||||
}));
|
||||
}));
|
||||
}));
|
||||
}));
|
||||
}
|
||||
|
||||
{
|
||||
// Ensure custom keepSocketAlive timeout is respected
|
||||
|
||||
const CUSTOM_TIMEOUT = 60;
|
||||
const AGENT_TIMEOUT = 50;
|
||||
|
||||
class CustomAgent extends http.Agent {
|
||||
keepSocketAlive(socket) {
|
||||
if (!super.keepSocketAlive(socket)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
socket.setTimeout(CUSTOM_TIMEOUT);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
const agent = new CustomAgent({ keepAlive: true, timeout: AGENT_TIMEOUT });
|
||||
|
||||
const server = http.createServer((req, res) => {
|
||||
res.end();
|
||||
});
|
||||
|
||||
server.listen(0, common.mustCall(() => {
|
||||
http.get({ port: server.address().port, agent })
|
||||
.on('response', common.mustCall((res) => {
|
||||
const socket = res.socket;
|
||||
assert(socket);
|
||||
res.resume();
|
||||
socket.on('free', common.mustCall(() => {
|
||||
socket.on('timeout', common.mustCall(() => {
|
||||
assert.strictEqual(socket.timeout, CUSTOM_TIMEOUT);
|
||||
agent.destroy();
|
||||
server.close();
|
||||
}));
|
||||
}));
|
||||
}));
|
||||
}));
|
||||
}
|
||||
88
test/js/node/test/parallel/test-http-agent.js
Normal file
88
test/js/node/test/parallel/test-http-agent.js
Normal file
@@ -0,0 +1,88 @@
|
||||
// Copyright Joyent, Inc. and other Node contributors.
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a
|
||||
// copy of this software and associated documentation files (the
|
||||
// "Software"), to deal in the Software without restriction, including
|
||||
// without limitation the rights to use, copy, modify, merge, publish,
|
||||
// distribute, sublicense, and/or sell copies of the Software, and to permit
|
||||
// persons to whom the Software is furnished to do so, subject to the
|
||||
// following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be included
|
||||
// in all copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
|
||||
// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN
|
||||
// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
|
||||
// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
|
||||
// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
|
||||
// USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
|
||||
'use strict';
|
||||
const common = require('../common');
|
||||
const Countdown = require('../common/countdown');
|
||||
const assert = require('assert');
|
||||
const http = require('http');
|
||||
|
||||
const N = 4;
|
||||
const M = 4;
|
||||
const server = http.Server(common.mustCall(function(req, res) {
|
||||
res.writeHead(200);
|
||||
res.end('hello world\n');
|
||||
}, (N * M))); // N * M = good requests (the errors will not be counted)
|
||||
|
||||
function makeRequests(outCount, inCount, shouldFail) {
|
||||
const countdown = new Countdown(
|
||||
outCount * inCount,
|
||||
common.mustCall(() => server.close())
|
||||
);
|
||||
let onRequest = common.mustNotCall(); // Temporary
|
||||
const p = new Promise((resolve) => {
|
||||
onRequest = common.mustCall((res) => {
|
||||
if (countdown.dec() === 0) {
|
||||
resolve();
|
||||
}
|
||||
|
||||
if (!shouldFail)
|
||||
res.resume();
|
||||
}, outCount * inCount);
|
||||
});
|
||||
|
||||
server.listen(0, () => {
|
||||
const port = server.address().port;
|
||||
for (let i = 0; i < outCount; i++) {
|
||||
setTimeout(() => {
|
||||
for (let j = 0; j < inCount; j++) {
|
||||
const req = http.get({ port: port, path: '/' }, onRequest);
|
||||
if (shouldFail)
|
||||
req.on('error', common.mustCall(onRequest));
|
||||
else
|
||||
req.on('error', (e) => assert.fail(e));
|
||||
}
|
||||
}, i);
|
||||
}
|
||||
});
|
||||
return p;
|
||||
}
|
||||
|
||||
const test1 = makeRequests(N, M);
|
||||
|
||||
const test2 = () => {
|
||||
// Should not explode if can not create sockets.
|
||||
// Ref: https://github.com/nodejs/node/issues/13045
|
||||
// Ref: https://github.com/nodejs/node/issues/13831
|
||||
http.Agent.prototype.createConnection = function createConnection(_, cb) {
|
||||
process.nextTick(cb, new Error('nothing'));
|
||||
};
|
||||
return makeRequests(N, M, true);
|
||||
};
|
||||
|
||||
test1
|
||||
.then(test2)
|
||||
.catch((e) => {
|
||||
// This is currently the way to fail a test with a Promise.
|
||||
console.error(e);
|
||||
process.exit(1);
|
||||
}
|
||||
);
|
||||
139
test/js/node/test/parallel/test-http-client-abort-destroy.js
Normal file
139
test/js/node/test/parallel/test-http-client-abort-destroy.js
Normal file
@@ -0,0 +1,139 @@
|
||||
'use strict';
|
||||
const common = require('../common');
|
||||
const http = require('http');
|
||||
const assert = require('assert');
|
||||
const { getEventListeners } = require('events');
|
||||
|
||||
{
|
||||
// abort
|
||||
|
||||
const server = http.createServer(common.mustCall((req, res) => {
|
||||
res.end('Hello');
|
||||
}));
|
||||
|
||||
server.listen(0, common.mustCall(() => {
|
||||
const options = { port: server.address().port };
|
||||
const req = http.get(options, common.mustCall((res) => {
|
||||
res.on('data', (data) => {
|
||||
req.abort();
|
||||
assert.strictEqual(req.aborted, true);
|
||||
assert.strictEqual(req.destroyed, true);
|
||||
server.close();
|
||||
});
|
||||
}));
|
||||
req.on('error', common.mustNotCall());
|
||||
assert.strictEqual(req.aborted, false);
|
||||
assert.strictEqual(req.destroyed, false);
|
||||
}));
|
||||
}
|
||||
|
||||
{
|
||||
// destroy + res
|
||||
|
||||
const server = http.createServer(common.mustCall((req, res) => {
|
||||
res.end('Hello');
|
||||
}));
|
||||
|
||||
server.listen(0, common.mustCall(() => {
|
||||
const options = { port: server.address().port };
|
||||
const req = http.get(options, common.mustCall((res) => {
|
||||
res.on('data', (data) => {
|
||||
req.destroy();
|
||||
assert.strictEqual(req.aborted, false);
|
||||
assert.strictEqual(req.destroyed, true);
|
||||
server.close();
|
||||
});
|
||||
}));
|
||||
req.on('error', common.mustNotCall());
|
||||
assert.strictEqual(req.aborted, false);
|
||||
assert.strictEqual(req.destroyed, false);
|
||||
}));
|
||||
}
|
||||
|
||||
{
|
||||
// destroy
|
||||
|
||||
const server = http.createServer(common.mustNotCall());
|
||||
|
||||
server.listen(0, common.mustCall(() => {
|
||||
const options = { port: server.address().port };
|
||||
const req = http.get(options, common.mustNotCall());
|
||||
req.on('error', common.mustCall((err) => {
|
||||
assert.strictEqual(err.code, 'ECONNRESET');
|
||||
server.close();
|
||||
}));
|
||||
assert.strictEqual(req.aborted, false);
|
||||
assert.strictEqual(req.destroyed, false);
|
||||
req.destroy();
|
||||
assert.strictEqual(req.aborted, false);
|
||||
assert.strictEqual(req.destroyed, true);
|
||||
}));
|
||||
}
|
||||
|
||||
|
||||
{
|
||||
// Destroy post-abort sync with AbortSignal
|
||||
|
||||
const server = http.createServer(common.mustNotCall());
|
||||
const controller = new AbortController();
|
||||
const { signal } = controller;
|
||||
server.listen(0, common.mustCall(() => {
|
||||
const options = { port: server.address().port, signal };
|
||||
const req = http.get(options, common.mustNotCall());
|
||||
req.on('error', common.mustCall((err) => {
|
||||
assert.strictEqual(err.code, 'ABORT_ERR');
|
||||
assert.strictEqual(err.name, 'AbortError');
|
||||
server.close();
|
||||
}));
|
||||
assert.strictEqual(getEventListeners(signal, 'abort').length, 1);
|
||||
assert.strictEqual(req.aborted, false);
|
||||
assert.strictEqual(req.destroyed, false);
|
||||
controller.abort();
|
||||
assert.strictEqual(req.aborted, false);
|
||||
assert.strictEqual(req.destroyed, true);
|
||||
}));
|
||||
}
|
||||
|
||||
{
|
||||
// Use post-abort async AbortSignal
|
||||
const server = http.createServer(common.mustNotCall());
|
||||
const controller = new AbortController();
|
||||
const { signal } = controller;
|
||||
server.listen(0, common.mustCall(() => {
|
||||
const options = { port: server.address().port, signal };
|
||||
const req = http.get(options, common.mustNotCall());
|
||||
req.on('error', common.mustCall((err) => {
|
||||
assert.strictEqual(err.code, 'ABORT_ERR');
|
||||
assert.strictEqual(err.name, 'AbortError');
|
||||
}));
|
||||
|
||||
req.on('close', common.mustCall(() => {
|
||||
assert.strictEqual(req.aborted, false);
|
||||
assert.strictEqual(req.destroyed, true);
|
||||
server.close();
|
||||
}));
|
||||
|
||||
assert.strictEqual(getEventListeners(signal, 'abort').length, 1);
|
||||
process.nextTick(() => controller.abort());
|
||||
}));
|
||||
}
|
||||
|
||||
{
|
||||
// Use pre-aborted AbortSignal
|
||||
const server = http.createServer(common.mustNotCall());
|
||||
const controller = new AbortController();
|
||||
const { signal } = controller;
|
||||
server.listen(0, common.mustCall(() => {
|
||||
controller.abort();
|
||||
const options = { port: server.address().port, signal };
|
||||
const req = http.get(options, common.mustNotCall());
|
||||
assert.strictEqual(getEventListeners(signal, 'abort').length, 0);
|
||||
req.on('error', common.mustCall((err) => {
|
||||
assert.strictEqual(err.code, 'ABORT_ERR');
|
||||
assert.strictEqual(err.name, 'AbortError');
|
||||
server.close();
|
||||
}));
|
||||
assert.strictEqual(req.aborted, false);
|
||||
assert.strictEqual(req.destroyed, true);
|
||||
}));
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
'use strict';
|
||||
const common = require('../common');
|
||||
const assert = require('assert');
|
||||
const http = require('http');
|
||||
|
||||
let socketsCreated = 0;
|
||||
|
||||
class Agent extends http.Agent {
|
||||
createConnection(options, oncreate) {
|
||||
const socket = super.createConnection(options, oncreate);
|
||||
socketsCreated++;
|
||||
return socket;
|
||||
}
|
||||
}
|
||||
|
||||
const server = http.createServer((req, res) => res.end());
|
||||
|
||||
server.listen(0, common.mustCall(() => {
|
||||
const port = server.address().port;
|
||||
const agent = new Agent({
|
||||
keepAlive: true,
|
||||
maxSockets: 1
|
||||
});
|
||||
|
||||
const req = http.get({ agent, port }, common.mustCall((res) => {
|
||||
res.resume();
|
||||
res.on('end', () => {
|
||||
res.destroy();
|
||||
|
||||
http.get({ agent, port }, common.mustCall((res) => {
|
||||
res.resume();
|
||||
assert.strictEqual(socketsCreated, 1);
|
||||
agent.destroy();
|
||||
server.close();
|
||||
}));
|
||||
});
|
||||
}));
|
||||
req.end();
|
||||
}));
|
||||
@@ -0,0 +1,47 @@
|
||||
'use strict';
|
||||
const common = require('../common');
|
||||
const assert = require('assert');
|
||||
const http = require('http');
|
||||
|
||||
|
||||
for (const destroyer of ['destroy', 'abort']) {
|
||||
let socketsCreated = 0;
|
||||
|
||||
class Agent extends http.Agent {
|
||||
createConnection(options, oncreate) {
|
||||
const socket = super.createConnection(options, oncreate);
|
||||
socketsCreated++;
|
||||
return socket;
|
||||
}
|
||||
}
|
||||
|
||||
const server = http.createServer((req, res) => res.end());
|
||||
|
||||
server.listen(0, common.mustCall(() => {
|
||||
const port = server.address().port;
|
||||
const agent = new Agent({
|
||||
keepAlive: true,
|
||||
maxSockets: 1
|
||||
});
|
||||
|
||||
http.get({ agent, port }, (res) => res.resume());
|
||||
|
||||
const req = http.get({ agent, port }, common.mustNotCall());
|
||||
req[destroyer]();
|
||||
|
||||
if (destroyer === 'destroy') {
|
||||
req.on('error', common.mustCall((err) => {
|
||||
assert.strictEqual(err.code, 'ECONNRESET');
|
||||
}));
|
||||
} else {
|
||||
req.on('error', common.mustNotCall());
|
||||
}
|
||||
|
||||
http.get({ agent, port }, common.mustCall((res) => {
|
||||
res.resume();
|
||||
assert.strictEqual(socketsCreated, 1);
|
||||
agent.destroy();
|
||||
server.close();
|
||||
}));
|
||||
}));
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
'use strict';
|
||||
const common = require('../common');
|
||||
if (common.isWindows) return; // TODO: BUN
|
||||
const assert = require('assert');
|
||||
const http = require('http');
|
||||
|
||||
let socketsCreated = 0;
|
||||
|
||||
class Agent extends http.Agent {
|
||||
createConnection(options, oncreate) {
|
||||
const socket = super.createConnection(options, oncreate);
|
||||
socketsCreated++;
|
||||
return socket;
|
||||
}
|
||||
}
|
||||
|
||||
const server = http.createServer((req, res) => res.end());
|
||||
|
||||
const socketPath = common.PIPE;
|
||||
const tmpdir = require('../common/tmpdir');
|
||||
tmpdir.refresh();
|
||||
|
||||
server.listen(socketPath, common.mustCall(() => {
|
||||
const agent = new Agent({
|
||||
keepAlive: true,
|
||||
maxSockets: 1
|
||||
});
|
||||
|
||||
http.get({ agent, socketPath }, (res) => res.resume());
|
||||
|
||||
const req = http.get({ agent, socketPath }, common.mustNotCall());
|
||||
req.abort();
|
||||
|
||||
http.get({ agent, socketPath }, common.mustCall((res) => {
|
||||
res.resume();
|
||||
assert.strictEqual(socketsCreated, 1);
|
||||
agent.destroy();
|
||||
server.close();
|
||||
}));
|
||||
}));
|
||||
@@ -0,0 +1,19 @@
|
||||
'use strict';
|
||||
const common = require('../common');
|
||||
const http = require('http');
|
||||
const net = require('net');
|
||||
|
||||
const server = http.createServer(common.mustNotCall());
|
||||
|
||||
server.listen(0, common.mustCall(() => {
|
||||
const req = http.get({
|
||||
createConnection(options, oncreate) {
|
||||
const socket = net.createConnection(options, oncreate);
|
||||
socket.once('close', () => server.close());
|
||||
return socket;
|
||||
},
|
||||
port: server.address().port
|
||||
});
|
||||
|
||||
req.abort();
|
||||
}));
|
||||
@@ -0,0 +1,26 @@
|
||||
'use strict';
|
||||
const common = require('../common');
|
||||
if (common.isWindows) return; // TODO: BUN
|
||||
const http = require('http');
|
||||
|
||||
const server = http.createServer(common.mustNotCall());
|
||||
|
||||
class Agent extends http.Agent {
|
||||
createConnection(options, oncreate) {
|
||||
const socket = super.createConnection(options, oncreate);
|
||||
socket.once('close', () => server.close());
|
||||
return socket;
|
||||
}
|
||||
}
|
||||
|
||||
const tmpdir = require('../common/tmpdir');
|
||||
tmpdir.refresh();
|
||||
|
||||
server.listen(common.PIPE, common.mustCall(() => {
|
||||
const req = http.get({
|
||||
agent: new Agent(),
|
||||
socketPath: common.PIPE
|
||||
});
|
||||
|
||||
req.abort();
|
||||
}));
|
||||
32
test/js/node/test/parallel/test-http-client-abort3.js
Normal file
32
test/js/node/test/parallel/test-http-client-abort3.js
Normal file
@@ -0,0 +1,32 @@
|
||||
'use strict';
|
||||
|
||||
const common = require('../common');
|
||||
const http = require('http');
|
||||
const net = require('net');
|
||||
|
||||
function createConnection() {
|
||||
const socket = new net.Socket();
|
||||
|
||||
process.nextTick(function() {
|
||||
socket.destroy(new Error('Oops'));
|
||||
});
|
||||
|
||||
return socket;
|
||||
}
|
||||
|
||||
{
|
||||
const req = http.get({ createConnection });
|
||||
|
||||
req.on('error', common.expectsError({ name: 'Error', message: 'Oops' }));
|
||||
req.abort();
|
||||
}
|
||||
|
||||
{
|
||||
class CustomAgent extends http.Agent {}
|
||||
CustomAgent.prototype.createConnection = createConnection;
|
||||
|
||||
const req = http.get({ agent: new CustomAgent() });
|
||||
|
||||
req.on('error', common.expectsError({ name: 'Error', message: 'Oops' }));
|
||||
req.abort();
|
||||
}
|
||||
45
test/js/node/test/parallel/test-http-client-aborted-event.js
Normal file
45
test/js/node/test/parallel/test-http-client-aborted-event.js
Normal file
@@ -0,0 +1,45 @@
|
||||
'use strict';
|
||||
const common = require('../common');
|
||||
const http = require('http');
|
||||
|
||||
{
|
||||
let serverRes;
|
||||
const server = http.Server(function(req, res) {
|
||||
res.write('Part of my res.');
|
||||
serverRes = res;
|
||||
});
|
||||
|
||||
server.listen(0, common.mustCall(function() {
|
||||
http.get({
|
||||
port: this.address().port,
|
||||
headers: { connection: 'keep-alive' }
|
||||
}, common.mustCall(function(res) {
|
||||
server.close();
|
||||
serverRes.destroy();
|
||||
res.on('aborted', common.mustCall());
|
||||
res.on('error', common.expectsError({
|
||||
code: 'ECONNRESET'
|
||||
}));
|
||||
}));
|
||||
}));
|
||||
}
|
||||
|
||||
{
|
||||
// Don't crash of no 'error' handler.
|
||||
let serverRes;
|
||||
const server = http.Server(function(req, res) {
|
||||
res.write('Part of my res.');
|
||||
serverRes = res;
|
||||
});
|
||||
|
||||
server.listen(0, common.mustCall(function() {
|
||||
http.get({
|
||||
port: this.address().port,
|
||||
headers: { connection: 'keep-alive' }
|
||||
}, common.mustCall(function(res) {
|
||||
server.close();
|
||||
serverRes.destroy();
|
||||
res.on('aborted', common.mustCall());
|
||||
}));
|
||||
}));
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
'use strict';
|
||||
const common = require('../common');
|
||||
const assert = require('assert');
|
||||
const http = require('http');
|
||||
|
||||
const server = http.createServer(common.mustCall((req, res) => {
|
||||
res.end('hello');
|
||||
}));
|
||||
|
||||
const keepAliveAgent = new http.Agent({ keepAlive: true });
|
||||
|
||||
server.listen(0, common.mustCall(() => {
|
||||
const req = http.get({
|
||||
port: server.address().port,
|
||||
agent: keepAliveAgent
|
||||
});
|
||||
|
||||
req
|
||||
.on('response', common.mustCall((res) => {
|
||||
res
|
||||
.on('close', common.mustCall(() => {
|
||||
assert.strictEqual(req.destroyed, true);
|
||||
server.close();
|
||||
keepAliveAgent.destroy();
|
||||
}))
|
||||
.on('data', common.mustCall());
|
||||
}))
|
||||
.end();
|
||||
}));
|
||||
71
test/js/node/test/parallel/test-http-client-agent.js
Normal file
71
test/js/node/test/parallel/test-http-client-agent.js
Normal file
@@ -0,0 +1,71 @@
|
||||
// Copyright Joyent, Inc. and other Node contributors.
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a
|
||||
// copy of this software and associated documentation files (the
|
||||
// "Software"), to deal in the Software without restriction, including
|
||||
// without limitation the rights to use, copy, modify, merge, publish,
|
||||
// distribute, sublicense, and/or sell copies of the Software, and to permit
|
||||
// persons to whom the Software is furnished to do so, subject to the
|
||||
// following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be included
|
||||
// in all copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
|
||||
// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN
|
||||
// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
|
||||
// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
|
||||
// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
|
||||
// USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
|
||||
'use strict';
|
||||
const common = require('../common');
|
||||
const assert = require('assert');
|
||||
const http = require('http');
|
||||
const Countdown = require('../common/countdown');
|
||||
|
||||
let name;
|
||||
const max = 3;
|
||||
const agent = new http.Agent();
|
||||
|
||||
const server = http.Server(common.mustCall((req, res) => {
|
||||
if (req.url === '/0') {
|
||||
setTimeout(common.mustCall(() => {
|
||||
res.writeHead(200);
|
||||
res.end('Hello, World!');
|
||||
}), 100);
|
||||
} else {
|
||||
res.writeHead(200);
|
||||
res.end('Hello, World!');
|
||||
}
|
||||
}, max));
|
||||
server.listen(0, common.mustCall(() => {
|
||||
name = agent.getName({ port: server.address().port });
|
||||
for (let i = 0; i < max; ++i)
|
||||
request(i);
|
||||
}));
|
||||
|
||||
const countdown = new Countdown(max, () => {
|
||||
assert(!(name in agent.sockets));
|
||||
assert(!(name in agent.requests));
|
||||
server.close();
|
||||
});
|
||||
|
||||
function request(i) {
|
||||
const req = http.get({
|
||||
port: server.address().port,
|
||||
path: `/${i}`,
|
||||
agent
|
||||
}, function(res) {
|
||||
const socket = req.socket;
|
||||
socket.on('close', common.mustCall(() => {
|
||||
countdown.dec();
|
||||
if (countdown.remaining > 0) {
|
||||
assert.strictEqual(agent.sockets[name].includes(socket),
|
||||
false);
|
||||
}
|
||||
}));
|
||||
res.resume();
|
||||
});
|
||||
}
|
||||
31
test/js/node/test/parallel/test-http-client-close-event.js
Normal file
31
test/js/node/test/parallel/test-http-client-close-event.js
Normal file
@@ -0,0 +1,31 @@
|
||||
'use strict';
|
||||
const common = require('../common');
|
||||
|
||||
// This test ensures that the `'close'` event is emitted after the `'error'`
|
||||
// event when a request is made and the socket is closed before we started to
|
||||
// receive a response.
|
||||
|
||||
const assert = require('assert');
|
||||
const http = require('http');
|
||||
|
||||
const server = http.createServer(common.mustNotCall());
|
||||
|
||||
server.listen(0, common.mustCall(() => {
|
||||
const req = http.get({ port: server.address().port }, common.mustNotCall());
|
||||
let errorEmitted = false;
|
||||
|
||||
req.on('error', common.mustCall((err) => {
|
||||
errorEmitted = true;
|
||||
assert.strictEqual(err.constructor, Error);
|
||||
assert.strictEqual(err.message, 'socket hang up');
|
||||
assert.strictEqual(err.code, 'ECONNRESET');
|
||||
}));
|
||||
|
||||
req.on('close', common.mustCall(() => {
|
||||
assert.strictEqual(req.destroyed, true);
|
||||
assert.strictEqual(errorEmitted, true);
|
||||
server.close();
|
||||
}));
|
||||
|
||||
req.destroy();
|
||||
}));
|
||||
@@ -0,0 +1,70 @@
|
||||
// Copyright Joyent, Inc. and other Node contributors.
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a
|
||||
// copy of this software and associated documentation files (the
|
||||
// "Software"), to deal in the Software without restriction, including
|
||||
// without limitation the rights to use, copy, modify, merge, publish,
|
||||
// distribute, sublicense, and/or sell copies of the Software, and to permit
|
||||
// persons to whom the Software is furnished to do so, subject to the
|
||||
// following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be included
|
||||
// in all copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
|
||||
// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN
|
||||
// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
|
||||
// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
|
||||
// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
|
||||
// USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
|
||||
'use strict';
|
||||
const common = require('../common');
|
||||
const assert = require('assert');
|
||||
const http = require('http');
|
||||
|
||||
const { once } = require('events');
|
||||
|
||||
const expectedHeaders = {
|
||||
'DELETE': ['host', 'connection'],
|
||||
'GET': ['host', 'connection'],
|
||||
'HEAD': ['host', 'connection'],
|
||||
'OPTIONS': ['host', 'connection'],
|
||||
'POST': ['host', 'connection', 'content-length'],
|
||||
'PUT': ['host', 'connection', 'content-length'],
|
||||
'TRACE': ['host', 'connection']
|
||||
};
|
||||
|
||||
const expectedMethods = Object.keys(expectedHeaders);
|
||||
|
||||
const server = http.createServer(common.mustCall((req, res) => {
|
||||
res.end();
|
||||
|
||||
assert(Object.hasOwn(expectedHeaders, req.method),
|
||||
`${req.method} was an unexpected method`);
|
||||
|
||||
const requestHeaders = Object.keys(req.headers);
|
||||
for (const header of requestHeaders) {
|
||||
assert.ok(
|
||||
expectedHeaders[req.method].includes(header.toLowerCase()),
|
||||
`${header} should not exist for method ${req.method}`
|
||||
);
|
||||
}
|
||||
|
||||
assert.strictEqual(
|
||||
requestHeaders.length,
|
||||
expectedHeaders[req.method].length,
|
||||
`some headers were missing for method: ${req.method}`
|
||||
);
|
||||
}, expectedMethods.length));
|
||||
|
||||
server.listen(0, common.mustCall(() => {
|
||||
Promise.all(expectedMethods.map(async (method) => {
|
||||
const request = http.request({
|
||||
method: method,
|
||||
port: server.address().port
|
||||
}).end();
|
||||
return once(request, 'response');
|
||||
})).then(common.mustCall(() => { server.close(); }));
|
||||
}));
|
||||
@@ -0,0 +1,32 @@
|
||||
'use strict';
|
||||
const common = require('../common');
|
||||
const assert = require('assert');
|
||||
const http = require('http');
|
||||
const net = require('net');
|
||||
|
||||
const response = Buffer.from('HTTP/1.1 200 OK\r\n' +
|
||||
'Content-Length: 6\r\n' +
|
||||
'Transfer-Encoding: Chunked\r\n' +
|
||||
'\r\n' +
|
||||
'6\r\nfoobar' +
|
||||
'0\r\n');
|
||||
|
||||
const server = net.createServer(common.mustCall((conn) => {
|
||||
conn.write(response);
|
||||
}));
|
||||
|
||||
server.listen(0, common.mustCall(() => {
|
||||
const req = http.get(`http://localhost:${server.address().port}/`);
|
||||
req.end();
|
||||
req.on('error', common.mustCall((err) => {
|
||||
const reason = "Transfer-Encoding can't be present with Content-Length";
|
||||
assert.strictEqual(err.message, `Parse Error: ${reason}`);
|
||||
assert(err.bytesParsed < response.length);
|
||||
assert(err.bytesParsed >= response.indexOf('Transfer-Encoding'));
|
||||
assert.strictEqual(err.code, 'HPE_INVALID_TRANSFER_ENCODING');
|
||||
assert.strictEqual(err.reason, reason);
|
||||
assert.deepStrictEqual(err.rawPacket, response);
|
||||
|
||||
server.close();
|
||||
}));
|
||||
}));
|
||||
27
test/js/node/test/parallel/test-http-client-finished.js
Normal file
27
test/js/node/test/parallel/test-http-client-finished.js
Normal file
@@ -0,0 +1,27 @@
|
||||
'use strict';
|
||||
const common = require('../common');
|
||||
const http = require('http');
|
||||
const { finished } = require('stream');
|
||||
|
||||
{
|
||||
// Test abort before finished.
|
||||
|
||||
const server = http.createServer(function(req, res) {
|
||||
res.write('asd');
|
||||
});
|
||||
|
||||
server.listen(0, common.mustCall(function() {
|
||||
http.request({
|
||||
port: this.address().port
|
||||
})
|
||||
.on('response', (res) => {
|
||||
res.on('readable', () => {
|
||||
res.destroy();
|
||||
});
|
||||
finished(res, common.mustCall(() => {
|
||||
server.close();
|
||||
}));
|
||||
})
|
||||
.end();
|
||||
}));
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
'use strict';
|
||||
|
||||
const common = require('../common');
|
||||
const { createServer, get } = require('http');
|
||||
const assert = require('assert');
|
||||
|
||||
const server = createServer(common.mustCall((req, res) => {
|
||||
res.writeHead(200);
|
||||
res.write('Part of res.');
|
||||
}));
|
||||
|
||||
function onUncaught(error) {
|
||||
assert.strictEqual(error.message, 'Destroy test');
|
||||
server.close();
|
||||
}
|
||||
|
||||
process.on('uncaughtException', common.mustCall(onUncaught));
|
||||
|
||||
server.listen(0, () => {
|
||||
get({
|
||||
port: server.address().port
|
||||
}, common.mustCall((res) => {
|
||||
const err = new Error('Destroy test');
|
||||
assert.strictEqual(res.errored, null);
|
||||
res.destroy(err);
|
||||
assert.strictEqual(res.closed, false);
|
||||
assert.strictEqual(res.errored, err);
|
||||
res.on('close', () => {
|
||||
assert.strictEqual(res.closed, true);
|
||||
});
|
||||
}));
|
||||
});
|
||||
@@ -0,0 +1,39 @@
|
||||
'use strict';
|
||||
const common = require('../common');
|
||||
const http = require('http');
|
||||
|
||||
const server = http.createServer((req, res) => {
|
||||
res.end();
|
||||
}).listen(0, common.mustCall(() => {
|
||||
const agent = new http.Agent({
|
||||
maxSockets: 1,
|
||||
keepAlive: true
|
||||
});
|
||||
|
||||
const port = server.address().port;
|
||||
|
||||
const post = http.request({
|
||||
agent,
|
||||
method: 'POST',
|
||||
port,
|
||||
}, common.mustCall((res) => {
|
||||
res.resume();
|
||||
}));
|
||||
|
||||
// What happens here is that the server `end`s the response before we send
|
||||
// `something`, and the client thought that this is a green light for sending
|
||||
// next GET request
|
||||
post.write(Buffer.alloc(16 * 1024, 'X'));
|
||||
setTimeout(() => {
|
||||
post.end('something');
|
||||
}, 100);
|
||||
|
||||
http.request({
|
||||
agent,
|
||||
method: 'GET',
|
||||
port,
|
||||
}, common.mustCall((res) => {
|
||||
server.close();
|
||||
res.connection.end();
|
||||
})).end();
|
||||
}));
|
||||
56
test/js/node/test/parallel/test-http-client-parse-error.js
Normal file
56
test/js/node/test/parallel/test-http-client-parse-error.js
Normal file
@@ -0,0 +1,56 @@
|
||||
// Copyright Joyent, Inc. and other Node contributors.
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a
|
||||
// copy of this software and associated documentation files (the
|
||||
// "Software"), to deal in the Software without restriction, including
|
||||
// without limitation the rights to use, copy, modify, merge, publish,
|
||||
// distribute, sublicense, and/or sell copies of the Software, and to permit
|
||||
// persons to whom the Software is furnished to do so, subject to the
|
||||
// following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be included
|
||||
// in all copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
|
||||
// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN
|
||||
// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
|
||||
// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
|
||||
// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
|
||||
// USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
|
||||
'use strict';
|
||||
const common = require('../common');
|
||||
if (common.isWindows) return; // TODO: BUN
|
||||
const http = require('http');
|
||||
const net = require('net');
|
||||
const assert = require('assert');
|
||||
const Countdown = require('../common/countdown');
|
||||
|
||||
const countdown = new Countdown(2, () => server.close());
|
||||
|
||||
const payloads = [
|
||||
'HTTP/1.1 302 Object Moved\r\nContent-Length: 0\r\n\r\nhi world',
|
||||
'bad http = should trigger parse error',
|
||||
];
|
||||
|
||||
// Create a TCP server
|
||||
const server =
|
||||
net.createServer(common.mustCall((c) => c.end(payloads.shift()), 2));
|
||||
|
||||
server.listen(0, common.mustCall(() => {
|
||||
for (let i = 0; i < 2; i++) {
|
||||
const req = http.get({
|
||||
port: server.address().port,
|
||||
path: '/'
|
||||
}).on('error', common.mustCall((e) => {
|
||||
assert.strictEqual(req.socket.listenerCount('data'), 0);
|
||||
assert.strictEqual(req.socket.listenerCount('end'), 1);
|
||||
common.expectsError({
|
||||
code: 'HPE_INVALID_CONSTANT',
|
||||
message: 'Parse Error: Expected HTTP/, RTSP/ or ICE/'
|
||||
})(e);
|
||||
countdown.dec();
|
||||
}));
|
||||
}
|
||||
}));
|
||||
71
test/js/node/test/parallel/test-http-client-readable.js
Normal file
71
test/js/node/test/parallel/test-http-client-readable.js
Normal file
@@ -0,0 +1,71 @@
|
||||
// Copyright Joyent, Inc. and other Node contributors.
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a
|
||||
// copy of this software and associated documentation files (the
|
||||
// "Software"), to deal in the Software without restriction, including
|
||||
// without limitation the rights to use, copy, modify, merge, publish,
|
||||
// distribute, sublicense, and/or sell copies of the Software, and to permit
|
||||
// persons to whom the Software is furnished to do so, subject to the
|
||||
// following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be included
|
||||
// in all copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
|
||||
// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN
|
||||
// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
|
||||
// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
|
||||
// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
|
||||
// USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
|
||||
'use strict';
|
||||
const common = require('../common');
|
||||
const assert = require('assert');
|
||||
const http = require('http');
|
||||
|
||||
const Duplex = require('stream').Duplex;
|
||||
|
||||
class FakeAgent extends http.Agent {
|
||||
createConnection() {
|
||||
const s = new Duplex();
|
||||
let once = false;
|
||||
|
||||
s._read = function() {
|
||||
if (once)
|
||||
return this.push(null);
|
||||
once = true;
|
||||
|
||||
this.push('HTTP/1.1 200 Ok\r\nTransfer-Encoding: chunked\r\n\r\n');
|
||||
this.push('b\r\nhello world\r\n');
|
||||
this.readable = false;
|
||||
this.push('0\r\n\r\n');
|
||||
};
|
||||
|
||||
// Blackhole
|
||||
s._write = function(data, enc, cb) {
|
||||
cb();
|
||||
};
|
||||
|
||||
s.destroy = s.destroySoon = function() {
|
||||
this.writable = false;
|
||||
};
|
||||
|
||||
return s;
|
||||
}
|
||||
}
|
||||
|
||||
let received = '';
|
||||
|
||||
const req = http.request({
|
||||
agent: new FakeAgent()
|
||||
}, common.mustCall(function requestCallback(res) {
|
||||
res.on('data', function dataCallback(chunk) {
|
||||
received += chunk;
|
||||
});
|
||||
|
||||
res.on('end', common.mustCall(function endCallback() {
|
||||
assert.strictEqual(received, 'hello world');
|
||||
}));
|
||||
}));
|
||||
req.end();
|
||||
@@ -0,0 +1,26 @@
|
||||
'use strict';
|
||||
|
||||
const common = require('../common');
|
||||
const http = require('http');
|
||||
const net = require('net');
|
||||
const assert = require('assert');
|
||||
|
||||
const reqstr = 'HTTP/1.1 200 OK\r\n' +
|
||||
'Content-Length: 1\r\n' +
|
||||
'Transfer-Encoding: chunked\r\n\r\n';
|
||||
|
||||
const server = net.createServer((socket) => {
|
||||
socket.write(reqstr);
|
||||
});
|
||||
|
||||
server.listen(0, () => {
|
||||
// The callback should not be called because the server is sending
|
||||
// both a Content-Length header and a Transfer-Encoding: chunked
|
||||
// header, which is a violation of the HTTP spec.
|
||||
const req = http.get({ port: server.address().port }, common.mustNotCall());
|
||||
req.on('error', common.mustCall((err) => {
|
||||
assert.match(err.message, /^Parse Error/);
|
||||
assert.strictEqual(err.code, 'HPE_INVALID_TRANSFER_ENCODING');
|
||||
server.close();
|
||||
}));
|
||||
});
|
||||
@@ -0,0 +1,25 @@
|
||||
'use strict';
|
||||
|
||||
const common = require('../common');
|
||||
const http = require('http');
|
||||
const net = require('net');
|
||||
const assert = require('assert');
|
||||
|
||||
const reqstr = 'HTTP/1.1 200 OK\r\n' +
|
||||
'Foo: Bar\r' +
|
||||
'Content-Length: 1\r\n\r\n';
|
||||
|
||||
const server = net.createServer((socket) => {
|
||||
socket.write(reqstr);
|
||||
});
|
||||
|
||||
server.listen(0, () => {
|
||||
// The callback should not be called because the server is sending a
|
||||
// header field that ends only in \r with no following \n
|
||||
const req = http.get({ port: server.address().port }, common.mustNotCall());
|
||||
req.on('error', common.mustCall((err) => {
|
||||
assert.match(err.message, /^Parse Error/);
|
||||
assert.strictEqual(err.code, 'HPE_LF_EXPECTED');
|
||||
server.close();
|
||||
}));
|
||||
});
|
||||
@@ -0,0 +1,33 @@
|
||||
'use strict';
|
||||
|
||||
// Test https://github.com/nodejs/node/issues/25499 fix.
|
||||
|
||||
const { mustCall } = require('../common');
|
||||
|
||||
const { Agent, createServer, get } = require('http');
|
||||
const { strictEqual } = require('assert');
|
||||
|
||||
const server = createServer(mustCall((req, res) => {
|
||||
res.end();
|
||||
}));
|
||||
|
||||
server.listen(0, () => {
|
||||
const agent = new Agent({ keepAlive: true, maxSockets: 1 });
|
||||
const port = server.address().port;
|
||||
|
||||
let socket;
|
||||
|
||||
const req = get({ agent, port }, (res) => {
|
||||
res.on('end', () => {
|
||||
strictEqual(req.setTimeout(0), req);
|
||||
strictEqual(socket.listenerCount('timeout'), 1);
|
||||
agent.destroy();
|
||||
server.close();
|
||||
});
|
||||
res.resume();
|
||||
});
|
||||
|
||||
req.on('socket', (sock) => {
|
||||
socket = sock;
|
||||
});
|
||||
});
|
||||
49
test/js/node/test/parallel/test-http-client-set-timeout.js
Normal file
49
test/js/node/test/parallel/test-http-client-set-timeout.js
Normal file
@@ -0,0 +1,49 @@
|
||||
'use strict';
|
||||
|
||||
// Test that the `'timeout'` event is emitted exactly once if the `timeout`
|
||||
// option and `request.setTimeout()` are used together.
|
||||
|
||||
const { expectsError, mustCall } = require('../common');
|
||||
const { strictEqual } = require('assert');
|
||||
const { createServer, get } = require('http');
|
||||
|
||||
const server = createServer(() => {
|
||||
// Never respond.
|
||||
});
|
||||
|
||||
server.listen(0, mustCall(() => {
|
||||
const req = get({
|
||||
port: server.address().port,
|
||||
timeout: 2000,
|
||||
});
|
||||
|
||||
req.setTimeout(1000);
|
||||
|
||||
req.on('socket', mustCall((socket) => {
|
||||
strictEqual(socket.timeout, 2000);
|
||||
|
||||
socket.on('connect', mustCall(() => {
|
||||
strictEqual(socket.timeout, 1000);
|
||||
|
||||
// Reschedule the timer to not wait 1 sec and make the test finish faster.
|
||||
socket.setTimeout(10);
|
||||
strictEqual(socket.timeout, 10);
|
||||
}));
|
||||
}));
|
||||
|
||||
req.on('error', expectsError({
|
||||
name: 'Error',
|
||||
code: 'ECONNRESET',
|
||||
message: 'socket hang up'
|
||||
}));
|
||||
|
||||
req.on('close', mustCall(() => {
|
||||
strictEqual(req.destroyed, true);
|
||||
server.close();
|
||||
}));
|
||||
|
||||
req.on('timeout', mustCall(() => {
|
||||
strictEqual(req.socket.listenerCount('timeout'), 1);
|
||||
req.destroy();
|
||||
}));
|
||||
}));
|
||||
@@ -0,0 +1,47 @@
|
||||
'use strict';
|
||||
const common = require('../common');
|
||||
const http = require('http');
|
||||
const assert = require('assert');
|
||||
|
||||
const agent = new http.Agent({ keepAlive: true });
|
||||
|
||||
const server = http.createServer((req, res) => {
|
||||
res.end('');
|
||||
});
|
||||
|
||||
// Maximum allowed value for timeouts
|
||||
const timeout = 2 ** 31 - 1;
|
||||
|
||||
const options = {
|
||||
agent,
|
||||
method: 'GET',
|
||||
port: undefined,
|
||||
host: common.localhostIPv4,
|
||||
path: '/',
|
||||
timeout: timeout
|
||||
};
|
||||
|
||||
server.listen(0, options.host, common.mustCall(() => {
|
||||
options.port = server.address().port;
|
||||
doRequest(common.mustCall((numListeners) => {
|
||||
assert.strictEqual(numListeners, 3);
|
||||
doRequest(common.mustCall((numListeners) => {
|
||||
assert.strictEqual(numListeners, 3);
|
||||
server.close();
|
||||
agent.destroy();
|
||||
}));
|
||||
}));
|
||||
}));
|
||||
|
||||
function doRequest(cb) {
|
||||
http.request(options, common.mustCall((response) => {
|
||||
const sockets = agent.sockets[`${options.host}:${options.port}:`];
|
||||
assert.strictEqual(sockets.length, 1);
|
||||
const socket = sockets[0];
|
||||
const numListeners = socket.listeners('timeout').length;
|
||||
response.resume();
|
||||
response.once('end', common.mustCall(() => {
|
||||
process.nextTick(cb, numListeners);
|
||||
}));
|
||||
})).end();
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
'use strict';
|
||||
|
||||
// Test that the request `timeout` option has precedence over the agent
|
||||
// `timeout` option.
|
||||
|
||||
const { mustCall } = require('../common');
|
||||
const { Agent, get } = require('http');
|
||||
const { strictEqual } = require('assert');
|
||||
|
||||
const request = get({
|
||||
agent: new Agent({ timeout: 50 }),
|
||||
lookup: () => {},
|
||||
timeout: 100
|
||||
});
|
||||
|
||||
request.on('socket', mustCall((socket) => {
|
||||
strictEqual(socket.timeout, 100);
|
||||
|
||||
const listeners = socket.listeners('timeout');
|
||||
|
||||
strictEqual(listeners.length, 2);
|
||||
strictEqual(listeners[1], request.timeoutCb);
|
||||
}));
|
||||
@@ -0,0 +1,63 @@
|
||||
// Copyright Joyent, Inc. and other Node contributors.
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a
|
||||
// copy of this software and associated documentation files (the
|
||||
// "Software"), to deal in the Software without restriction, including
|
||||
// without limitation the rights to use, copy, modify, merge, publish,
|
||||
// distribute, sublicense, and/or sell copies of the Software, and to permit
|
||||
// persons to whom the Software is furnished to do so, subject to the
|
||||
// following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be included
|
||||
// in all copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
|
||||
// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN
|
||||
// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
|
||||
// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
|
||||
// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
|
||||
// USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
|
||||
'use strict';
|
||||
const common = require('../common');
|
||||
const assert = require('assert');
|
||||
const http = require('http');
|
||||
|
||||
let nchunks = 0;
|
||||
|
||||
const options = {
|
||||
method: 'GET',
|
||||
port: undefined,
|
||||
host: '127.0.0.1',
|
||||
path: '/'
|
||||
};
|
||||
|
||||
const server = http.createServer(function(req, res) {
|
||||
res.writeHead(200, { 'Content-Length': '2' });
|
||||
res.write('*');
|
||||
server.once('timeout', common.mustCall(function() { res.end('*'); }));
|
||||
});
|
||||
|
||||
server.listen(0, options.host, function() {
|
||||
options.port = this.address().port;
|
||||
const req = http.request(options, onresponse);
|
||||
req.end();
|
||||
|
||||
function onresponse(res) {
|
||||
req.setTimeout(50, common.mustCall(function() {
|
||||
assert.strictEqual(nchunks, 1); // Should have received the first chunk
|
||||
server.emit('timeout');
|
||||
}));
|
||||
|
||||
res.on('data', common.mustCall(function(data) {
|
||||
assert.strictEqual(String(data), '*');
|
||||
nchunks++;
|
||||
}, 2));
|
||||
|
||||
res.on('end', common.mustCall(function() {
|
||||
assert.strictEqual(nchunks, 2);
|
||||
server.close();
|
||||
}));
|
||||
}
|
||||
});
|
||||
90
test/js/node/test/parallel/test-http-createConnection.js
Normal file
90
test/js/node/test/parallel/test-http-createConnection.js
Normal file
@@ -0,0 +1,90 @@
|
||||
// Copyright Joyent, Inc. and other Node contributors.
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a
|
||||
// copy of this software and associated documentation files (the
|
||||
// "Software"), to deal in the Software without restriction, including
|
||||
// without limitation the rights to use, copy, modify, merge, publish,
|
||||
// distribute, sublicense, and/or sell copies of the Software, and to permit
|
||||
// persons to whom the Software is furnished to do so, subject to the
|
||||
// following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be included
|
||||
// in all copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
|
||||
// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN
|
||||
// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
|
||||
// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
|
||||
// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
|
||||
// USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
|
||||
'use strict';
|
||||
const common = require('../common');
|
||||
const http = require('http');
|
||||
const net = require('net');
|
||||
const assert = require('assert');
|
||||
|
||||
function commonHttpGet(fn) {
|
||||
if (typeof fn === 'function') {
|
||||
fn = common.mustCall(fn);
|
||||
}
|
||||
return new Promise((resolve, reject) => {
|
||||
http.get({ createConnection: fn }, (res) => {
|
||||
resolve(res);
|
||||
}).on('error', (err) => {
|
||||
reject(err);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
const server = http.createServer(common.mustCall(function(req, res) {
|
||||
res.end();
|
||||
}, 4)).listen(0, '127.0.0.1', async () => {
|
||||
await commonHttpGet(createConnection);
|
||||
await commonHttpGet(createConnectionAsync);
|
||||
await commonHttpGet(createConnectionBoth1);
|
||||
await commonHttpGet(createConnectionBoth2);
|
||||
|
||||
// Errors
|
||||
await assert.rejects(() => commonHttpGet(createConnectionError), {
|
||||
message: 'sync'
|
||||
});
|
||||
await assert.rejects(() => commonHttpGet(createConnectionAsyncError), {
|
||||
message: 'async'
|
||||
});
|
||||
|
||||
server.close();
|
||||
});
|
||||
|
||||
function createConnection() {
|
||||
return net.createConnection(server.address().port, '127.0.0.1');
|
||||
}
|
||||
|
||||
function createConnectionAsync(options, cb) {
|
||||
setImmediate(function() {
|
||||
cb(null, net.createConnection(server.address().port, '127.0.0.1'));
|
||||
});
|
||||
}
|
||||
|
||||
function createConnectionBoth1(options, cb) {
|
||||
const socket = net.createConnection(server.address().port, '127.0.0.1');
|
||||
setImmediate(function() {
|
||||
cb(null, socket);
|
||||
});
|
||||
return socket;
|
||||
}
|
||||
|
||||
function createConnectionBoth2(options, cb) {
|
||||
const socket = net.createConnection(server.address().port, '127.0.0.1');
|
||||
cb(null, socket);
|
||||
return socket;
|
||||
}
|
||||
|
||||
function createConnectionError(options, cb) {
|
||||
throw new Error('sync');
|
||||
}
|
||||
|
||||
function createConnectionAsyncError(options, cb) {
|
||||
process.nextTick(cb, new Error('async'));
|
||||
}
|
||||
60
test/js/node/test/parallel/test-http-default-port.js
Normal file
60
test/js/node/test/parallel/test-http-default-port.js
Normal file
@@ -0,0 +1,60 @@
|
||||
// Copyright Joyent, Inc. and other Node contributors.
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a
|
||||
// copy of this software and associated documentation files (the
|
||||
// "Software"), to deal in the Software without restriction, including
|
||||
// without limitation the rights to use, copy, modify, merge, publish,
|
||||
// distribute, sublicense, and/or sell copies of the Software, and to permit
|
||||
// persons to whom the Software is furnished to do so, subject to the
|
||||
// following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be included
|
||||
// in all copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
|
||||
// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN
|
||||
// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
|
||||
// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
|
||||
// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
|
||||
// USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
|
||||
'use strict';
|
||||
const common = require('../common');
|
||||
|
||||
if (!common.hasCrypto)
|
||||
common.skip('missing crypto');
|
||||
|
||||
const fixtures = require('../common/fixtures');
|
||||
const http = require('http');
|
||||
const https = require('https');
|
||||
const assert = require('assert');
|
||||
const hostExpect = 'localhost';
|
||||
const options = {
|
||||
key: fixtures.readKey('agent1-key.pem'),
|
||||
cert: fixtures.readKey('agent1-cert.pem')
|
||||
};
|
||||
|
||||
for (const { mod, createServer } of [
|
||||
{ mod: http, createServer: http.createServer },
|
||||
{ mod: https, createServer: https.createServer.bind(null, options) },
|
||||
]) {
|
||||
const server = createServer(common.mustCall((req, res) => {
|
||||
assert.strictEqual(req.headers.host, hostExpect);
|
||||
assert.strictEqual(req.headers['x-port'], `${server.address().port}`);
|
||||
res.writeHead(200);
|
||||
res.end('ok');
|
||||
server.close();
|
||||
})).listen(0, common.mustCall(() => {
|
||||
mod.globalAgent.defaultPort = server.address().port;
|
||||
mod.get({
|
||||
host: 'localhost',
|
||||
rejectUnauthorized: false,
|
||||
headers: {
|
||||
'x-port': server.address().port
|
||||
}
|
||||
}, common.mustCall((res) => {
|
||||
res.resume();
|
||||
}));
|
||||
}));
|
||||
}
|
||||
@@ -0,0 +1,79 @@
|
||||
// Copyright Joyent, Inc. and other Node contributors.
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a
|
||||
// copy of this software and associated documentation files (the
|
||||
// "Software"), to deal in the Software without restriction, including
|
||||
// without limitation the rights to use, copy, modify, merge, publish,
|
||||
// distribute, sublicense, and/or sell copies of the Software, and to permit
|
||||
// persons to whom the Software is furnished to do so, subject to the
|
||||
// following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be included
|
||||
// in all copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
|
||||
// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN
|
||||
// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
|
||||
// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
|
||||
// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
|
||||
// USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
|
||||
'use strict';
|
||||
const common = require('../common');
|
||||
|
||||
// Verify that ECONNRESET is raised when writing to a http request
|
||||
// where the server has ended the socket.
|
||||
|
||||
const assert = require('assert');
|
||||
const http = require('http');
|
||||
|
||||
const kResponseDestroyed = Symbol('kResponseDestroyed');
|
||||
|
||||
const server = http.createServer(function(req, res) {
|
||||
req.on('data', common.mustCall(function() {
|
||||
res.destroy();
|
||||
server.emit(kResponseDestroyed);
|
||||
}));
|
||||
});
|
||||
|
||||
server.listen(0, function() {
|
||||
const req = http.request({
|
||||
port: this.address().port,
|
||||
path: '/',
|
||||
method: 'POST'
|
||||
});
|
||||
|
||||
server.once(kResponseDestroyed, common.mustCall(function() {
|
||||
req.write('hello');
|
||||
}));
|
||||
|
||||
req.on('error', common.mustCall(function(er) {
|
||||
assert.strictEqual(req.res, null);
|
||||
switch (er.code) {
|
||||
// This is the expected case
|
||||
case 'ECONNRESET':
|
||||
break;
|
||||
|
||||
// On Windows, this sometimes manifests as ECONNABORTED
|
||||
case 'ECONNABORTED':
|
||||
break;
|
||||
|
||||
// This test is timing sensitive so an EPIPE is not out of the question.
|
||||
// It should be infrequent, given the 50 ms timeout, but not impossible.
|
||||
case 'EPIPE':
|
||||
break;
|
||||
|
||||
default:
|
||||
// Write to a torn down client should RESET or ABORT
|
||||
assert.fail(`Unexpected error code ${er.code}`);
|
||||
}
|
||||
|
||||
|
||||
assert.strictEqual(req.outputData.length, 0);
|
||||
server.close();
|
||||
}));
|
||||
|
||||
req.on('response', common.mustNotCall());
|
||||
req.write('hello', common.mustSucceed());
|
||||
});
|
||||
@@ -0,0 +1,31 @@
|
||||
'use strict';
|
||||
|
||||
const common = require('../common');
|
||||
const assert = require('assert');
|
||||
const http = require('http');
|
||||
|
||||
const server = http.createServer(common.mustCall(function(req, res) {
|
||||
assert.deepStrictEqual(req.rawHeaders, [
|
||||
'host', `${common.localhostIPv4}:${server.address().port}`,
|
||||
'foo', 'bar',
|
||||
'test', 'value',
|
||||
'foo', 'baz',
|
||||
]);
|
||||
|
||||
res.end('ok');
|
||||
server.close();
|
||||
}));
|
||||
server.listen(0, common.localhostIPv4, function() {
|
||||
http.request({
|
||||
method: 'POST',
|
||||
host: common.localhostIPv4,
|
||||
port: this.address().port,
|
||||
setDefaultHeaders: false,
|
||||
headers: [
|
||||
'host', `${common.localhostIPv4}:${server.address().port}`,
|
||||
'foo', 'bar',
|
||||
'test', 'value',
|
||||
'foo', 'baz',
|
||||
]
|
||||
}).end();
|
||||
});
|
||||
@@ -1,5 +1,6 @@
|
||||
'use strict';
|
||||
const { expectsError, mustCall } = require('../common');
|
||||
if ('Bun' in globalThis) require('../common').skip("TODO: BUN: test was edited and never worked");
|
||||
const assert = require('assert');
|
||||
const { createServer, maxHeaderSize } = require('http');
|
||||
const { createConnection } = require('net');
|
||||
@@ -16,22 +17,23 @@ const PAYLOAD = PAYLOAD_GET + CRLF + DUMMY_HEADER_NAME + DUMMY_HEADER_VALUE;
|
||||
const server = createServer();
|
||||
|
||||
server.on('connection', mustCall((socket) => {
|
||||
|
||||
socket.on('error', expectsError({
|
||||
name: 'Error',
|
||||
message: 'Parse Error: Header overflow',
|
||||
code: 'HPE_HEADER_OVERFLOW',
|
||||
// those can be inconsistent depending if everything is sended in one go or not
|
||||
// bytesParsed: PAYLOAD.length,
|
||||
// rawPacket: Buffer.from(PAYLOAD)
|
||||
bytesParsed: PAYLOAD.length,
|
||||
rawPacket: Buffer.from(PAYLOAD)
|
||||
}));
|
||||
|
||||
// The data is not sent from the client to ensure that it is received as a
|
||||
// single chunk.
|
||||
socket.push(PAYLOAD);
|
||||
}));
|
||||
|
||||
server.listen(0, mustCall(() => {
|
||||
const c = createConnection(server.address().port);
|
||||
let received = '';
|
||||
c.write(PAYLOAD);
|
||||
|
||||
c.on('data', mustCall((data) => {
|
||||
received += data.toString();
|
||||
}));
|
||||
|
||||
@@ -0,0 +1,41 @@
|
||||
'use strict';
|
||||
|
||||
// When using the object form of http.request and using an IPv6 address
|
||||
// as a hostname, and using a non-standard port, the Host header
|
||||
// is improperly formatted.
|
||||
// Issue: https://github.com/nodejs/node/issues/5308
|
||||
// As per https://tools.ietf.org/html/rfc7230#section-5.4 and
|
||||
// https://tools.ietf.org/html/rfc3986#section-3.2.2
|
||||
// the IPv6 address should be enclosed in square brackets
|
||||
|
||||
const common = require('../common');
|
||||
const assert = require('assert');
|
||||
const http = require('http');
|
||||
const net = require('net');
|
||||
|
||||
const requests = [
|
||||
{ host: 'foo:1234', headers: { expectedhost: 'foo:1234:80' } },
|
||||
{ host: '::1', headers: { expectedhost: '[::1]:80' } },
|
||||
];
|
||||
|
||||
function createLocalConnection(options) {
|
||||
options.host = undefined;
|
||||
options.port = this.port;
|
||||
options.path = undefined;
|
||||
return net.createConnection(options);
|
||||
}
|
||||
|
||||
http.createServer(common.mustCall(function(req, res) {
|
||||
this.requests ||= 0;
|
||||
assert.strictEqual(req.headers.host, req.headers.expectedhost);
|
||||
res.end();
|
||||
if (++this.requests === requests.length)
|
||||
this.close();
|
||||
}, requests.length)).listen(0, function() {
|
||||
const address = this.address();
|
||||
for (let i = 0; i < requests.length; ++i) {
|
||||
requests[i].createConnection =
|
||||
common.mustCall(createLocalConnection.bind(address));
|
||||
http.get(requests[i]);
|
||||
}
|
||||
});
|
||||
@@ -15,6 +15,7 @@ vals.forEach((v) => {
|
||||
{
|
||||
code: 'ERR_INVALID_ARG_TYPE',
|
||||
name: 'TypeError',
|
||||
message: 'The "options.hostname" property must be of type string, undefined, or null.' + received
|
||||
}
|
||||
);
|
||||
|
||||
@@ -23,6 +24,7 @@ vals.forEach((v) => {
|
||||
{
|
||||
code: 'ERR_INVALID_ARG_TYPE',
|
||||
name: 'TypeError',
|
||||
message: 'The "options.host" property must be of type string, undefined, or null.' + received
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user