mirror of
https://github.com/oven-sh/bun
synced 2026-02-26 03:27:23 +01:00
Compare commits
12 Commits
cursor/fix
...
codex/fix-
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e2e53b03c1 | ||
|
|
8ce67a22a9 | ||
|
|
576f66c149 | ||
|
|
cd0756c95c | ||
|
|
c92f3f7b72 | ||
|
|
f1226c9767 | ||
|
|
b111e6db02 | ||
|
|
ffffb634c6 | ||
|
|
d109183d3e | ||
|
|
14c9165d6f | ||
|
|
c42539b0bf | ||
|
|
022a567af0 |
@@ -1,58 +0,0 @@
|
||||
# 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.
|
||||
@@ -858,7 +858,8 @@ function getSshKeys() {
|
||||
const sshFiles = readdirSync(sshPath, { withFileTypes: true, encoding: "utf-8" });
|
||||
const publicPaths = sshFiles
|
||||
.filter(entry => entry.isFile() && entry.name.endsWith(".pub"))
|
||||
.map(({ name }) => join(sshPath, name));
|
||||
.map(({ name }) => join(sshPath, name))
|
||||
.filter(path => !readFile(path, { cache: true }).startsWith("ssh-ed25519"));
|
||||
|
||||
sshKeys.push(
|
||||
...publicPaths.map(publicPath => ({
|
||||
|
||||
@@ -21,8 +21,6 @@
|
||||
#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);
|
||||
@@ -1001,7 +999,7 @@ static void writeResponseHeader(uWS::HttpResponse<isSSL>* res, const WTF::String
|
||||
}
|
||||
|
||||
template<bool isSSL>
|
||||
static void writeFetchHeadersToUWSResponse(WebCore::FetchHeaders& headers, uWS::HttpResponse<isSSL>* res, bool* hasConnectionHeader = nullptr, bool* hasContentLengthHeader = nullptr, bool* hasDateHeader = nullptr)
|
||||
static void writeFetchHeadersToUWSResponse(WebCore::FetchHeaders& headers, uWS::HttpResponse<isSSL>* res)
|
||||
{
|
||||
auto& internalHeaders = headers.internalHeaders();
|
||||
|
||||
@@ -1023,17 +1021,6 @@ 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;
|
||||
//
|
||||
@@ -1065,17 +1052,6 @@ 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);
|
||||
}
|
||||
}
|
||||
@@ -1097,102 +1073,56 @@ 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, &hasConnectionHeader, &hasContentLengthHeader, &hasDateHeader);
|
||||
} else {
|
||||
if (headersObject->hasNonReifiedStaticProperties()) [[unlikely]] {
|
||||
headersObject->reifyAllStaticProperties(globalObject);
|
||||
RETURN_IF_EXCEPTION(scope, void());
|
||||
}
|
||||
writeFetchHeadersToUWSResponse<isSSL>(fetchHeaders->wrapped(), response);
|
||||
return;
|
||||
}
|
||||
|
||||
auto* structure = headersObject->structure();
|
||||
if (headersObject->hasNonReifiedStaticProperties()) [[unlikely]] {
|
||||
headersObject->reifyAllStaticProperties(globalObject);
|
||||
RETURN_IF_EXCEPTION(scope, void());
|
||||
}
|
||||
|
||||
if (structure->canPerformFastPropertyEnumeration()) {
|
||||
structure->forEachProperty(vm, [&](const auto& entry) {
|
||||
JSValue headerValue = headersObject->getDirect(entry.offset());
|
||||
if (!headerValue.isString()) {
|
||||
auto* structure = headersObject->structure();
|
||||
|
||||
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);
|
||||
if (structure->canPerformFastPropertyEnumeration()) {
|
||||
structure->forEachProperty(vm, [&](const auto& entry) {
|
||||
JSValue headerValue = headersObject->getDirect(entry.offset());
|
||||
if (!headerValue.isString()) {
|
||||
|
||||
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);
|
||||
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);
|
||||
}
|
||||
|
||||
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);
|
||||
RETURN_IF_EXCEPTION(scope, void());
|
||||
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());
|
||||
}
|
||||
|
||||
|
||||
@@ -24,6 +24,7 @@ const Duplex = require("internal/streams/duplex");
|
||||
const { getDefaultHighWaterMark } = require("internal/streams/state");
|
||||
const EventEmitter = require("node:events");
|
||||
let dns: typeof import("node:dns");
|
||||
const os = require("node:os");
|
||||
|
||||
const [addServerName, upgradeDuplexToTLS, isNamedPipeSocket, getBufferedAmount] = $zig(
|
||||
"socket.zig",
|
||||
@@ -87,8 +88,21 @@ function isIP(s): 0 | 4 | 6 {
|
||||
return 0;
|
||||
}
|
||||
|
||||
function isLocalAddress(addr: string): boolean {
|
||||
if (!addr) return false;
|
||||
if (addr === "0.0.0.0" || addr === "::") return true;
|
||||
const interfaces = os.networkInterfaces();
|
||||
for (const name in interfaces) {
|
||||
const list = interfaces[name];
|
||||
if (!list) continue;
|
||||
for (const info of list) {
|
||||
if (info && info.address === addr) return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
const bunTlsSymbol = Symbol.for("::buntls::");
|
||||
const bunSocketServerConnections = Symbol.for("::bunnetserverconnections::");
|
||||
const bunSocketServerOptions = Symbol.for("::bunnetserveroptions::");
|
||||
const owner_symbol = Symbol("owner_symbol");
|
||||
|
||||
@@ -230,13 +244,6 @@ const SocketHandlers: SocketHandler = {
|
||||
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) {
|
||||
@@ -346,7 +353,7 @@ const ServerHandlers: SocketHandler = {
|
||||
const data = this.data;
|
||||
if (!data) return;
|
||||
|
||||
data.server[bunSocketServerConnections]--;
|
||||
data.server._connections--;
|
||||
{
|
||||
if (!data[kclosed]) {
|
||||
data[kclosed] = true;
|
||||
@@ -392,7 +399,7 @@ const ServerHandlers: SocketHandler = {
|
||||
return;
|
||||
}
|
||||
}
|
||||
if (self.maxConnections && self[bunSocketServerConnections] >= self.maxConnections) {
|
||||
if (self.maxConnections != null && self._connections >= self.maxConnections) {
|
||||
const data = {
|
||||
localAddress: _socket.localAddress,
|
||||
localPort: _socket.localPort || this.localPort,
|
||||
@@ -411,7 +418,7 @@ const ServerHandlers: SocketHandler = {
|
||||
const bunTLS = _socket[bunTlsSymbol];
|
||||
const isTLS = typeof bunTLS === "function";
|
||||
|
||||
self[bunSocketServerConnections]++;
|
||||
self._connections++;
|
||||
|
||||
if (pauseOnConnect) {
|
||||
_socket.pause();
|
||||
@@ -1710,13 +1717,23 @@ function internalConnect(self, options, address, port, addressType, localAddress
|
||||
if (localAddress || localPort) {
|
||||
if (addressType === 4) {
|
||||
localAddress ||= DEFAULT_IPV4_ADDR;
|
||||
// TODO:
|
||||
// err = self._handle.bind(localAddress, localPort);
|
||||
if (!isLocalAddress(localAddress)) {
|
||||
const UV_EADDRNOTAVAIL = -4090;
|
||||
const ex = new ExceptionWithHostPort(UV_EADDRNOTAVAIL, "bind", localAddress, localPort);
|
||||
self.destroy(ex);
|
||||
return;
|
||||
}
|
||||
// TODO: bind socket when supported
|
||||
} else {
|
||||
// addressType === 6
|
||||
localAddress ||= DEFAULT_IPV6_ADDR;
|
||||
// TODO:
|
||||
// err = self._handle.bind6(localAddress, localPort, flags);
|
||||
if (!isLocalAddress(localAddress)) {
|
||||
const UV_EADDRNOTAVAIL = -4090;
|
||||
const ex = new ExceptionWithHostPort(UV_EADDRNOTAVAIL, "bind", localAddress, localPort);
|
||||
self.destroy(ex);
|
||||
return;
|
||||
}
|
||||
// TODO: bind6 socket when supported
|
||||
}
|
||||
$debug(
|
||||
"connect: binding to localAddress: %s and localPort: %d (addressType: %d)",
|
||||
@@ -2082,7 +2099,6 @@ function Server(options?, connectionListener?) {
|
||||
|
||||
// https://nodejs.org/api/net.html#netcreateserveroptions-connectionlistener
|
||||
const {
|
||||
maxConnections, //
|
||||
allowHalfOpen = false,
|
||||
keepAlive = false,
|
||||
keepAliveInitialDelay = 0,
|
||||
@@ -2099,7 +2115,6 @@ function Server(options?, connectionListener?) {
|
||||
this._unref = false;
|
||||
this.listeningId = 1;
|
||||
|
||||
this[bunSocketServerConnections] = 0;
|
||||
this[bunSocketServerOptions] = undefined;
|
||||
this.allowHalfOpen = allowHalfOpen;
|
||||
this.keepAlive = keepAlive;
|
||||
@@ -2107,7 +2122,6 @@ function Server(options?, connectionListener?) {
|
||||
this.highWaterMark = highWaterMark;
|
||||
this.pauseOnConnect = Boolean(pauseOnConnect);
|
||||
this.noDelay = noDelay;
|
||||
this.maxConnections = Number.isSafeInteger(maxConnections) && maxConnections > 0 ? maxConnections : 0;
|
||||
|
||||
options.connectionListener = connectionListener;
|
||||
this[bunSocketServerOptions] = options;
|
||||
@@ -2170,7 +2184,7 @@ Server.prototype[Symbol.asyncDispose] = function () {
|
||||
};
|
||||
|
||||
Server.prototype._emitCloseIfDrained = function _emitCloseIfDrained() {
|
||||
if (this._handle || this[bunSocketServerConnections] > 0) {
|
||||
if (this._handle || this._connections > 0) {
|
||||
return;
|
||||
}
|
||||
process.nextTick(() => {
|
||||
@@ -2199,7 +2213,7 @@ Server.prototype.getConnections = function getConnections(callback) {
|
||||
//in Bun case we will never error on getConnections
|
||||
//node only errors if in the middle of the couting the server got disconnected, what never happens in Bun
|
||||
//if disconnected will only pass null as well and 0 connected
|
||||
callback(null, this._handle ? this[bunSocketServerConnections] : 0);
|
||||
callback(null, this._handle ? this._connections : 0);
|
||||
}
|
||||
return this;
|
||||
};
|
||||
|
||||
@@ -7671,7 +7671,15 @@ fn NewParser_(
|
||||
ifStmtScopeIndex = try p.pushScopeForParsePass(js_ast.Scope.Kind.block, loc);
|
||||
}
|
||||
|
||||
const scopeIndex = try p.pushScopeForParsePass(js_ast.Scope.Kind.function_args, p.lexer.loc());
|
||||
var scopeIndex: usize = 0;
|
||||
var pushedScopeForFunctionArgs = false;
|
||||
// Push scope if the current lexer token is an open parenthesis token.
|
||||
// That is, the parser is about parsing function arguments
|
||||
if (p.lexer.token == .t_open_paren) {
|
||||
scopeIndex = try p.pushScopeForParsePass(js_ast.Scope.Kind.function_args, p.lexer.loc());
|
||||
pushedScopeForFunctionArgs = true;
|
||||
}
|
||||
|
||||
var func = try p.parseFn(name, FnOrArrowDataParse{
|
||||
.needs_async_loc = loc,
|
||||
.async_range = asyncRange orelse logger.Range.None,
|
||||
@@ -7687,7 +7695,7 @@ fn NewParser_(
|
||||
|
||||
if (comptime is_typescript_enabled) {
|
||||
// Don't output anything if it's just a forward declaration of a function
|
||||
if (opts.is_typescript_declare or func.flags.contains(.is_forward_declaration)) {
|
||||
if ((opts.is_typescript_declare or func.flags.contains(.is_forward_declaration)) and pushedScopeForFunctionArgs) {
|
||||
p.popAndDiscardScope(scopeIndex);
|
||||
|
||||
// Balance the fake block scope introduced above
|
||||
@@ -7703,7 +7711,9 @@ fn NewParser_(
|
||||
}
|
||||
}
|
||||
|
||||
p.popScope();
|
||||
if (pushedScopeForFunctionArgs) {
|
||||
p.popScope();
|
||||
}
|
||||
|
||||
// Only declare the function after we know if it had a body or not. Otherwise
|
||||
// TypeScript code such as this will double-declare the symbol:
|
||||
@@ -12605,14 +12615,19 @@ fn NewParser_(
|
||||
p.allow_in = true;
|
||||
|
||||
const loc = p.lexer.loc();
|
||||
_ = try p.pushScopeForParsePass(Scope.Kind.function_body, p.lexer.loc());
|
||||
defer p.popScope();
|
||||
var pushedScopeForFunctionBody = false;
|
||||
if (p.lexer.token == .t_open_brace) {
|
||||
_ = try p.pushScopeForParsePass(Scope.Kind.function_body, p.lexer.loc());
|
||||
pushedScopeForFunctionBody = true;
|
||||
}
|
||||
|
||||
try p.lexer.expect(.t_open_brace);
|
||||
var opts = ParseStatementOptions{};
|
||||
const stmts = try p.parseStmtsUpTo(.t_close_brace, &opts);
|
||||
try p.lexer.next();
|
||||
|
||||
if (pushedScopeForFunctionBody) p.popScope();
|
||||
|
||||
p.allow_in = oldAllowIn;
|
||||
p.fn_or_arrow_data_parse = oldFnOrArrowData;
|
||||
return G.FnBody{ .loc = loc, .stmts = stmts };
|
||||
|
||||
@@ -3462,6 +3462,18 @@ describe("await can only be used inside an async function message", () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe("malformed function definition does not crash due to invalid scope initialization", () => {
|
||||
it("fails with a parse error and exits cleanly", async () => {
|
||||
const tests = ["function:", "function a() {function:}"];
|
||||
for (const code of tests) {
|
||||
for (const loader of ["js", "ts"]) {
|
||||
const transpiler = new Bun.Transpiler({ loader });
|
||||
expect(() => transpiler.transformSync(code)).toThrow("Parse error");
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
it("does not crash with 9 comments and typescript type skipping", () => {
|
||||
const cmd = [bunExe(), "build", "--minify-identifiers", join(import.meta.dir, "fixtures", "9-comments.ts")];
|
||||
const { stdout, stderr, exitCode } = Bun.spawnSync({
|
||||
|
||||
@@ -1,30 +0,0 @@
|
||||
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();
|
||||
}));
|
||||
}));
|
||||
@@ -0,0 +1,32 @@
|
||||
'use strict';
|
||||
const common = require('../common');
|
||||
const assert = require('assert');
|
||||
const http = require('http');
|
||||
|
||||
const invalidLocalAddress = '1.2.3.4';
|
||||
|
||||
const server = http.createServer(function(req, res) {
|
||||
console.log(`Connect from: ${req.connection.remoteAddress}`);
|
||||
|
||||
req.on('end', function() {
|
||||
res.writeHead(200, { 'Content-Type': 'text/plain' });
|
||||
res.end(`You are from: ${req.connection.remoteAddress}`);
|
||||
});
|
||||
req.resume();
|
||||
});
|
||||
|
||||
server.listen(0, '127.0.0.1', common.mustCall(function() {
|
||||
http.request({
|
||||
host: 'localhost',
|
||||
port: this.address().port,
|
||||
path: '/',
|
||||
method: 'GET',
|
||||
localAddress: invalidLocalAddress
|
||||
}, function(res) {
|
||||
assert.fail('unexpectedly got response from server');
|
||||
}).on('error', common.mustCall(function(e) {
|
||||
console.log(`client got error: ${e.message}`);
|
||||
server.close();
|
||||
})).end();
|
||||
}));
|
||||
|
||||
@@ -4,117 +4,80 @@ const common = require('../common');
|
||||
const { createMockedLookup } = require('../common/dns');
|
||||
|
||||
const assert = require('assert');
|
||||
const {
|
||||
createConnection,
|
||||
createServer,
|
||||
setDefaultAutoSelectFamily,
|
||||
} = require('net');
|
||||
const { createConnection, createServer, setDefaultAutoSelectFamily } = require('net');
|
||||
|
||||
const autoSelectFamilyAttemptTimeout =
|
||||
common.defaultAutoSelectFamilyAttemptTimeout;
|
||||
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();
|
||||
}),
|
||||
);
|
||||
socket.on('data', common.mustCall(() => {
|
||||
socket.write('response-ipv4');
|
||||
socket.end();
|
||||
}));
|
||||
});
|
||||
|
||||
ipv4Server.listen(
|
||||
0,
|
||||
'127.0.0.1',
|
||||
common.mustCall(() => {
|
||||
setDefaultAutoSelectFamily(true);
|
||||
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,
|
||||
});
|
||||
const connection = createConnection({
|
||||
host: 'example.org',
|
||||
port: ipv4Server.address().port,
|
||||
lookup: createMockedLookup('::1', '127.0.0.1'),
|
||||
autoSelectFamilyAttemptTimeout,
|
||||
});
|
||||
|
||||
let response = '';
|
||||
connection.setEncoding('utf-8');
|
||||
let response = '';
|
||||
connection.setEncoding('utf-8');
|
||||
|
||||
connection.on('data', (chunk) => {
|
||||
response += chunk;
|
||||
});
|
||||
connection.on('data', (chunk) => {
|
||||
response += chunk;
|
||||
});
|
||||
|
||||
connection.on(
|
||||
'end',
|
||||
common.mustCall(() => {
|
||||
assert.strictEqual(response, 'response-ipv4');
|
||||
ipv4Server.close();
|
||||
}),
|
||||
);
|
||||
connection.on('end', common.mustCall(() => {
|
||||
assert.strictEqual(response, 'response-ipv4');
|
||||
ipv4Server.close();
|
||||
}));
|
||||
|
||||
connection.write('request');
|
||||
}),
|
||||
);
|
||||
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();
|
||||
}),
|
||||
);
|
||||
socket.on('data', common.mustCall(() => {
|
||||
socket.write('response-ipv4');
|
||||
socket.end();
|
||||
}));
|
||||
});
|
||||
|
||||
ipv4Server.listen(
|
||||
0,
|
||||
'127.0.0.1',
|
||||
common.mustCall(() => {
|
||||
setDefaultAutoSelectFamily(false);
|
||||
ipv4Server.listen(0, '127.0.0.1', common.mustCall(() => {
|
||||
setDefaultAutoSelectFamily(false);
|
||||
|
||||
const port = ipv4Server.address().port;
|
||||
const port = ipv4Server.address().port;
|
||||
|
||||
const connection = createConnection({
|
||||
host: 'example.org',
|
||||
port,
|
||||
lookup: createMockedLookup('::1', '127.0.0.1'),
|
||||
});
|
||||
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)`,
|
||||
);
|
||||
}
|
||||
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();
|
||||
}),
|
||||
);
|
||||
}),
|
||||
);
|
||||
ipv4Server.close();
|
||||
}));
|
||||
}));
|
||||
}
|
||||
|
||||
78
test/js/node/test/parallel/test-net-bytes-stats.js
Normal file
78
test/js/node/test/parallel/test-net-bytes-stats.js
Normal file
@@ -0,0 +1,78 @@
|
||||
// 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');
|
||||
|
||||
let bytesRead = 0;
|
||||
let bytesWritten = 0;
|
||||
let count = 0;
|
||||
|
||||
const tcp = net.Server(function(s) {
|
||||
console.log('tcp server connection');
|
||||
|
||||
// trigger old mode.
|
||||
s.resume();
|
||||
|
||||
s.on('end', function() {
|
||||
bytesRead += s.bytesRead;
|
||||
console.log(`tcp socket disconnect #${count}`);
|
||||
});
|
||||
});
|
||||
|
||||
tcp.listen(0, function doTest() {
|
||||
console.error('listening');
|
||||
const socket = net.createConnection(this.address().port);
|
||||
|
||||
socket.on('connect', function() {
|
||||
count++;
|
||||
console.error('CLIENT connect #%d', count);
|
||||
|
||||
socket.write('foo', function() {
|
||||
console.error('CLIENT: write cb');
|
||||
socket.end('bar');
|
||||
});
|
||||
});
|
||||
|
||||
socket.on('finish', function() {
|
||||
bytesWritten += socket.bytesWritten;
|
||||
console.error('CLIENT end event #%d', count);
|
||||
});
|
||||
|
||||
socket.on('close', function() {
|
||||
console.error('CLIENT close event #%d', count);
|
||||
console.log(`Bytes read: ${bytesRead}`);
|
||||
console.log(`Bytes written: ${bytesWritten}`);
|
||||
if (count < 2) {
|
||||
console.error('RECONNECTING');
|
||||
socket.connect(tcp.address().port);
|
||||
} else {
|
||||
tcp.close();
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
process.on('exit', function() {
|
||||
assert.strictEqual(bytesRead, 12);
|
||||
assert.strictEqual(bytesWritten, 12);
|
||||
});
|
||||
@@ -0,0 +1,41 @@
|
||||
'use strict';
|
||||
const common = require('../common');
|
||||
const assert = require('assert');
|
||||
const net = require('net');
|
||||
|
||||
let firstSocket;
|
||||
const dormantServer = net.createServer(common.mustNotCall());
|
||||
const server = net.createServer(common.mustCall((socket) => {
|
||||
firstSocket = socket;
|
||||
}));
|
||||
|
||||
dormantServer.maxConnections = 0;
|
||||
server.maxConnections = 1;
|
||||
|
||||
dormantServer.on('drop', common.mustCall((data) => {
|
||||
assert.strictEqual(!!data.localAddress, true);
|
||||
assert.strictEqual(!!data.localPort, true);
|
||||
assert.strictEqual(!!data.remoteAddress, true);
|
||||
assert.strictEqual(!!data.remotePort, true);
|
||||
assert.strictEqual(!!data.remoteFamily, true);
|
||||
dormantServer.close();
|
||||
}));
|
||||
|
||||
server.on('drop', common.mustCall((data) => {
|
||||
assert.strictEqual(!!data.localAddress, true);
|
||||
assert.strictEqual(!!data.localPort, true);
|
||||
assert.strictEqual(!!data.remoteAddress, true);
|
||||
assert.strictEqual(!!data.remotePort, true);
|
||||
assert.strictEqual(!!data.remoteFamily, true);
|
||||
firstSocket.destroy();
|
||||
server.close();
|
||||
}));
|
||||
|
||||
dormantServer.listen(0, () => {
|
||||
net.createConnection(dormantServer.address().port);
|
||||
});
|
||||
|
||||
server.listen(0, () => {
|
||||
net.createConnection(server.address().port);
|
||||
net.createConnection(server.address().port);
|
||||
});
|
||||
41
test/js/node/test/parallel/test-net-socket-local-address.js
Normal file
41
test/js/node/test/parallel/test-net-socket-local-address.js
Normal file
@@ -0,0 +1,41 @@
|
||||
'use strict';
|
||||
const common = require('../common');
|
||||
// Skip test in FreeBSD jails
|
||||
if (common.inFreeBSDJail)
|
||||
common.skip('In a FreeBSD jail');
|
||||
|
||||
const assert = require('assert');
|
||||
const net = require('net');
|
||||
|
||||
let conns = 0;
|
||||
const clientLocalPorts = [];
|
||||
const serverRemotePorts = [];
|
||||
const client = new net.Socket();
|
||||
const server = net.createServer((socket) => {
|
||||
serverRemotePorts.push(socket.remotePort);
|
||||
socket.end();
|
||||
});
|
||||
|
||||
server.on('close', common.mustCall(() => {
|
||||
// Client and server should agree on the ports used
|
||||
assert.deepStrictEqual(serverRemotePorts, clientLocalPorts);
|
||||
assert.strictEqual(conns, 2);
|
||||
}));
|
||||
|
||||
server.listen(0, common.localhostIPv4, connect);
|
||||
|
||||
function connect() {
|
||||
if (conns === 2) {
|
||||
server.close();
|
||||
return;
|
||||
}
|
||||
|
||||
conns++;
|
||||
client.once('close', connect);
|
||||
assert.strictEqual(
|
||||
client,
|
||||
client.connect(server.address().port, common.localhostIPv4, () => {
|
||||
clientLocalPorts.push(client.localPort);
|
||||
})
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
'use strict';
|
||||
const common = require('../common');
|
||||
const net = require('net');
|
||||
const assert = require('assert');
|
||||
|
||||
const c = net.createConnection(common.PORT);
|
||||
|
||||
c.on('connect', common.mustNotCall());
|
||||
|
||||
c.on('error', common.mustCall(function(error) {
|
||||
// Family autoselection might be skipped if only a single address is returned by DNS.
|
||||
const failedAttempt = Array.isArray(error.errors) ? error.errors[0] : error;
|
||||
|
||||
assert.strictEqual(failedAttempt.code, 'ECONNREFUSED');
|
||||
assert.strictEqual(failedAttempt.port, common.PORT);
|
||||
assert.match(failedAttempt.address, /^(127\.0\.0\.1|::1)$/);
|
||||
}));
|
||||
@@ -0,0 +1,32 @@
|
||||
// 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 net = require('net');
|
||||
const assert = require('assert');
|
||||
|
||||
const c = net.createConnection(common.PORT);
|
||||
c.on('connect', common.mustNotCall());
|
||||
c.on('error', common.mustCall((e) => {
|
||||
assert.strictEqual(c.connecting, false);
|
||||
assert.strictEqual(e.code, 'ECONNREFUSED');
|
||||
}));
|
||||
Reference in New Issue
Block a user