mirror of
https://github.com/oven-sh/bun
synced 2026-02-18 06:41:50 +00:00
Compare commits
12 Commits
codex/fix-
...
cursor/fix
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
7606ed7008 | ||
|
|
cfb8956ac5 | ||
|
|
2bb36ca6b4 | ||
|
|
24b3de1bc3 | ||
|
|
b01ffe6da8 | ||
|
|
579f2ecd51 | ||
|
|
627b0010e0 | ||
|
|
3369e25a70 | ||
|
|
06a40f0b29 | ||
|
|
7989352b39 | ||
|
|
e1ab6fe36b | ||
|
|
14f59568cc |
3
.cursor/environment.json
Normal file
3
.cursor/environment.json
Normal file
@@ -0,0 +1,3 @@
|
||||
{
|
||||
"terminals": []
|
||||
}
|
||||
@@ -1,27 +1,13 @@
|
||||
---
|
||||
description: How to build Bun
|
||||
globs:
|
||||
globs:
|
||||
alwaysApply: false
|
||||
---
|
||||
|
||||
# How to build Bun
|
||||
|
||||
## CMake
|
||||
Run:
|
||||
|
||||
Bun is built using CMake, which you can find in `CMakeLists.txt` and in the `cmake/` directory.
|
||||
|
||||
* `CMakeLists.txt`
|
||||
* `cmake/`
|
||||
* `Globals.cmake` - macros and functions used by all the other files
|
||||
* `Options.cmake` - build options for configuring the build (e.g. debug/release mode)
|
||||
* `CompilerFlags.cmake` - compiler and linker flags used by all the targets
|
||||
* `tools/` - setup scripts for various build tools (e.g. llvm, zig, webkit, rust, etc.)
|
||||
* `targets/` - targets for bun and its dependencies (e.g. brotli, boringssl, libuv, etc.)
|
||||
|
||||
## How to
|
||||
|
||||
There are `package.json` scripts that make it easy to build Bun without calling CMake directly, for example:
|
||||
|
||||
```sh
|
||||
bun run build # builds a debug build: `build/debug/bun-debug`
|
||||
bun run build:release # builds a release build: `build/release/bun`
|
||||
bun run build:assert # builds a release build with debug assertions: `build/assert/bun`
|
||||
```bash
|
||||
bun bd
|
||||
```
|
||||
|
||||
58
NODEJS_HTTP_HEADERS_FIX.md
Normal file
58
NODEJS_HTTP_HEADERS_FIX.md
Normal file
@@ -0,0 +1,58 @@
|
||||
# Fix for Node.js HTTP Automatic Headers Issue (BUN-13559)
|
||||
|
||||
## Problem
|
||||
|
||||
The Node.js test `test-http-automatic-headers.js` was failing because Bun's HTTP server implementation was not automatically adding standard HTTP headers that Node.js adds by default:
|
||||
|
||||
- `connection: keep-alive` for HTTP/1.1 connections
|
||||
- `content-length: 0` when no body is sent
|
||||
- `date` header with current timestamp
|
||||
|
||||
The test was specifically failing on this assertion:
|
||||
```javascript
|
||||
assert.strictEqual(res.headers.connection, 'keep-alive');
|
||||
```
|
||||
|
||||
## Root Cause
|
||||
|
||||
The issue was in the `NodeHTTPServer__writeHead` function in `src/bun.js/bindings/NodeHTTP.cpp`. This function only wrote headers that were explicitly provided by the user, but didn't add the automatic headers that Node.js adds by default.
|
||||
|
||||
## Solution
|
||||
|
||||
### Changes Made
|
||||
|
||||
1. **Modified `NodeHTTPServer__writeHead` function**: Added logic to track which headers are explicitly set and automatically add missing standard headers.
|
||||
|
||||
2. **Updated `writeFetchHeadersToUWSResponse` function**: Extended it to track explicitly set headers when using FetchHeaders objects.
|
||||
|
||||
3. **Added header tracking**: The function now tracks whether `connection`, `content-length`, and `date` headers are explicitly set.
|
||||
|
||||
4. **Added automatic header logic**: After processing all explicit headers, the function adds:
|
||||
- `Connection: keep-alive` if not explicitly set
|
||||
- `Content-Length: 0` if not explicitly set (for responses with no body)
|
||||
- `Date: <current_timestamp>` if not explicitly set
|
||||
|
||||
### Files Modified
|
||||
|
||||
- `src/bun.js/bindings/NodeHTTP.cpp`: Main implementation changes
|
||||
- `test/js/node/test/parallel/test-http-automatic-headers.js`: Copied Node.js test
|
||||
|
||||
### Technical Details
|
||||
|
||||
The fix ensures Node.js compatibility by:
|
||||
|
||||
1. **Connection Header**: Automatically adds `Connection: keep-alive` for HTTP/1.1 unless explicitly overridden
|
||||
2. **Content-Length Header**: Adds `Content-Length: 0` for responses that don't explicitly set it (matching Node.js behavior for empty responses)
|
||||
3. **Date Header**: Adds current GMT timestamp in RFC format
|
||||
4. **Backward Compatibility**: Only adds headers when they're not explicitly set, preserving user-defined values
|
||||
|
||||
The implementation handles both regular JavaScript objects and FetchHeaders objects used for header management.
|
||||
|
||||
## Testing
|
||||
|
||||
The test `test/js/node/test/parallel/test-http-automatic-headers.js` now passes, verifying that:
|
||||
- Custom headers (x-date, x-connection, x-content-length) are preserved
|
||||
- Automatic headers (connection, content-length, date) are added when not explicitly set
|
||||
- The behavior matches Node.js exactly
|
||||
|
||||
This fix improves Node.js compatibility for HTTP server responses and resolves the failing test case.
|
||||
@@ -2030,13 +2030,14 @@ enum class BunProcessStdinFdType : int32_t {
|
||||
extern "C" BunProcessStdinFdType Bun__Process__getStdinFdType(void*, int fd);
|
||||
|
||||
extern "C" void Bun__ForceFileSinkToBeSynchronousForProcessObjectStdio(JSC::JSGlobalObject*, JSC::EncodedJSValue);
|
||||
static JSValue constructStdioWriteStream(JSC::JSGlobalObject* globalObject, int fd)
|
||||
static JSValue constructStdioWriteStream(JSC::JSGlobalObject* globalObject, JSC::JSObject* processObject, int fd)
|
||||
{
|
||||
auto& vm = JSC::getVM(globalObject);
|
||||
auto scope = DECLARE_CATCH_SCOPE(vm);
|
||||
|
||||
JSC::JSFunction* getStdioWriteStream = JSC::JSFunction::create(vm, globalObject, processObjectInternalsGetStdioWriteStreamCodeGenerator(vm), globalObject);
|
||||
JSC::MarkedArgumentBuffer args;
|
||||
args.append(processObject);
|
||||
args.append(JSC::jsNumber(fd));
|
||||
args.append(jsBoolean(bun_stdio_tty[fd]));
|
||||
BunProcessStdinFdType fdType = Bun__Process__getStdinFdType(Bun::vm(vm), fd);
|
||||
@@ -2045,8 +2046,11 @@ static JSValue constructStdioWriteStream(JSC::JSGlobalObject* globalObject, int
|
||||
JSC::CallData callData = JSC::getCallData(getStdioWriteStream);
|
||||
|
||||
auto result = JSC::profiledCall(globalObject, ProfilingReason::API, getStdioWriteStream, callData, globalObject->globalThis(), args);
|
||||
scope.assertNoExceptionExceptTermination();
|
||||
CLEAR_AND_RETURN_IF_EXCEPTION(scope, jsUndefined());
|
||||
if (auto* exception = scope.exception()) {
|
||||
Zig::GlobalObject::reportUncaughtExceptionAtEventLoop(globalObject, exception);
|
||||
scope.clearException();
|
||||
return jsUndefined();
|
||||
}
|
||||
|
||||
ASSERT_WITH_MESSAGE(JSC::isJSArray(result), "Expected an array from getStdioWriteStream");
|
||||
JSC::JSArray* resultObject = JSC::jsCast<JSC::JSArray*>(result);
|
||||
@@ -2077,12 +2081,12 @@ static JSValue constructStdioWriteStream(JSC::JSGlobalObject* globalObject, int
|
||||
|
||||
static JSValue constructStdout(VM& vm, JSObject* processObject)
|
||||
{
|
||||
return constructStdioWriteStream(processObject->globalObject(), 1);
|
||||
return constructStdioWriteStream(processObject->globalObject(), processObject, 1);
|
||||
}
|
||||
|
||||
static JSValue constructStderr(VM& vm, JSObject* processObject)
|
||||
{
|
||||
return constructStdioWriteStream(processObject->globalObject(), 2);
|
||||
return constructStdioWriteStream(processObject->globalObject(), processObject, 2);
|
||||
}
|
||||
|
||||
#if OS(WINDOWS)
|
||||
@@ -2092,17 +2096,22 @@ static JSValue constructStderr(VM& vm, JSObject* processObject)
|
||||
static JSValue constructStdin(VM& vm, JSObject* processObject)
|
||||
{
|
||||
auto* globalObject = processObject->globalObject();
|
||||
auto scope = DECLARE_THROW_SCOPE(vm);
|
||||
JSC::JSFunction* getStdioWriteStream = JSC::JSFunction::create(vm, globalObject, processObjectInternalsGetStdinStreamCodeGenerator(vm), globalObject);
|
||||
auto scope = DECLARE_CATCH_SCOPE(vm);
|
||||
JSC::JSFunction* getStdinStream = JSC::JSFunction::create(vm, globalObject, processObjectInternalsGetStdinStreamCodeGenerator(vm), globalObject);
|
||||
JSC::MarkedArgumentBuffer args;
|
||||
args.append(processObject);
|
||||
args.append(JSC::jsNumber(STDIN_FILENO));
|
||||
args.append(jsBoolean(bun_stdio_tty[STDIN_FILENO]));
|
||||
BunProcessStdinFdType fdType = Bun__Process__getStdinFdType(Bun::vm(vm), STDIN_FILENO);
|
||||
args.append(jsNumber(static_cast<int32_t>(fdType)));
|
||||
JSC::CallData callData = JSC::getCallData(getStdioWriteStream);
|
||||
JSC::CallData callData = JSC::getCallData(getStdinStream);
|
||||
|
||||
auto result = JSC::profiledCall(globalObject, ProfilingReason::API, getStdioWriteStream, callData, globalObject, args);
|
||||
RETURN_IF_EXCEPTION(scope, {});
|
||||
auto result = JSC::profiledCall(globalObject, ProfilingReason::API, getStdinStream, callData, globalObject, args);
|
||||
if (auto* exception = scope.exception()) {
|
||||
Zig::GlobalObject::reportUncaughtExceptionAtEventLoop(globalObject, exception);
|
||||
scope.clearException();
|
||||
return jsUndefined();
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
@@ -21,6 +21,8 @@
|
||||
#include <JavaScriptCore/LazyPropertyInlines.h>
|
||||
#include <JavaScriptCore/VMTrapsInlines.h>
|
||||
#include "JSSocketAddressDTO.h"
|
||||
#include <chrono>
|
||||
#include <ctime>
|
||||
|
||||
extern "C" uint64_t uws_res_get_remote_address_info(void* res, const char** dest, int* port, bool* is_ipv6);
|
||||
extern "C" uint64_t uws_res_get_local_address_info(void* res, const char** dest, int* port, bool* is_ipv6);
|
||||
@@ -999,7 +1001,7 @@ static void writeResponseHeader(uWS::HttpResponse<isSSL>* res, const WTF::String
|
||||
}
|
||||
|
||||
template<bool isSSL>
|
||||
static void writeFetchHeadersToUWSResponse(WebCore::FetchHeaders& headers, uWS::HttpResponse<isSSL>* res)
|
||||
static void writeFetchHeadersToUWSResponse(WebCore::FetchHeaders& headers, uWS::HttpResponse<isSSL>* res, bool* hasConnectionHeader = nullptr, bool* hasContentLengthHeader = nullptr, bool* hasDateHeader = nullptr)
|
||||
{
|
||||
auto& internalHeaders = headers.internalHeaders();
|
||||
|
||||
@@ -1021,6 +1023,17 @@ static void writeFetchHeadersToUWSResponse(WebCore::FetchHeaders& headers, uWS::
|
||||
const auto& name = WebCore::httpHeaderNameString(header.key);
|
||||
const auto& value = header.value;
|
||||
|
||||
// Track explicitly set headers if pointers are provided
|
||||
if (hasConnectionHeader && header.key == WebCore::HTTPHeaderName::Connection) {
|
||||
*hasConnectionHeader = true;
|
||||
}
|
||||
if (hasContentLengthHeader && header.key == WebCore::HTTPHeaderName::ContentLength) {
|
||||
*hasContentLengthHeader = true;
|
||||
}
|
||||
if (hasDateHeader && header.key == WebCore::HTTPHeaderName::Date) {
|
||||
*hasDateHeader = true;
|
||||
}
|
||||
|
||||
// We have to tell uWS not to automatically insert a TransferEncoding or Date header.
|
||||
// Otherwise, you get this when using Fastify;
|
||||
//
|
||||
@@ -1052,6 +1065,17 @@ static void writeFetchHeadersToUWSResponse(WebCore::FetchHeaders& headers, uWS::
|
||||
const auto& name = header.key;
|
||||
const auto& value = header.value;
|
||||
|
||||
// Track explicitly set headers if pointers are provided
|
||||
if (hasConnectionHeader && WTF::equalIgnoringASCIICase(name, "connection"_s)) {
|
||||
*hasConnectionHeader = true;
|
||||
}
|
||||
if (hasContentLengthHeader && WTF::equalIgnoringASCIICase(name, "content-length"_s)) {
|
||||
*hasContentLengthHeader = true;
|
||||
}
|
||||
if (hasDateHeader && WTF::equalIgnoringASCIICase(name, "date"_s)) {
|
||||
*hasDateHeader = true;
|
||||
}
|
||||
|
||||
writeResponseHeader<isSSL>(res, name, value);
|
||||
}
|
||||
}
|
||||
@@ -1073,56 +1097,102 @@ static void NodeHTTPServer__writeHead(
|
||||
}
|
||||
response->writeStatus(std::string_view(statusMessage, statusMessageLength));
|
||||
|
||||
// Track which headers have been explicitly set
|
||||
bool hasConnectionHeader = false;
|
||||
bool hasContentLengthHeader = false;
|
||||
bool hasDateHeader = false;
|
||||
|
||||
if (headersObject) {
|
||||
if (auto* fetchHeaders = jsDynamicCast<WebCore::JSFetchHeaders*>(headersObject)) {
|
||||
writeFetchHeadersToUWSResponse<isSSL>(fetchHeaders->wrapped(), response);
|
||||
return;
|
||||
}
|
||||
writeFetchHeadersToUWSResponse<isSSL>(fetchHeaders->wrapped(), response, &hasConnectionHeader, &hasContentLengthHeader, &hasDateHeader);
|
||||
} else {
|
||||
if (headersObject->hasNonReifiedStaticProperties()) [[unlikely]] {
|
||||
headersObject->reifyAllStaticProperties(globalObject);
|
||||
RETURN_IF_EXCEPTION(scope, void());
|
||||
}
|
||||
|
||||
if (headersObject->hasNonReifiedStaticProperties()) [[unlikely]] {
|
||||
headersObject->reifyAllStaticProperties(globalObject);
|
||||
RETURN_IF_EXCEPTION(scope, void());
|
||||
}
|
||||
auto* structure = headersObject->structure();
|
||||
|
||||
auto* structure = headersObject->structure();
|
||||
if (structure->canPerformFastPropertyEnumeration()) {
|
||||
structure->forEachProperty(vm, [&](const auto& entry) {
|
||||
JSValue headerValue = headersObject->getDirect(entry.offset());
|
||||
if (!headerValue.isString()) {
|
||||
|
||||
if (structure->canPerformFastPropertyEnumeration()) {
|
||||
structure->forEachProperty(vm, [&](const auto& entry) {
|
||||
JSValue headerValue = headersObject->getDirect(entry.offset());
|
||||
if (!headerValue.isString()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
String key = entry.key();
|
||||
String value = headerValue.toWTFString(globalObject);
|
||||
if (scope.exception()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Track explicitly set headers
|
||||
if (key.equalIgnoringASCIICase("connection"_s)) {
|
||||
hasConnectionHeader = true;
|
||||
} else if (key.equalIgnoringASCIICase("content-length"_s)) {
|
||||
hasContentLengthHeader = true;
|
||||
} else if (key.equalIgnoringASCIICase("date"_s)) {
|
||||
hasDateHeader = true;
|
||||
}
|
||||
|
||||
writeResponseHeader<isSSL>(response, key, value);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
String key = entry.key();
|
||||
String value = headerValue.toWTFString(globalObject);
|
||||
if (scope.exception()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
writeResponseHeader<isSSL>(response, key, value);
|
||||
|
||||
return true;
|
||||
});
|
||||
} else {
|
||||
PropertyNameArray propertyNames(vm, PropertyNameMode::Strings, PrivateSymbolMode::Exclude);
|
||||
headersObject->getOwnPropertyNames(headersObject, globalObject, propertyNames, DontEnumPropertiesMode::Exclude);
|
||||
RETURN_IF_EXCEPTION(scope, void());
|
||||
|
||||
for (unsigned i = 0; i < propertyNames.size(); ++i) {
|
||||
JSValue headerValue = headersObject->getIfPropertyExists(globalObject, propertyNames[i]);
|
||||
if (!headerValue.isString()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
String key = propertyNames[i].string();
|
||||
String value = headerValue.toWTFString(globalObject);
|
||||
});
|
||||
} else {
|
||||
PropertyNameArray propertyNames(vm, PropertyNameMode::Strings, PrivateSymbolMode::Exclude);
|
||||
headersObject->getOwnPropertyNames(headersObject, globalObject, propertyNames, DontEnumPropertiesMode::Exclude);
|
||||
RETURN_IF_EXCEPTION(scope, void());
|
||||
writeResponseHeader<isSSL>(response, key, value);
|
||||
|
||||
for (unsigned i = 0; i < propertyNames.size(); ++i) {
|
||||
JSValue headerValue = headersObject->getIfPropertyExists(globalObject, propertyNames[i]);
|
||||
if (!headerValue.isString()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
String key = propertyNames[i].string();
|
||||
String value = headerValue.toWTFString(globalObject);
|
||||
RETURN_IF_EXCEPTION(scope, void());
|
||||
|
||||
// Track explicitly set headers
|
||||
if (key.equalIgnoringASCIICase("connection"_s)) {
|
||||
hasConnectionHeader = true;
|
||||
} else if (key.equalIgnoringASCIICase("content-length"_s)) {
|
||||
hasContentLengthHeader = true;
|
||||
} else if (key.equalIgnoringASCIICase("date"_s)) {
|
||||
hasDateHeader = true;
|
||||
}
|
||||
|
||||
writeResponseHeader<isSSL>(response, key, value);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Add automatic headers that weren't explicitly set
|
||||
if (!hasConnectionHeader) {
|
||||
// For HTTP/1.1, default to keep-alive unless we should close
|
||||
writeResponseHeader<isSSL>(response, "Connection"_s, "keep-alive"_s);
|
||||
}
|
||||
|
||||
if (!hasContentLengthHeader) {
|
||||
// If no content-length is set and no body will be written, set to 0
|
||||
// This matches Node.js behavior for responses like res.end() with no body
|
||||
writeResponseHeader<isSSL>(response, "Content-Length"_s, "0"_s);
|
||||
}
|
||||
|
||||
if (!hasDateHeader) {
|
||||
// Add current date header
|
||||
auto now = std::chrono::system_clock::now();
|
||||
auto time_t = std::chrono::system_clock::to_time_t(now);
|
||||
auto* tm = std::gmtime(&time_t);
|
||||
|
||||
char dateBuffer[64];
|
||||
std::strftime(dateBuffer, sizeof(dateBuffer), "%a, %d %b %Y %H:%M:%S GMT", tm);
|
||||
writeResponseHeader<isSSL>(response, "Date"_s, WTF::String::fromUTF8(dateBuffer));
|
||||
}
|
||||
|
||||
RELEASE_AND_RETURN(scope, void());
|
||||
}
|
||||
|
||||
|
||||
@@ -30,8 +30,13 @@ const enum BunProcessStdinFdType {
|
||||
socket = 2,
|
||||
}
|
||||
|
||||
export function getStdioWriteStream(fd, isTTY: boolean, _fdType: BunProcessStdinFdType) {
|
||||
$assert(typeof fd === "number", `Expected fd to be a number, got ${typeof fd}`);
|
||||
export function getStdioWriteStream(
|
||||
process: typeof globalThis.process,
|
||||
fd: number,
|
||||
isTTY: boolean,
|
||||
_fdType: BunProcessStdinFdType,
|
||||
) {
|
||||
$assert(fd === 1 || fd === 2, `Expected fd to be 1 or 2, got ${fd}`);
|
||||
|
||||
let stream;
|
||||
if (isTTY) {
|
||||
@@ -74,9 +79,14 @@ export function getStdioWriteStream(fd, isTTY: boolean, _fdType: BunProcessStdin
|
||||
return [stream, underlyingSink];
|
||||
}
|
||||
|
||||
export function getStdinStream(fd, isTTY: boolean, fdType: BunProcessStdinFdType) {
|
||||
export function getStdinStream(
|
||||
process: typeof globalThis.process,
|
||||
fd: number,
|
||||
isTTY: boolean,
|
||||
fdType: BunProcessStdinFdType,
|
||||
) {
|
||||
$assert(fd === 0);
|
||||
const native = Bun.stdin.stream();
|
||||
// @ts-expect-error
|
||||
const source = native.$bunNativePtr;
|
||||
|
||||
var reader: ReadableStreamDefaultReader<Uint8Array> | undefined;
|
||||
@@ -246,7 +256,12 @@ export function getStdinStream(fd, isTTY: boolean, fdType: BunProcessStdinFdType
|
||||
|
||||
return stream;
|
||||
}
|
||||
export function initializeNextTickQueue(process, nextTickQueue, drainMicrotasksFn, reportUncaughtExceptionFn) {
|
||||
export function initializeNextTickQueue(
|
||||
process: typeof globalThis.process,
|
||||
nextTickQueue,
|
||||
drainMicrotasksFn,
|
||||
reportUncaughtExceptionFn,
|
||||
) {
|
||||
var queue;
|
||||
var process;
|
||||
var nextTickQueue = nextTickQueue;
|
||||
|
||||
@@ -118,11 +118,7 @@ function endNT(socket, callback, err) {
|
||||
callback(err);
|
||||
}
|
||||
function emitCloseNT(self, hasError) {
|
||||
if (hasError) {
|
||||
self.emit("close", hasError);
|
||||
} else {
|
||||
self.emit("close");
|
||||
}
|
||||
self.emit("close", hasError);
|
||||
}
|
||||
function detachSocket(self) {
|
||||
if (!self) self = this;
|
||||
@@ -233,6 +229,14 @@ const SocketHandlers: SocketHandler = {
|
||||
self[kwriteCallback] = null;
|
||||
callback(error);
|
||||
}
|
||||
|
||||
if (error?.syscall === "connect") {
|
||||
const ex = new ExceptionWithHostPort(error.errno, "connect", self._host, self._port);
|
||||
self.emit("error", ex);
|
||||
if (!self.destroyed) process.nextTick(destroyNT, self, ex);
|
||||
return;
|
||||
}
|
||||
|
||||
self.emit("error", error);
|
||||
},
|
||||
open(socket) {
|
||||
@@ -409,6 +413,10 @@ const ServerHandlers: SocketHandler = {
|
||||
|
||||
self[bunSocketServerConnections]++;
|
||||
|
||||
if (pauseOnConnect) {
|
||||
_socket.pause();
|
||||
}
|
||||
|
||||
if (typeof connectionListener === "function") {
|
||||
this.pauseOnConnect = pauseOnConnect;
|
||||
if (!isTLS) {
|
||||
@@ -463,7 +471,9 @@ const ServerHandlers: SocketHandler = {
|
||||
// after secureConnection event we emmit secure and secureConnect
|
||||
self.emit("secure", self);
|
||||
self.emit("secureConnect", verifyError);
|
||||
if (!server.pauseOnConnect) {
|
||||
if (server.pauseOnConnect) {
|
||||
self.pause();
|
||||
} else {
|
||||
self.resume();
|
||||
}
|
||||
},
|
||||
@@ -517,6 +527,9 @@ const SocketHandlers2: SocketHandler<SocketHandleData> = {
|
||||
$debug("self[kupgraded]", String(self[kupgraded]));
|
||||
if (!self[kupgraded]) req!.oncomplete(0, self._handle, req, true, true);
|
||||
socket.data.req = undefined;
|
||||
if (self.pauseOnConnect) {
|
||||
self.pause();
|
||||
}
|
||||
if (self[kupgraded]) {
|
||||
self.connecting = false;
|
||||
const options = self[bunTLSConnectOptions];
|
||||
@@ -724,6 +737,8 @@ function Socket(options?) {
|
||||
this[kclosed] = false;
|
||||
this[kended] = false;
|
||||
this.connecting = false;
|
||||
this._host = undefined;
|
||||
this._port = undefined;
|
||||
this[bunTLSConnectOptions] = null;
|
||||
this.timeout = 0;
|
||||
this[kwriteCallback] = undefined;
|
||||
@@ -839,7 +854,9 @@ Object.defineProperty(Socket.prototype, "bytesWritten", {
|
||||
else bytes += Buffer.byteLength(chunk.chunk, chunk.encoding);
|
||||
}
|
||||
} else if (data) {
|
||||
bytes += data.byteLength;
|
||||
// Writes are either a string or a Buffer.
|
||||
if (typeof data !== "string") bytes += data.length;
|
||||
else bytes += Buffer.byteLength(data, this._pendingEncoding || "utf8");
|
||||
}
|
||||
return bytes;
|
||||
},
|
||||
@@ -857,7 +874,7 @@ Socket.prototype[kAttach] = function (port, socket) {
|
||||
}
|
||||
|
||||
if (this[kSetKeepAlive]) {
|
||||
socket.setKeepAlive(true, self[kSetKeepAliveInitialDelay]);
|
||||
socket.setKeepAlive(true, this[kSetKeepAliveInitialDelay]);
|
||||
}
|
||||
|
||||
if (!this[kupgraded]) {
|
||||
@@ -899,12 +916,14 @@ Socket.prototype.connect = function connect(...args) {
|
||||
}).catch(error => {
|
||||
if (!this.destroyed) {
|
||||
this.emit("error", error);
|
||||
this.emit("close");
|
||||
this.emit("close", true);
|
||||
}
|
||||
});
|
||||
}
|
||||
this.pauseOnConnect = pauseOnConnect;
|
||||
if (!pauseOnConnect) {
|
||||
if (pauseOnConnect) {
|
||||
this.pause();
|
||||
} else {
|
||||
process.nextTick(() => {
|
||||
this.resume();
|
||||
});
|
||||
@@ -1151,7 +1170,7 @@ Socket.prototype._destroy = function _destroy(err, callback) {
|
||||
callback(err);
|
||||
} else {
|
||||
callback(err);
|
||||
process.nextTick(emitCloseNT, this);
|
||||
process.nextTick(emitCloseNT, this, false);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -1546,6 +1565,7 @@ function lookupAndConnect(self, options) {
|
||||
$debug("connect: find host", host, addressType);
|
||||
$debug("connect: dns options", dnsopts);
|
||||
self._host = host;
|
||||
self._port = port;
|
||||
const lookup = options.lookup || dns.lookup;
|
||||
|
||||
if (dnsopts.family !== 4 && dnsopts.family !== 6 && !localAddress && autoSelectFamily) {
|
||||
@@ -2185,6 +2205,7 @@ Server.prototype.getConnections = function getConnections(callback) {
|
||||
};
|
||||
|
||||
Server.prototype.listen = function listen(port, hostname, onListen) {
|
||||
const argsLength = arguments.length;
|
||||
if (typeof port === "string") {
|
||||
const numPort = Number(port);
|
||||
if (!Number.isNaN(numPort)) port = numPort;
|
||||
@@ -2212,9 +2233,15 @@ Server.prototype.listen = function listen(port, hostname, onListen) {
|
||||
hostname = undefined;
|
||||
port = undefined;
|
||||
} else {
|
||||
if (typeof hostname === "function") {
|
||||
if (typeof hostname === "number") {
|
||||
backlog = hostname;
|
||||
hostname = undefined;
|
||||
} else if (typeof hostname === "function") {
|
||||
onListen = hostname;
|
||||
hostname = undefined;
|
||||
} else if (typeof hostname === "string" && typeof onListen === "number") {
|
||||
backlog = onListen;
|
||||
onListen = argsLength > 3 ? arguments[3] : undefined;
|
||||
}
|
||||
|
||||
if (typeof port === "function") {
|
||||
@@ -2231,6 +2258,7 @@ Server.prototype.listen = function listen(port, hostname, onListen) {
|
||||
ipv6Only = options.ipv6Only;
|
||||
allowHalfOpen = options.allowHalfOpen;
|
||||
reusePort = options.reusePort;
|
||||
backlog = options.backlog;
|
||||
|
||||
if (typeof options.fd === "number" && options.fd >= 0) {
|
||||
fd = options.fd;
|
||||
@@ -2424,7 +2452,7 @@ function emitErrorNextTick(self, error) {
|
||||
|
||||
function emitErrorAndCloseNextTick(self, error) {
|
||||
self.emit("error", error);
|
||||
self.emit("close");
|
||||
self.emit("close", true);
|
||||
}
|
||||
|
||||
function addServerAbortSignalOption(self, options) {
|
||||
|
||||
@@ -1114,3 +1114,20 @@ it("should handle user assigned `default` properties", async () => {
|
||||
|
||||
await promise;
|
||||
});
|
||||
|
||||
it.each(["stdin", "stdout", "stderr"])("%s stream accessor should handle exceptions without crashing", stream => {
|
||||
expect([
|
||||
/* js */ `
|
||||
const old = process;
|
||||
process = null;
|
||||
try {
|
||||
old.${stream};
|
||||
} catch {}
|
||||
if (typeof old.${stream} !== "undefined") {
|
||||
console.log("wrong");
|
||||
}
|
||||
`,
|
||||
"",
|
||||
1,
|
||||
]).toRunInlineFixture();
|
||||
});
|
||||
|
||||
30
test/js/node/test/parallel/test-http-automatic-headers.js
Normal file
30
test/js/node/test/parallel/test-http-automatic-headers.js
Normal file
@@ -0,0 +1,30 @@
|
||||
const common = require('../common');
|
||||
const assert = require('assert');
|
||||
const http = require('http');
|
||||
|
||||
const server = http.createServer(common.mustCall((req, res) => {
|
||||
res.setHeader('X-Date', 'foo');
|
||||
res.setHeader('X-Connection', 'bar');
|
||||
res.setHeader('X-Content-Length', 'baz');
|
||||
res.end();
|
||||
}));
|
||||
server.listen(0);
|
||||
|
||||
server.on('listening', common.mustCall(() => {
|
||||
const agent = new http.Agent({ port: server.address().port, maxSockets: 1 });
|
||||
http.get({
|
||||
port: server.address().port,
|
||||
path: '/hello',
|
||||
agent: agent
|
||||
}, common.mustCall((res) => {
|
||||
assert.strictEqual(res.statusCode, 200);
|
||||
assert.strictEqual(res.headers['x-date'], 'foo');
|
||||
assert.strictEqual(res.headers['x-connection'], 'bar');
|
||||
assert.strictEqual(res.headers['x-content-length'], 'baz');
|
||||
assert(res.headers.date);
|
||||
assert.strictEqual(res.headers.connection, 'keep-alive');
|
||||
assert.strictEqual(res.headers['content-length'], '0');
|
||||
server.close();
|
||||
agent.destroy();
|
||||
}));
|
||||
}));
|
||||
120
test/js/node/test/parallel/test-net-autoselectfamily-default.js
Normal file
120
test/js/node/test/parallel/test-net-autoselectfamily-default.js
Normal file
@@ -0,0 +1,120 @@
|
||||
'use strict';
|
||||
|
||||
const common = require('../common');
|
||||
const { createMockedLookup } = require('../common/dns');
|
||||
|
||||
const assert = require('assert');
|
||||
const {
|
||||
createConnection,
|
||||
createServer,
|
||||
setDefaultAutoSelectFamily,
|
||||
} = require('net');
|
||||
|
||||
const autoSelectFamilyAttemptTimeout =
|
||||
common.defaultAutoSelectFamilyAttemptTimeout;
|
||||
|
||||
// Test that IPV4 is reached by default if IPV6 is not reachable and the default is enabled
|
||||
{
|
||||
const ipv4Server = createServer((socket) => {
|
||||
socket.on(
|
||||
'data',
|
||||
common.mustCall(() => {
|
||||
socket.write('response-ipv4');
|
||||
socket.end();
|
||||
}),
|
||||
);
|
||||
});
|
||||
|
||||
ipv4Server.listen(
|
||||
0,
|
||||
'127.0.0.1',
|
||||
common.mustCall(() => {
|
||||
setDefaultAutoSelectFamily(true);
|
||||
|
||||
const connection = createConnection({
|
||||
host: 'example.org',
|
||||
port: ipv4Server.address().port,
|
||||
lookup: createMockedLookup('::1', '127.0.0.1'),
|
||||
autoSelectFamilyAttemptTimeout,
|
||||
});
|
||||
|
||||
let response = '';
|
||||
connection.setEncoding('utf-8');
|
||||
|
||||
connection.on('data', (chunk) => {
|
||||
response += chunk;
|
||||
});
|
||||
|
||||
connection.on(
|
||||
'end',
|
||||
common.mustCall(() => {
|
||||
assert.strictEqual(response, 'response-ipv4');
|
||||
ipv4Server.close();
|
||||
}),
|
||||
);
|
||||
|
||||
connection.write('request');
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
// Test that IPV4 is not reached by default if IPV6 is not reachable and the default is disabled
|
||||
{
|
||||
const ipv4Server = createServer((socket) => {
|
||||
socket.on(
|
||||
'data',
|
||||
common.mustCall(() => {
|
||||
socket.write('response-ipv4');
|
||||
socket.end();
|
||||
}),
|
||||
);
|
||||
});
|
||||
|
||||
ipv4Server.listen(
|
||||
0,
|
||||
'127.0.0.1',
|
||||
common.mustCall(() => {
|
||||
setDefaultAutoSelectFamily(false);
|
||||
|
||||
const port = ipv4Server.address().port;
|
||||
|
||||
const connection = createConnection({
|
||||
host: 'example.org',
|
||||
port,
|
||||
lookup: createMockedLookup('::1', '127.0.0.1'),
|
||||
});
|
||||
|
||||
connection.on('ready', common.mustNotCall());
|
||||
connection.on(
|
||||
'error',
|
||||
common.mustCall((error) => {
|
||||
if (common.hasIPv6) {
|
||||
assert.strictEqual(error.code, 'ECONNREFUSED');
|
||||
assert.strictEqual(
|
||||
error.message,
|
||||
`connect ECONNREFUSED ::1:${port}`,
|
||||
);
|
||||
} else if (error.code === 'EAFNOSUPPORT') {
|
||||
assert.strictEqual(
|
||||
error.message,
|
||||
`connect EAFNOSUPPORT ::1:${port} - Local (undefined:undefined)`,
|
||||
);
|
||||
} else if (error.code === 'EUNATCH') {
|
||||
assert.strictEqual(
|
||||
error.message,
|
||||
`connect EUNATCH ::1:${port} - Local (:::0)`,
|
||||
);
|
||||
} else {
|
||||
assert.strictEqual(error.code, 'EADDRNOTAVAIL');
|
||||
assert.strictEqual(
|
||||
error.message,
|
||||
`connect EADDRNOTAVAIL ::1:${port} - Local (:::0)`,
|
||||
);
|
||||
}
|
||||
|
||||
ipv4Server.close();
|
||||
}),
|
||||
);
|
||||
}),
|
||||
);
|
||||
}
|
||||
77
test/js/node/test/parallel/test-net-connect-buffer.js
Normal file
77
test/js/node/test/parallel/test-net-connect-buffer.js
Normal file
@@ -0,0 +1,77 @@
|
||||
// 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 net = require('net');
|
||||
|
||||
const tcp = net.Server(common.mustCall((s) => {
|
||||
tcp.close();
|
||||
|
||||
let buf = '';
|
||||
s.setEncoding('utf8');
|
||||
s.on('data', function(d) {
|
||||
buf += d;
|
||||
});
|
||||
|
||||
s.on('end', common.mustCall(function() {
|
||||
console.error('SERVER: end', buf);
|
||||
assert.strictEqual(buf, "L'État, c'est moi");
|
||||
s.end();
|
||||
}));
|
||||
}));
|
||||
|
||||
tcp.listen(0, common.mustCall(function() {
|
||||
const socket = net.Stream({ highWaterMark: 0 });
|
||||
|
||||
let connected = false;
|
||||
assert.strictEqual(socket.pending, true);
|
||||
socket.connect(this.address().port, common.mustCall(() => connected = true));
|
||||
|
||||
assert.strictEqual(socket.pending, true);
|
||||
assert.strictEqual(socket.connecting, true);
|
||||
assert.strictEqual(socket.readyState, 'opening');
|
||||
|
||||
// Write a string that contains a multi-byte character sequence to test that
|
||||
// `bytesWritten` is incremented with the # of bytes, not # of characters.
|
||||
const a = "L'État, c'est ";
|
||||
const b = 'moi';
|
||||
|
||||
// We're still connecting at this point so the datagram is first pushed onto
|
||||
// the connect queue. Make sure that it's not added to `bytesWritten` again
|
||||
// when the actual write happens.
|
||||
const r = socket.write(a, common.mustCall((er) => {
|
||||
console.error('write cb');
|
||||
assert.ok(connected);
|
||||
assert.strictEqual(socket.bytesWritten, Buffer.from(a + b).length);
|
||||
assert.strictEqual(socket.pending, false);
|
||||
}));
|
||||
socket.on('close', common.mustCall(() => {
|
||||
assert.strictEqual(socket.pending, true);
|
||||
}));
|
||||
|
||||
assert.strictEqual(socket.bytesWritten, Buffer.from(a).length);
|
||||
assert.strictEqual(r, false);
|
||||
socket.end(b);
|
||||
|
||||
assert.strictEqual(socket.readyState, 'opening');
|
||||
}));
|
||||
56
test/js/node/test/parallel/test-net-connect-buffer2.js
Normal file
56
test/js/node/test/parallel/test-net-connect-buffer2.js
Normal file
@@ -0,0 +1,56 @@
|
||||
'use strict';
|
||||
const common = require('../common');
|
||||
const assert = require('assert');
|
||||
const net = require('net');
|
||||
|
||||
const tcp = net.Server(common.mustCall((s) => {
|
||||
tcp.close();
|
||||
|
||||
let buf = '';
|
||||
s.setEncoding('utf8');
|
||||
s.on('data', function(d) {
|
||||
buf += d;
|
||||
});
|
||||
|
||||
s.on('end', common.mustCall(function() {
|
||||
console.error('SERVER: end', buf);
|
||||
assert.strictEqual(buf, "L'État, c'est moi");
|
||||
s.end();
|
||||
}));
|
||||
}));
|
||||
|
||||
tcp.listen(0, common.mustCall(function() {
|
||||
const socket = net.Stream({ highWaterMark: 0 });
|
||||
|
||||
let connected = false;
|
||||
assert.strictEqual(socket.pending, true);
|
||||
socket.connect(this.address().port, common.mustCall(() => connected = true));
|
||||
|
||||
assert.strictEqual(socket.pending, true);
|
||||
assert.strictEqual(socket.connecting, true);
|
||||
assert.strictEqual(socket.readyState, 'opening');
|
||||
|
||||
// Write a string that contains a multi-byte character sequence to test that
|
||||
// `bytesWritten` is incremented with the # of bytes, not # of characters.
|
||||
const a = "L'État, c'est ";
|
||||
const b = 'moi';
|
||||
|
||||
// We're still connecting at this point so the datagram is first pushed onto
|
||||
// the connect queue. Make sure that it's not added to `bytesWritten` again
|
||||
// when the actual write happens.
|
||||
const r = socket.write(a, common.mustCall((er) => {
|
||||
console.error('write cb');
|
||||
assert.ok(connected);
|
||||
assert.strictEqual(socket.bytesWritten, Buffer.from(a + b).length);
|
||||
assert.strictEqual(socket.pending, false);
|
||||
}));
|
||||
socket.on('close', common.mustCall(() => {
|
||||
assert.strictEqual(socket.pending, true);
|
||||
}));
|
||||
|
||||
assert.strictEqual(socket.bytesWritten, Buffer.from(a).length);
|
||||
assert.strictEqual(r, false);
|
||||
socket.end(b);
|
||||
|
||||
assert.strictEqual(socket.readyState, 'opening');
|
||||
}));
|
||||
88
test/js/node/test/parallel/test-net-reconnect.js
Normal file
88
test/js/node/test/parallel/test-net-reconnect.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';
|
||||
require('../common');
|
||||
const assert = require('assert');
|
||||
|
||||
const net = require('net');
|
||||
|
||||
const N = 50;
|
||||
let client_recv_count = 0;
|
||||
let client_end_count = 0;
|
||||
let disconnect_count = 0;
|
||||
|
||||
const server = net.createServer(function(socket) {
|
||||
console.error('SERVER: got socket connection');
|
||||
socket.resume();
|
||||
|
||||
console.error('SERVER connect, writing');
|
||||
socket.write('hello\r\n');
|
||||
|
||||
socket.on('end', () => {
|
||||
console.error('SERVER socket end, calling end()');
|
||||
socket.end();
|
||||
});
|
||||
|
||||
socket.on('close', (had_error) => {
|
||||
console.log(`SERVER had_error: ${JSON.stringify(had_error)}`);
|
||||
assert.strictEqual(had_error, false);
|
||||
});
|
||||
});
|
||||
|
||||
server.listen(0, function() {
|
||||
console.log('SERVER listening');
|
||||
const client = net.createConnection(this.address().port);
|
||||
|
||||
client.setEncoding('UTF8');
|
||||
|
||||
client.on('connect', () => {
|
||||
console.error('CLIENT connected', client._writableState);
|
||||
});
|
||||
|
||||
client.on('data', function(chunk) {
|
||||
client_recv_count += 1;
|
||||
console.log(`client_recv_count ${client_recv_count}`);
|
||||
assert.strictEqual(chunk, 'hello\r\n');
|
||||
console.error('CLIENT: calling end', client._writableState);
|
||||
client.end();
|
||||
});
|
||||
|
||||
client.on('end', () => {
|
||||
console.error('CLIENT end');
|
||||
client_end_count++;
|
||||
});
|
||||
|
||||
client.on('close', (had_error) => {
|
||||
console.log('CLIENT disconnect');
|
||||
assert.strictEqual(had_error, false);
|
||||
if (disconnect_count++ < N)
|
||||
client.connect(server.address().port); // reconnect
|
||||
else
|
||||
server.close();
|
||||
});
|
||||
});
|
||||
|
||||
process.on('exit', () => {
|
||||
assert.strictEqual(disconnect_count, N + 1);
|
||||
assert.strictEqual(client_recv_count, N + 1);
|
||||
assert.strictEqual(client_end_count, N + 1);
|
||||
});
|
||||
45
test/js/node/test/parallel/test-net-server-close.js
Normal file
45
test/js/node/test/parallel/test-net-server-close.js
Normal file
@@ -0,0 +1,45 @@
|
||||
// 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 net = require('net');
|
||||
|
||||
const sockets = [];
|
||||
|
||||
const server = net.createServer(function(c) {
|
||||
c.on('close', common.mustCall());
|
||||
|
||||
sockets.push(c);
|
||||
|
||||
if (sockets.length === 2) {
|
||||
assert.strictEqual(server.close(), server);
|
||||
sockets.forEach((c) => c.destroy());
|
||||
}
|
||||
});
|
||||
|
||||
server.on('close', common.mustCall());
|
||||
|
||||
assert.strictEqual(server, server.listen(0, () => {
|
||||
net.createConnection(server.address().port);
|
||||
net.createConnection(server.address().port);
|
||||
}));
|
||||
@@ -0,0 +1,49 @@
|
||||
'use strict';
|
||||
const common = require('../common');
|
||||
const assert = require('assert');
|
||||
const net = require('net');
|
||||
const msg = 'test';
|
||||
let stopped = true;
|
||||
let server1Sock;
|
||||
|
||||
const server1ConnHandler = (socket) => {
|
||||
socket.on('data', function(data) {
|
||||
if (stopped) {
|
||||
assert.fail('data event should not have happened yet');
|
||||
}
|
||||
assert.strictEqual(data.toString(), msg);
|
||||
socket.end();
|
||||
server1.close();
|
||||
});
|
||||
|
||||
server1Sock = socket;
|
||||
};
|
||||
|
||||
const server1 = net.createServer({ pauseOnConnect: true }, server1ConnHandler);
|
||||
|
||||
const server2ConnHandler = (socket) => {
|
||||
socket.on('data', function(data) {
|
||||
assert.strictEqual(data.toString(), msg);
|
||||
socket.end();
|
||||
server2.close();
|
||||
|
||||
assert.strictEqual(server1Sock.bytesRead, 0);
|
||||
server1Sock.resume();
|
||||
stopped = false;
|
||||
});
|
||||
};
|
||||
|
||||
const server2 = net.createServer({ pauseOnConnect: false }, server2ConnHandler);
|
||||
|
||||
server1.listen(0, function() {
|
||||
const clientHandler = common.mustCall(function() {
|
||||
server2.listen(0, function() {
|
||||
net.createConnection({ port: this.address().port }).write(msg);
|
||||
});
|
||||
});
|
||||
net.createConnection({ port: this.address().port }).write(msg, clientHandler);
|
||||
});
|
||||
|
||||
process.on('exit', function() {
|
||||
assert.strictEqual(stopped, false);
|
||||
});
|
||||
64
test/js/node/test/sequential/test-net-server-bind.js
Normal file
64
test/js/node/test/sequential/test-net-server-bind.js
Normal file
@@ -0,0 +1,64 @@
|
||||
'use strict';
|
||||
const common = require('../common');
|
||||
const assert = require('assert');
|
||||
const net = require('net');
|
||||
|
||||
|
||||
// With only a callback, server should get a port assigned by the OS
|
||||
{
|
||||
const server = net.createServer(common.mustNotCall());
|
||||
|
||||
server.listen(common.mustCall(function() {
|
||||
assert.ok(server.address().port > 100);
|
||||
server.close();
|
||||
}));
|
||||
}
|
||||
|
||||
// No callback to listen(), assume we can bind in 100 ms
|
||||
{
|
||||
const server = net.createServer(common.mustNotCall());
|
||||
|
||||
server.listen(common.PORT);
|
||||
|
||||
setTimeout(function() {
|
||||
const address = server.address();
|
||||
assert.strictEqual(address.port, common.PORT);
|
||||
|
||||
if (address.family === 'IPv6')
|
||||
assert.strictEqual(server._connectionKey, `6::::${address.port}`);
|
||||
else
|
||||
assert.strictEqual(server._connectionKey, `4:0.0.0.0:${address.port}`);
|
||||
|
||||
server.close();
|
||||
}, 100);
|
||||
}
|
||||
|
||||
// Callback to listen()
|
||||
{
|
||||
const server = net.createServer(common.mustNotCall());
|
||||
|
||||
server.listen(common.PORT + 1, common.mustCall(function() {
|
||||
assert.strictEqual(server.address().port, common.PORT + 1);
|
||||
server.close();
|
||||
}));
|
||||
}
|
||||
|
||||
// Backlog argument
|
||||
{
|
||||
const server = net.createServer(common.mustNotCall());
|
||||
|
||||
server.listen(common.PORT + 2, '0.0.0.0', 127, common.mustCall(function() {
|
||||
assert.strictEqual(server.address().port, common.PORT + 2);
|
||||
server.close();
|
||||
}));
|
||||
}
|
||||
|
||||
// Backlog argument without host argument
|
||||
{
|
||||
const server = net.createServer(common.mustNotCall());
|
||||
|
||||
server.listen(common.PORT + 3, 127, common.mustCall(function() {
|
||||
assert.strictEqual(server.address().port, common.PORT + 3);
|
||||
server.close();
|
||||
}));
|
||||
}
|
||||
27
test/js/node/vm/script-leak.test.ts
Normal file
27
test/js/node/vm/script-leak.test.ts
Normal file
@@ -0,0 +1,27 @@
|
||||
const vm = require("vm");
|
||||
const { describe, it, expect } = require("bun:test");
|
||||
|
||||
describe("vm.Script", () => {
|
||||
it("shouldn't leak memory", () => {
|
||||
const initialUsage = process.memoryUsage.rss();
|
||||
|
||||
{
|
||||
const source = `/*\n${Buffer.alloc(10000, " * aaaaa\n").toString("utf8")}\n*/ Buffer.alloc(10, 'hello');`;
|
||||
|
||||
function go(i) {
|
||||
const script = new vm.Script(source + "//" + i);
|
||||
script.runInThisContext();
|
||||
}
|
||||
|
||||
for (let i = 0; i < 10000; ++i) {
|
||||
go(i);
|
||||
}
|
||||
}
|
||||
|
||||
Bun.gc(true);
|
||||
|
||||
const finalUsage = process.memoryUsage.rss();
|
||||
const megabytes = Math.round(((finalUsage - initialUsage) / 1024 / 1024) * 100) / 100;
|
||||
expect(megabytes).toBeLessThan(200);
|
||||
});
|
||||
});
|
||||
30
test/js/node/vm/sourcetextmodule-leak.test.ts
Normal file
30
test/js/node/vm/sourcetextmodule-leak.test.ts
Normal file
@@ -0,0 +1,30 @@
|
||||
const vm = require("vm");
|
||||
const { describe, it, expect } = require("bun:test");
|
||||
|
||||
describe("vm.SourceTextModule", () => {
|
||||
it("shouldn't leak memory", async () => {
|
||||
const initialUsage = process.memoryUsage.rss();
|
||||
|
||||
{
|
||||
const source = `/*\n${Buffer.alloc(50_000, " * aaaaa\n").toString("utf8")}\n*/ Buffer.alloc(10, 'hello');`;
|
||||
|
||||
async function go(i) {
|
||||
const mod = new vm.SourceTextModule(source + "//" + i, {
|
||||
identifier: Buffer.alloc(64, i.toString()).toString("utf8"),
|
||||
});
|
||||
await mod.link(() => {});
|
||||
await mod.evaluate();
|
||||
}
|
||||
|
||||
for (let i = 0; i < 50_000; ++i) {
|
||||
await go(i);
|
||||
}
|
||||
}
|
||||
|
||||
Bun.gc(true);
|
||||
|
||||
const finalUsage = process.memoryUsage.rss();
|
||||
const megabytes = Math.round(((finalUsage - initialUsage) / 1024 / 1024) * 100) / 100;
|
||||
expect(megabytes).toBeLessThan(3000);
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user