Compare commits

...

15 Commits

Author SHA1 Message Date
Jarred Sumner
00e017c274 stash this 2024-10-09 02:33:53 -07:00
Jarred Sumner
8212e3afc6 Merge branch 'main' into jarred/uws 2024-10-08 23:53:26 -07:00
Jarred Sumner
da3e7160a1 Test 2024-10-07 22:48:55 -07:00
Jarred Sumner
2de2cb607b wip 2024-10-07 22:48:29 -07:00
Jarred Sumner
3852109ea0 Update server.zig 2024-10-07 21:07:23 -07:00
Jarred Sumner
8ca6dba524 assign 2024-10-07 21:02:54 -07:00
Jarred Sumner
d9a1dc78f9 Fix some of this 2024-10-07 20:22:42 -07:00
Jarred Sumner
1f93522b88 Automatically stop reading body 2024-10-07 18:46:27 -07:00
Jarred Sumner
4a53dee5c1 Update http.ts 2024-10-07 07:23:22 -07:00
Jarred Sumner
44f1870b17 tweaks 2024-10-07 07:13:37 -07:00
Jarred Sumner
7b295c0d18 more 2024-10-07 06:52:22 -07:00
Jarred Sumner
432e92fd27 Update dev-server.test.ts 2024-10-06 22:21:47 -07:00
Jarred Sumner
68ad269ef0 more 2024-10-06 21:34:49 -07:00
Jarred Sumner
deb44aa80b okay fastify and express seem to work again 2024-10-06 07:11:18 -07:00
Jarred Sumner
a660692743 Rewrite node:http 2024-10-06 06:12:23 -07:00
38 changed files with 3063 additions and 607 deletions

View File

@@ -12,6 +12,7 @@
"eventemitter3": "^5.0.0",
"execa": "^8.0.1",
"fast-glob": "3.3.1",
"fastify": "^5.0.0",
"fdir": "^6.1.0",
"mitata": "^0.1.6",
"string-width": "7.1.0",

View File

@@ -0,0 +1,13 @@
import express from "express";
const app = express();
const port = 3000;
var i = 0;
app.get("/", (req, res) => {
res.send("Hello World!" + i++);
});
app.listen(port, () => {
console.log(`Express app listening at http://localhost:${port}`);
});

View File

@@ -0,0 +1,20 @@
import Fastify from "fastify";
const fastify = Fastify({
logger: false,
});
fastify.get("/", async (request, reply) => {
return { hello: "world" };
});
const start = async () => {
try {
await fastify.listen({ port: 3000 });
} catch (err) {
fastify.log.error(err);
process.exit(1);
}
};
start();

View File

@@ -127,8 +127,9 @@ private:
/* Signal broken HTTP request only if we have a pending request */
if (httpResponseData->onAborted) {
httpResponseData->onAborted((HttpResponse<SSL> *)s, httpResponseData->userData);
} else if (httpResponseData->socketData && httpContextData->onSocketClosed) {
httpContextData->onSocketClosed(httpResponseData->socketData, SSL, s);
}
/* Destruct socket ext */
httpResponseData->~HttpResponseData<SSL>();

View File

@@ -27,6 +27,7 @@ namespace uWS {
template<bool> struct HttpResponse;
struct HttpRequest;
template <bool SSL>
struct alignas(16) HttpContextData {
template <bool> friend struct HttpContext;
@@ -34,6 +35,7 @@ struct alignas(16) HttpContextData {
template <bool> friend struct TemplatedApp;
private:
std::vector<MoveOnlyFunction<void(HttpResponse<SSL> *, int)>> filterHandlers;
using OnSocketClosedCallback = void (*)(void* userData, int is_ssl, struct us_socket_t *rawSocket);
MoveOnlyFunction<void(const char *hostname)> missingServerNameHandler;
@@ -51,6 +53,9 @@ private:
bool isParsingHttp = false;
bool rejectUnauthorized = false;
/* Used to simulate Node.js socket events. */
OnSocketClosedCallback onSocketClosed = nullptr;
// TODO: SNI
void clearRoutes() {
this->router = HttpRouter<RouterData>{};

View File

@@ -81,8 +81,12 @@ public:
/* Called only once per request */
void writeMark() {
if (getHttpResponseData()->state & HttpResponseData<SSL>::HTTP_WROTE_DATE_HEADER) {
return;
}
/* Date is always written */
writeHeader("Date", std::string_view(((LoopData *) us_loop_ext(us_socket_context_loop(SSL, (us_socket_context(SSL, (us_socket_t *) this)))))->date, 29));
getHttpResponseData()->state |= HttpResponseData<SSL>::HTTP_WROTE_DATE_HEADER;
}
/* Returns true on success, indicating that it might be feasible to write more data.
@@ -113,7 +117,8 @@ public:
httpResponseData->state |= HttpResponseData<SSL>::HTTP_CONNECTION_CLOSE;
}
if (httpResponseData->state & HttpResponseData<SSL>::HTTP_WRITE_CALLED) {
/* 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)) {
/* We do not have tryWrite-like functionalities, so ignore optional in this path */
@@ -152,7 +157,7 @@ public:
return true;
} else {
/* Write content-length on first call */
if (!(httpResponseData->state & HttpResponseData<SSL>::HTTP_END_CALLED)) {
if (!(httpResponseData->state & (HttpResponseData<SSL>::HTTP_END_CALLED))) {
/* Write mark, this propagates to WebSockets too */
writeMark();
@@ -162,7 +167,8 @@ public:
Super::write("Content-Length: ", 16);
writeUnsigned64(totalSize);
Super::write("\r\n\r\n", 4);
} else {
httpResponseData->state |= HttpResponseData<SSL>::HTTP_WROTE_CONTENT_LENGTH_HEADER;
} else if (!(httpResponseData->state & (HttpResponseData<SSL>::HTTP_WRITE_CALLED))) {
Super::write("\r\n", 2);
}
@@ -427,7 +433,7 @@ public:
/* End the response with an optional data chunk. Always starts a timeout. */
void end(std::string_view data = {}, bool closeConnection = false) {
internalEnd(data, data.length(), false, true, closeConnection);
internalEnd(data, data.length(), false, !(this->getHttpResponseData()->state & HttpResponseData<SSL>::HTTP_WROTE_CONTENT_LENGTH_HEADER), closeConnection);
}
/* Try and end the response. Returns [true, true] on success.
@@ -440,7 +446,7 @@ public:
bool sendTerminatingChunk(bool closeConnection = false) {
writeStatus(HTTP_200_OK);
HttpResponseData<SSL> *httpResponseData = getHttpResponseData();
if (!(httpResponseData->state & HttpResponseData<SSL>::HTTP_WRITE_CALLED)) {
if (!(httpResponseData->state & (HttpResponseData<SSL>::HTTP_WRITE_CALLED | HttpResponseData<SSL>::HTTP_WROTE_CONTENT_LENGTH_HEADER))) {
/* Write mark on first call to write */
writeMark();
@@ -455,33 +461,46 @@ public:
}
/* Write parts of the response in chunking fashion. Starts timeout if failed. */
bool write(std::string_view data) {
bool write(std::string_view data, size_t *writtenPtr = nullptr) {
writeStatus(HTTP_200_OK);
/* Do not allow sending 0 chunks, they mark end of response */
if (!data.length()) {
if (writtenPtr) {
*writtenPtr = 0;
}
/* If you called us, then according to you it was fine to call us so it's fine to still call us */
return true;
}
HttpResponseData<SSL> *httpResponseData = getHttpResponseData();
if (!(httpResponseData->state & HttpResponseData<SSL>::HTTP_WRITE_CALLED)) {
/* Write mark on first call to write */
writeMark();
if (!(httpResponseData->state & HttpResponseData<SSL>::HTTP_WROTE_CONTENT_LENGTH_HEADER)) {
if (!(httpResponseData->state & HttpResponseData<SSL>::HTTP_WRITE_CALLED)) {
/* Write mark on first call to write */
writeMark();
writeHeader("Transfer-Encoding", "chunked");
writeHeader("Transfer-Encoding", "chunked");
httpResponseData->state |= HttpResponseData<SSL>::HTTP_WRITE_CALLED;
}
Super::write("\r\n", 2);
writeUnsignedHex((unsigned int) data.length());
Super::write("\r\n", 2);
} else if (!(httpResponseData->state & HttpResponseData<SSL>::HTTP_WRITE_CALLED)) {
writeMark();
Super::write("\r\n", 2);
httpResponseData->state |= HttpResponseData<SSL>::HTTP_WRITE_CALLED;
}
Super::write("\r\n", 2);
writeUnsignedHex((unsigned int) data.length());
Super::write("\r\n", 2);
auto [written, failed] = Super::write(data.data(), (int) data.length());
/* Reset timeout on each sended chunk */
this->resetTimeout();
if (writtenPtr) {
*writtenPtr = written;
}
/* If we did not fail the write, accept more */
return !failed;
}
@@ -628,6 +647,17 @@ public:
data->received_bytes_per_timeout = 0;
}
void* getSocketData() {
HttpResponseData<SSL> *httpResponseData = getHttpResponseData();
return httpResponseData->socketData;
}
void setSocketData(void* socketData) {
HttpResponseData<SSL> *httpResponseData = getHttpResponseData();
httpResponseData->socketData = socketData;
}
void setWriteOffset(uint64_t offset) {
HttpResponseData<SSL> *httpResponseData = getHttpResponseData();

View File

@@ -78,11 +78,14 @@ struct HttpResponseData : AsyncSocketData<SSL>, HttpParser {
HTTP_WRITE_CALLED = 2, // used
HTTP_END_CALLED = 4, // used
HTTP_RESPONSE_PENDING = 8, // used
HTTP_CONNECTION_CLOSE = 16 // used
HTTP_CONNECTION_CLOSE = 16, // used
HTTP_WROTE_CONTENT_LENGTH_HEADER = 32, // used
HTTP_WROTE_DATE_HEADER = 64, // used
};
/* Shared context pointer */
void* userData = nullptr;
void* socketData = nullptr;
/* Per socket event handlers */
OnWritableCallback onWritable = nullptr;
@@ -107,3 +110,5 @@ struct HttpResponseData : AsyncSocketData<SSL>, HttpParser {
}
#endif // UWS_HTTPRESPONSEDATA_H
static_assert(sizeof(uWS::HttpResponseData<true>) == 128, "HttpResponseData size is incorrect");

View File

@@ -90,6 +90,73 @@ export default [
generate(`HTTPSServer`),
generate(`DebugHTTPSServer`),
define({
name: "NodeHTTPResponse",
JSType: "0b11101110",
proto: {
writeHead: {
fn: "writeHead",
length: 3,
},
write: {
fn: "write",
length: 2,
},
end: {
fn: "end",
length: 2,
},
cork: {
fn: "cork",
length: 1,
},
ref: {
fn: "jsRef",
},
unref: {
fn: "jsUnref",
},
abort: {
fn: "abort",
length: 0,
},
bufferedAmount: {
getter: "getBufferedAmount",
},
aborted: {
getter: "getAborted",
},
finished: {
getter: "getFinished",
},
hasBody: {
getter: "getHasBody",
},
ended: {
getter: "getEnded",
},
ondata: {
getter: "getOnData",
setter: "setOnData",
},
onabort: {
getter: "getOnAbort",
setter: "setOnAbort",
},
// ontimeout: {
// getter: "getOnTimeout",
// setter: "setOnTimeout",
// },
onwritable: {
getter: "getOnWritable",
setter: "setOnWritable",
},
},
klass: {},
finalize: true,
noConstructor: true,
}),
define({
name: "ServerWebSocket",
JSType: "0b11101110",

File diff suppressed because it is too large Load Diff

View File

@@ -30,10 +30,10 @@ export default [
["ERR_SERVER_NOT_RUNNING", Error, "Error"],
["ERR_SOCKET_BAD_TYPE", TypeError, "TypeError"],
["ERR_STREAM_ALREADY_FINISHED", TypeError, "TypeError"],
["ERR_STREAM_CANNOT_PIPE", TypeError, "TypeError"],
["ERR_STREAM_DESTROYED", TypeError, "TypeError"],
["ERR_STREAM_CANNOT_PIPE", Error, "Error"],
["ERR_STREAM_DESTROYED", Error, "Error"],
["ERR_STREAM_NULL_VALUES", TypeError, "TypeError"],
["ERR_STREAM_WRITE_AFTER_END", TypeError, "TypeError"],
["ERR_STREAM_WRITE_AFTER_END", Error, "Error"],
["ERR_ZLIB_INITIALIZATION_FAILED", Error, "Error"],
["ERR_STRING_TOO_LONG", Error, "Error"],
["ERR_CRYPTO_SCRYPT_INVALID_PARAMETER", Error, "Error"],
@@ -48,7 +48,12 @@ export default [
["ERR_BUFFER_OUT_OF_BOUNDS", RangeError, "RangeError"],
["ERR_UNKNOWN_SIGNAL", TypeError, "TypeError"],
["ERR_SOCKET_BAD_PORT", RangeError, "RangeError"],
["ERR_HTTP_HEADERS_SENT", Error, "Error"],
["ERR_HTTP_BODY_NOT_ALLOWED", Error, "Error"],
["ERR_HTTP_INVALID_STATUS_CODE", RangeError, "RangeError"],
["ERR_HTTP_INVALID_HEADER_VALUE", TypeError, "TypeError"],
["ERR_INVALID_CHAR", TypeError, "TypeError"],
["ERR_METHOD_NOT_IMPLEMENTED", Error, "Error"],
// Bun-specific
["ERR_FORMDATA_PARSE_ERROR", TypeError, "TypeError"],
["ERR_BODY_ALREADY_USED", Error, "Error"],

View File

@@ -21,9 +21,147 @@ namespace Bun {
using namespace JSC;
using namespace WebCore;
// Create a static hash table of values containing an onclose DOMAttributeGetterSetter and a close function
static const struct HashTableValue JSNodeHTTPServerSocketPrototypeTableValues[] = {
{ "onclose"_s, static_cast<unsigned>(PropertyAttribute::CustomAccessor), NoIntrinsic, { HashTableValue::GetterSetterType, getterOnClose, setterOnClose } },
{ "close"_s, static_cast<unsigned>(PropertyAttribute::Function | PropertyAttribute::DontEnum), NoIntrinsic, { HashTableValue::NativeFunctionType, functionNodeHTTPServerSocket_close, 0 } },
};
class JSNodeHTTPServerSocketPrototype final : public JSC::JSNonFinalObject {
public:
using Base = JSC::JSNonFinalObject;
static JSNodeHTTPServerSocketPrototype* create(VM& vm, JSGlobalObject* globalObject, Structure* structure)
{
JSNodeHTTPServerSocketPrototype* prototype = new (NotNull, allocateCell<JSNodeHTTPServerSocketPrototype>(vm)) JSNodeHTTPServerSocketPrototype(vm, structure);
prototype->finishCreation(vm, globalObject);
return prototype;
}
DECLARE_INFO;
static constexpr bool needsDestruction = false;
static constexpr unsigned StructureFlags = Base::StructureFlags | HasStaticPropertyTable;
template<typename CellType, JSC::SubspaceAccess>
static JSC::GCClient::IsoSubspace* subspaceFor(JSC::VM& vm)
{
STATIC_ASSERT_ISO_SUBSPACE_SHARABLE(JSNodeHTTPServerSocketPrototype, Base);
return &vm.plainObjectSpace();
}
private:
JSNodeHTTPServerSocketPrototype(VM& vm, Structure* structure)
: Base(vm, structure)
{
}
void finishCreation(VM& vm, JSGlobalObject* globalObject)
{
Base::finishCreation(vm);
ASSERT(inherits(info()));
}
};
class JSNodeHTTPServerSocket : public JSC::JSDestructibleObject {
using Base = JSC::JSDestructibleObject;
public:
static JSNodeHTTPServerSocket* create(JSC::VM& vm, JSC::Structure* structure, us_socket_t* socket, bool is_ssl)
{
return new (JSC::allocateCell<JSNodeHTTPServerSocket>(vm)) JSNodeHTTPServerSocket(vm, structure, socket, is_ssl);
}
static JSNodeHTTPServerSocket* create(JSC::VM& vm, Zig::GlobalObject* globalObject, us_socket_t* socket, bool is_ssl)
{
auto* structure = globalObject->m_JSNodeHTTPServerSocketStructure.getInitializedOnMainThread(globalObject);
return create(vm, structure, socket, is_ssl);
}
static void destroy(JSC::JSCell* cell)
{
static_cast<JSNodeHTTPServerSocket*>(cell)->JSNodeHTTPServerSocket::~JSNodeHTTPServerSocket();
}
template<bool SSL>
static void clearSocketData(us_socket_t* socket)
{
auto* httpResponseData = (uWS::HttpResponseData<SSL>*)us_socket_ext(SSL, socket);
if (httpResponseData->socketData) {
httpResponseData->socketData = nullptr;
}
}
~JSNodeHTTPServerSocket()
{
if (socket) {
if (is_ssl) {
clearSocketData<true>(socket);
} else {
clearSocketData<false>(socket);
}
}
}
JSNodeHTTPServerSocket(JSC::VM& vm, JSC::Structure* structure, us_socket_t* socket, bool is_ssl)
: JSC::JSDestructibleObject(vm, structure)
, socket(socket)
, is_ssl(is_ssl)
{
}
mutable WriteBarrier<JSObject> functionToCallOnClose;
mutable WriteBarrier<WebCore::JSNodeHTTPResponse> currentResponseObject;
unsigned is_ssl : 1;
us_socket_t* socket;
DECLARE_INFO;
DECLARE_VISIT_CHILDREN;
template<typename, JSC::SubspaceAccess mode> static JSC::GCClient::IsoSubspace* subspaceFor(JSC::VM& vm)
{
if constexpr (mode == JSC::SubspaceAccess::Concurrently)
return nullptr;
return WebCore::subspaceForImpl<JSNodeHTTPServerSocket, UseCustomHeapCellType::No>(
vm,
[](auto& spaces) { return spaces.m_clientSubspaceForJSNodeHTTPServerSocket.get(); },
[](auto& spaces, auto&& space) { spaces.m_clientSubspaceForJSNodeHTTPServerSocket = std::forward<decltype(space)>(space); },
[](auto& spaces) { return spaces.m_subspaceForJSNodeHTTPServerSocket.get(); },
[](auto& spaces, auto&& space) { spaces.m_subspaceForJSNodeHTTPServerSocket = std::forward<decltype(space)>(space); });
}
void onClose()
{
this->socket = nullptr;
}
static JSC_HOST_CALL_ATTRIBUTES JSC::EncodedJSValue onClose(JSC::JSGlobalObject* globalObject, JSC::CallFrame* callFrame)
{
auto* thisObject = jsDynamicCast<JSNodeHTTPServerSocket*>(callFrame->thisValue());
if (!thisObject) {
return JSValue::encode(JSC::jsUndefined());
}
thisObject->onClose();
return JSValue::encode(JSC::jsUndefined());
}
static Structure* createStructure(JSC::VM& vm, JSC::JSGlobalObject* globalObject)
{
JSC::JSObject* prototype = JSC::constructEmptyObject(vm, globalObject->nullPrototypeObjectStructure());
prototype->structure()->setMayBePrototype(true);
return JSC::Structure::create(vm, globalObject, JSC::TypeInfo(JSC::ObjectType, StructureFlags), info());
}
};
BUN_DECLARE_HOST_FUNCTION(jsFunctionRequestOrResponseHasBodyValue);
BUN_DECLARE_HOST_FUNCTION(jsFunctionGetCompleteRequestOrResponseBodyValueAsArrayBuffer);
extern "C" uWS::HttpRequest* Request__getUWSRequest(void*);
extern "C" void Request__setInternalEventCallback(void*, EncodedJSValue, JSC::JSGlobalObject*);
extern "C" void Request__setTimeout(void*, EncodedJSValue, JSC::JSGlobalObject*);
extern "C" void NodeHTTPResponse__setTimeout(void*, EncodedJSValue, JSC::JSGlobalObject*);
extern "C" void Server__setIdleTimeout(EncodedJSValue, EncodedJSValue, JSC::JSGlobalObject*);
static EncodedJSValue assignHeadersFromFetchHeaders(FetchHeaders& impl, JSObject* prototype, JSObject* objectValue, JSC::InternalFieldTuple* tuple, JSC::JSGlobalObject* globalObject, JSC::VM& vm)
{
@@ -94,6 +232,164 @@ static EncodedJSValue assignHeadersFromFetchHeaders(FetchHeaders& impl, JSObject
return JSValue::encode(tuple);
}
static void assignHeadersFromUWebSocketsForCall(uWS::HttpRequest* request, MarkedArgumentBuffer& args, JSC::JSGlobalObject* globalObject, JSC::VM& vm)
{
auto scope = DECLARE_THROW_SCOPE(vm);
std::string_view fullURLStdStr = request->getFullUrl();
String fullURL = String::fromUTF8ReplacingInvalidSequences({ reinterpret_cast<const LChar*>(fullURLStdStr.data()), fullURLStdStr.length() });
// Get the URL.
{
args.append(jsString(vm, fullURL));
}
// Get the method.
{
std::string_view methodView = request->getMethod();
WTF::String methodString;
switch (methodView.length()) {
case 3: {
if (methodView == std::string_view("get", 3)) {
methodString = "GET"_s;
break;
}
if (methodView == std::string_view("put", 3)) {
methodString = "PUT"_s;
break;
}
break;
}
case 4: {
if (methodView == std::string_view("post", 4)) {
methodString = "POST"_s;
break;
}
if (methodView == std::string_view("head", 4)) {
methodString = "HEAD"_s;
break;
}
if (methodView == std::string_view("copy", 4)) {
methodString = "COPY"_s;
break;
}
}
case 5: {
if (methodView == std::string_view("patch", 5)) {
methodString = "PATCH"_s;
break;
}
if (methodView == std::string_view("merge", 5)) {
methodString = "MERGE"_s;
break;
}
if (methodView == std::string_view("trace", 5)) {
methodString = "TRACE"_s;
break;
}
if (methodView == std::string_view("fetch", 5)) {
methodString = "FETCH"_s;
break;
}
if (methodView == std::string_view("purge", 5)) {
methodString = "PURGE"_s;
break;
}
break;
}
case 6: {
if (methodView == std::string_view("delete", 6)) {
methodString = "DELETE"_s;
break;
}
break;
}
case 7: {
if (methodView == std::string_view("connect", 7)) {
methodString = "CONNECT"_s;
break;
}
if (methodView == std::string_view("options", 7)) {
methodString = "OPTIONS"_s;
break;
}
break;
}
}
if (methodString.isNull()) {
methodString = String::fromUTF8ReplacingInvalidSequences({ reinterpret_cast<const LChar*>(methodView.data()), methodView.length() });
}
args.append(jsString(vm, methodString));
}
size_t size = 0;
for (auto it = request->begin(); it != request->end(); ++it) {
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* array = constructEmptyArray(globalObject, nullptr, size * 2);
JSC::JSArray* setCookiesHeaderArray = nullptr;
JSC::JSString* setCookiesHeaderString = nullptr;
args.append(headersObject);
args.append(array);
unsigned i = 0;
for (auto it = request->begin(); it != request->end(); ++it) {
auto pair = *it;
StringView nameView = StringView(std::span { reinterpret_cast<const LChar*>(pair.first.data()), pair.first.length() });
LChar* data = nullptr;
auto value = String::createUninitialized(pair.second.length(), data);
if (pair.second.length() > 0)
memcpy(data, pair.second.data(), pair.second.length());
HTTPHeaderName name;
WTF::String nameString;
WTF::String lowercasedNameString;
if (WebCore::findHTTPHeaderName(nameView, name)) {
nameString = WTF::httpHeaderNameStringImpl(name);
lowercasedNameString = nameString;
} else {
nameString = nameView.toString();
lowercasedNameString = nameString.convertToASCIILowercase();
}
JSString* jsValue = jsString(vm, value);
if (name == WebCore::HTTPHeaderName::SetCookie) {
if (!setCookiesHeaderArray) {
setCookiesHeaderArray = constructEmptyArray(globalObject, nullptr);
setCookiesHeaderString = jsString(vm, nameString);
headersObject->putDirect(vm, Identifier::fromString(vm, lowercasedNameString), setCookiesHeaderArray, 0);
RETURN_IF_EXCEPTION(scope, void());
}
array->putDirectIndex(globalObject, i++, setCookiesHeaderString);
array->putDirectIndex(globalObject, i++, jsValue);
setCookiesHeaderArray->push(globalObject, jsValue);
RETURN_IF_EXCEPTION(scope, void());
} else {
headersObject->putDirect(vm, Identifier::fromString(vm, lowercasedNameString), jsValue, 0);
array->putDirectIndex(globalObject, i++, jsString(vm, nameString));
array->putDirectIndex(globalObject, i++, jsValue);
RETURN_IF_EXCEPTION(scope, void());
}
}
}
// This is an 8% speedup.
static EncodedJSValue assignHeadersFromUWebSockets(uWS::HttpRequest* request, JSObject* prototype, JSObject* objectValue, JSC::InternalFieldTuple* tuple, JSC::JSGlobalObject* globalObject, JSC::VM& vm)
{
@@ -257,6 +553,249 @@ static EncodedJSValue assignHeadersFromUWebSockets(uWS::HttpRequest* request, JS
return JSValue::encode(tuple);
}
extern "C" EncodedJSValue NodeHTTPResponse__createForJS(size_t any_server, JSC::JSGlobalObject* globalObject, int* hasBody, uWS::HttpRequest* request, int isSSL, void* response_ptr, void** nodeHttpResponsePtr);
template<bool isSSL>
static EncodedJSValue NodeHTTPServer__onRequest(
size_t any_server,
JSC::JSGlobalObject* globalObject,
JSValue thisValue,
JSValue callback,
uWS::HttpRequest* request,
uWS::HttpResponse<isSSL>* response,
void** nodeHttpResponsePtr)
{
auto& vm = globalObject->vm();
auto scope = DECLARE_THROW_SCOPE(vm);
JSObject* callbackObject = jsCast<JSObject*>(callback);
MarkedArgumentBuffer args;
args.append(thisValue);
assignHeadersFromUWebSocketsForCall(request, args, globalObject, vm);
if (scope.exception()) {
auto* exception = scope.exception();
response->endWithoutBody();
scope.clearException();
return JSValue::encode(exception);
}
int hasBody = 0;
EncodedJSValue nodehttpobjectValue = NodeHTTPResponse__createForJS(any_server, globalObject, &hasBody, request, isSSL, response, nodeHttpResponsePtr);
JSC::CallData callData = getCallData(callbackObject);
args.append(JSValue::decode(nodehttpobjectValue));
args.append(jsBoolean(hasBody));
WTF::NakedPtr<JSC::Exception> exception;
JSValue returnValue = JSC::profiledCall(globalObject, JSC::ProfilingReason::API, callbackObject, callData, jsUndefined(), args, exception);
if (exception) {
auto* ptr = exception.get();
exception.clear();
return JSValue::encode(ptr);
}
return JSValue::encode(returnValue);
}
template<bool isSSL>
static void writeResponseHeader(uWS::HttpResponse<isSSL>* res, const WTF::StringView& name, const WTF::StringView& value)
{
WTF::CString nameStr;
WTF::CString valueStr;
std::string_view nameView;
std::string_view valueView;
if (name.is8Bit()) {
const auto nameSpan = name.span8();
ASSERT(name.containsOnlyASCII());
nameView = std::string_view(reinterpret_cast<const char*>(nameSpan.data()), nameSpan.size());
} else {
nameStr = name.utf8();
nameView = std::string_view(nameStr.data(), nameStr.length());
}
if (value.is8Bit()) {
const auto valueSpan = value.span8();
valueView = std::string_view(reinterpret_cast<const char*>(valueSpan.data()), valueSpan.size());
} else {
valueStr = value.utf8();
valueView = std::string_view(valueStr.data(), valueStr.length());
}
res->writeHeader(nameView, valueView);
}
template<bool isSSL>
static void writeFetchHeadersToUWSResponse(WebCore::FetchHeaders& headers, uWS::HttpResponse<isSSL>* res)
{
auto& internalHeaders = headers.internalHeaders();
for (auto& value : internalHeaders.getSetCookieHeaders()) {
if (value.is8Bit()) {
const auto valueSpan = value.span8();
res->writeHeader(std::string_view("set-cookie", 10), std::string_view(reinterpret_cast<const char*>(valueSpan.data()), valueSpan.size()));
} else {
WTF::CString valueStr = value.utf8();
res->writeHeader(std::string_view("set-cookie", 10), std::string_view(valueStr.data(), valueStr.length()));
}
}
auto* data = res->getHttpResponseData();
for (const auto& header : internalHeaders.commonHeaders()) {
const auto& name = WebCore::httpHeaderNameString(header.key);
const auto& value = header.value;
// We have to tell uWS not to automatically insert a TransferEncoding or Date header.
// Otherwise, you get this when using Fastify;
//
// curl http://localhost:3000 --verbose
// * Trying [::1]:3000...
// * Connected to localhost (::1) port 3000
// > GET / HTTP/1.1
// > Host: localhost:3000
// > User-Agent: curl/8.4.0
// > Accept: */*
// >
// < HTTP/1.1 200 OK
// < Content-Type: application/json; charset=utf-8
// < Content-Length: 17
// < Date: Sun, 06 Oct 2024 13:37:01 GMT
// < Transfer-Encoding: chunked
// <
//
if (header.key == WebCore::HTTPHeaderName::ContentLength) {
if (!(data->state & uWS::HttpResponseData<isSSL>::HTTP_WROTE_CONTENT_LENGTH_HEADER)) {
data->state |= uWS::HttpResponseData<isSSL>::HTTP_WROTE_CONTENT_LENGTH_HEADER;
res->writeMark();
}
}
writeResponseHeader<isSSL>(res, name, value);
}
for (auto& header : internalHeaders.uncommonHeaders()) {
const auto& name = header.key;
const auto& value = header.value;
writeResponseHeader<isSSL>(res, name, value);
}
}
template<bool isSSL>
static void NodeHTTPServer__writeHead(
JSC::JSGlobalObject* globalObject,
const char* statusMessage,
size_t statusMessageLength,
JSValue headersObjectValue,
uWS::HttpResponse<isSSL>* response)
{
auto& vm = globalObject->vm();
auto scope = DECLARE_THROW_SCOPE(vm);
JSObject* headersObject = headersObjectValue.getObject();
response->writeStatus(std::string_view(statusMessage, statusMessageLength));
if (headersObject) {
if (auto* fetchHeaders = jsDynamicCast<WebCore::JSFetchHeaders*>(headersObject)) {
writeFetchHeadersToUWSResponse<isSSL>(fetchHeaders->wrapped(), response);
return;
}
if (UNLIKELY(headersObject->hasNonReifiedStaticProperties())) {
headersObject->reifyAllStaticProperties(globalObject);
RETURN_IF_EXCEPTION(scope, void());
}
auto* structure = headersObject->structure();
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;
}
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);
}
}
}
RELEASE_AND_RETURN(scope, void());
}
extern "C" void NodeHTTPServer__writeHead_http(
JSC::JSGlobalObject* globalObject,
const char* statusMessage,
size_t statusMessageLength,
JSValue headersObjectValue,
uWS::HttpResponse<false>* response)
{
return NodeHTTPServer__writeHead<false>(globalObject, statusMessage, statusMessageLength, headersObjectValue, response);
}
extern "C" void NodeHTTPServer__writeHead_https(
JSC::JSGlobalObject* globalObject,
const char* statusMessage,
size_t statusMessageLength,
JSValue headersObjectValue,
uWS::HttpResponse<true>* response)
{
return NodeHTTPServer__writeHead<true>(globalObject, statusMessage, statusMessageLength, headersObjectValue, response);
}
extern "C" EncodedJSValue NodeHTTPServer__onRequest_http(
size_t any_server,
JSC::JSGlobalObject* globalObject,
EncodedJSValue thisValue,
EncodedJSValue callback,
uWS::HttpRequest* request,
uWS::HttpResponse<false>* response,
void** nodeHttpResponsePtr)
{
return NodeHTTPServer__onRequest<false>(any_server, globalObject, JSValue::decode(thisValue), JSValue::decode(callback), request, response, nodeHttpResponsePtr);
}
extern "C" EncodedJSValue NodeHTTPServer__onRequest_https(
size_t any_server,
JSC::JSGlobalObject* globalObject,
EncodedJSValue thisValue,
EncodedJSValue callback,
uWS::HttpRequest* request,
uWS::HttpResponse<true>* response,
void** nodeHttpResponsePtr)
{
return NodeHTTPServer__onRequest<true>(any_server, globalObject, JSValue::decode(thisValue), JSValue::decode(callback), request, response, nodeHttpResponsePtr);
}
JSC_DEFINE_HOST_FUNCTION(jsHTTPAssignHeaders, (JSGlobalObject * globalObject, CallFrame* callFrame))
{
auto& vm = globalObject->vm();
@@ -357,6 +896,10 @@ JSC_DEFINE_HOST_FUNCTION(jsHTTPSetTimeout, (JSGlobalObject * globalObject, CallF
Request__setTimeout(jsRequest->wrapped(), JSValue::encode(seconds), globalObject);
}
if (auto* nodeHttpResponse = jsDynamicCast<WebCore::JSNodeHTTPResponse*>(requestValue)) {
NodeHTTPResponse__setTimeout(nodeHttpResponse->wrapped(), JSValue::encode(seconds), globalObject);
}
return JSValue::encode(jsUndefined());
}
JSC_DEFINE_HOST_FUNCTION(jsHTTPSetServerIdleTimeout, (JSGlobalObject * globalObject, CallFrame* callFrame))
@@ -415,14 +958,15 @@ JSC_DEFINE_HOST_FUNCTION(jsHTTPSetHeader, (JSGlobalObject * globalObject, CallFr
auto scope = DECLARE_THROW_SCOPE(vm);
JSValue headersValue = callFrame->argument(0);
JSValue nameValue = callFrame->argument(1);
JSValue valueValue = callFrame->argument(2);
if (auto* headers = jsDynamicCast<WebCore::JSFetchHeaders*>(headersValue)) {
JSValue nameValue = callFrame->argument(1);
if (nameValue.isString()) {
String name = nameValue.toWTFString(globalObject);
FetchHeaders* impl = &headers->wrapped();
JSValue valueValue = callFrame->argument(2);
if (valueValue.isUndefined())
return JSValue::encode(jsUndefined());
@@ -433,23 +977,28 @@ JSC_DEFINE_HOST_FUNCTION(jsHTTPSetHeader, (JSGlobalObject * globalObject, CallFr
JSValue item = array->getIndex(globalObject, 0);
if (UNLIKELY(scope.exception()))
return JSValue::encode(jsUndefined());
impl->set(name, item.getString(globalObject));
auto value = item.toWTFString(globalObject);
RETURN_IF_EXCEPTION(scope, {});
impl->set(name, value);
RETURN_IF_EXCEPTION(scope, {});
}
for (unsigned i = 1; i < length; ++i) {
JSValue value = array->getIndex(globalObject, i);
if (UNLIKELY(scope.exception()))
return JSValue::encode(jsUndefined());
if (!value.isString())
continue;
impl->append(name, value.getString(globalObject));
auto string = value.toWTFString(globalObject);
RETURN_IF_EXCEPTION(scope, {});
impl->append(name, string);
RETURN_IF_EXCEPTION(scope, {});
}
RELEASE_AND_RETURN(scope, JSValue::encode(jsUndefined()));
return JSValue::encode(jsUndefined());
}
impl->set(name, valueValue.getString(globalObject));
auto value = valueValue.toWTFString(globalObject);
RETURN_IF_EXCEPTION(scope, {});
impl->set(name, value);
RETURN_IF_EXCEPTION(scope, {});
return JSValue::encode(jsUndefined());
}
@@ -497,7 +1046,23 @@ JSValue createNodeHTTPInternalBinding(Zig::GlobalObject* globalObject)
obj->putDirect(
vm, JSC::PropertyName(JSC::Identifier::fromString(vm, "headersTuple"_s)),
JSC::InternalFieldTuple::create(vm, globalObject->m_internalFieldTupleStructure.get()), 0);
obj->putDirectNativeFunction(
vm, globalObject, JSC::PropertyName(JSC::Identifier::fromString(vm, "webRequestOrResponseHasBodyValue"_s)),
1, jsFunctionRequestOrResponseHasBodyValue, ImplementationVisibility::Public, Intrinsic::NoIntrinsic, 0);
obj->putDirectNativeFunction(
vm, globalObject, JSC::PropertyName(JSC::Identifier::fromString(vm, "getCompleteWebRequestOrResponseBodyValueAsArrayBuffer"_s)),
1, jsFunctionGetCompleteRequestOrResponseBodyValueAsArrayBuffer, ImplementationVisibility::Public, Intrinsic::NoIntrinsic, 0);
return obj;
}
extern "C" void WebCore__FetchHeaders__toUWSResponse(WebCore::FetchHeaders* arg0, bool is_ssl, void* arg2)
{
if (is_ssl) {
writeFetchHeadersToUWSResponse<true>(*arg0, reinterpret_cast<uWS::HttpResponse<true>*>(arg2));
} else {
writeFetchHeadersToUWSResponse<false>(*arg0, reinterpret_cast<uWS::HttpResponse<false>*>(arg2));
}
}
} // namespace Bun

View File

@@ -1,11 +1,12 @@
#include "config.h"
namespace Bun {
JSC_DECLARE_HOST_FUNCTION(jsHTTPAssignHeaders);
JSC_DECLARE_HOST_FUNCTION(jsHTTPGetHeader);
JSC_DECLARE_HOST_FUNCTION(jsHTTPSetHeader);
JSC::Structure* createNodeHTTPServerSocketStructure(JSC::VM& vm);
JSC::JSValue createNodeHTTPInternalBinding(Zig::GlobalObject*);
}
}

View File

@@ -2687,7 +2687,10 @@ void GlobalObject::finishCreation(VM& vm)
m_commonStrings.initialize();
Bun::addNodeModuleConstructorProperties(vm, this);
m_JSNodeHTTPServerSocketStructure.initLater(
[](const Initializer<Structure>& init) {
init.set(Bun::createNodeHTTPServerSocketStructure(init.vm));
});
m_JSDOMFileConstructor.initLater(
[](const Initializer<JSObject>& init) {
JSObject* fileConstructor = Bun::createJSDOMFileConstructor(init.vm, init.owner);
@@ -3632,6 +3635,7 @@ void GlobalObject::visitChildrenImpl(JSCell* cell, Visitor& visitor)
thisObject->m_JSBufferClassStructure.visit(visitor);
thisObject->m_JSBufferListClassStructure.visit(visitor);
thisObject->m_JSBufferSubclassStructure.visit(visitor);
thisObject->m_JSNodeHTTPServerSocketStructure.visit(visitor);
thisObject->m_JSCryptoKey.visit(visitor);
thisObject->m_JSDOMFileConstructor.visit(visitor);
thisObject->m_JSFFIFunctionStructure.visit(visitor);
@@ -4126,6 +4130,7 @@ JSC::JSValue EvalGlobalObject::moduleLoaderEvaluate(JSGlobalObject* lexicalGloba
GlobalObject::PromiseFunctions GlobalObject::promiseHandlerID(Zig::FFIFunction handler)
{
if (handler == Bun__HTTPRequestContext__onReject) {
return GlobalObject::PromiseFunctions::Bun__HTTPRequestContext__onReject;
} else if (handler == Bun__HTTPRequestContext__onRejectStream) {
@@ -4178,6 +4183,10 @@ GlobalObject::PromiseFunctions GlobalObject::promiseHandlerID(Zig::FFIFunction h
return GlobalObject::PromiseFunctions::Bun__onResolveEntryPointResult;
} else if (handler == Bun__onRejectEntryPointResult) {
return GlobalObject::PromiseFunctions::Bun__onRejectEntryPointResult;
} else if (handler == Bun__NodeHTTPRequest__onResolve) {
return GlobalObject::PromiseFunctions::Bun__NodeHTTPRequest__onResolve;
} else if (handler == Bun__NodeHTTPRequest__onReject) {
return GlobalObject::PromiseFunctions::Bun__NodeHTTPRequest__onReject;
} else {
RELEASE_ASSERT_NOT_REACHED();
}

View File

@@ -354,8 +354,10 @@ public:
Bun__BodyValueBufferer__onResolveStream,
Bun__onResolveEntryPointResult,
Bun__onRejectEntryPointResult,
Bun__NodeHTTPRequest__onResolve,
Bun__NodeHTTPRequest__onReject,
};
static constexpr size_t promiseFunctionsSize = 24;
static constexpr size_t promiseFunctionsSize = 25;
static PromiseFunctions promiseHandlerID(SYSV_ABI EncodedJSValue (*handler)(JSC__JSGlobalObject* arg0, JSC__CallFrame* arg1));
@@ -585,6 +587,8 @@ public:
LazyProperty<JSGlobalObject, JSObject> m_performanceObject;
LazyProperty<JSGlobalObject, JSObject> m_processObject;
LazyProperty<JSGlobalObject, Structure> m_JSNodeHTTPServerSocketStructure;
bool hasOverridenModuleResolveFilenameFunction = false;
private:

View File

@@ -137,65 +137,6 @@ static WTF::StringView StringView_slice(WTF::StringView sv, unsigned start, unsi
return sv.substring(start, end - start);
}
template<typename UWSResponse>
static void writeResponseHeader(UWSResponse* res, const WTF::StringView& name, const WTF::StringView& value)
{
WTF::CString nameStr;
WTF::CString valueStr;
std::string_view nameView;
std::string_view valueView;
if (name.is8Bit()) {
const auto nameSpan = name.span8();
nameView = std::string_view(reinterpret_cast<const char*>(nameSpan.data()), nameSpan.size());
} else {
nameStr = name.utf8();
nameView = std::string_view(nameStr.data(), nameStr.length());
}
if (value.is8Bit()) {
const auto valueSpan = value.span8();
valueView = std::string_view(reinterpret_cast<const char*>(valueSpan.data()), valueSpan.size());
} else {
valueStr = value.utf8();
valueView = std::string_view(valueStr.data(), valueStr.length());
}
res->writeHeader(nameView, valueView);
}
template<typename UWSResponse>
static void copyToUWS(WebCore::FetchHeaders* headers, UWSResponse* res)
{
auto& internalHeaders = headers->internalHeaders();
for (auto& value : internalHeaders.getSetCookieHeaders()) {
if (value.is8Bit()) {
const auto valueSpan = value.span8();
res->writeHeader(std::string_view("set-cookie", 10), std::string_view(reinterpret_cast<const char*>(valueSpan.data()), valueSpan.size()));
} else {
WTF::CString valueStr = value.utf8();
res->writeHeader(std::string_view("set-cookie", 10), std::string_view(valueStr.data(), valueStr.length()));
}
}
for (const auto& header : internalHeaders.commonHeaders()) {
const auto& name = WebCore::httpHeaderNameString(header.key);
const auto& value = header.value;
writeResponseHeader<UWSResponse>(res, name, value);
}
for (auto& header : internalHeaders.uncommonHeaders()) {
const auto& name = header.key;
const auto& value = header.value;
writeResponseHeader<UWSResponse>(res, name, value);
}
}
using namespace JSC;
using namespace WebCore;
@@ -1365,15 +1306,6 @@ bool WebCore__FetchHeaders__isEmpty(WebCore__FetchHeaders* arg0)
return arg0->size() == 0;
}
void WebCore__FetchHeaders__toUWSResponse(WebCore__FetchHeaders* arg0, bool is_ssl, void* arg2)
{
if (is_ssl) {
copyToUWS<uWS::HttpResponse<true>>(arg0, reinterpret_cast<uWS::HttpResponse<true>*>(arg2));
} else {
copyToUWS<uWS::HttpResponse<false>>(arg0, reinterpret_cast<uWS::HttpResponse<false>*>(arg2));
}
}
WebCore__FetchHeaders* WebCore__FetchHeaders__createEmpty()
{
auto* headers = new WebCore::FetchHeaders({ WebCore::FetchHeaders::Guard::None, {} });

View File

@@ -76,4 +76,5 @@ pub const Classes = struct {
pub const TextEncoderStreamEncoder = JSC.WebCore.TextEncoderStreamEncoder;
pub const NativeZlib = JSC.API.NativeZlib;
pub const NativeBrotli = JSC.API.NativeBrotli;
pub const NodeHTTPResponse = JSC.API.NodeHTTPResponse;
};

View File

@@ -790,6 +790,9 @@ BUN_DECLARE_HOST_FUNCTION(Bun__HTTPRequestContext__onRejectStream);
BUN_DECLARE_HOST_FUNCTION(Bun__HTTPRequestContext__onResolve);
BUN_DECLARE_HOST_FUNCTION(Bun__HTTPRequestContext__onResolveStream);
BUN_DECLARE_HOST_FUNCTION(Bun__NodeHTTPRequest__onResolve);
BUN_DECLARE_HOST_FUNCTION(Bun__NodeHTTPRequest__onReject);
#endif
#ifdef __cplusplus

View File

@@ -56,6 +56,7 @@ public:
std::unique_ptr<GCClient::IsoSubspace> m_clientSubspaceForHandleScopeBuffer;
std::unique_ptr<GCClient::IsoSubspace> m_clientSubspaceForFunctionTemplate;
std::unique_ptr<GCClient::IsoSubspace> m_clientSubspaceForV8Function;
std::unique_ptr<GCClient::IsoSubspace> m_clientSubspaceForJSNodeHTTPServerSocket;
#include "ZigGeneratedClasses+DOMClientIsoSubspaces.h"
/* --- bun --- */

View File

@@ -56,6 +56,7 @@ public:
std::unique_ptr<IsoSubspace> m_subspaceForHandleScopeBuffer;
std::unique_ptr<IsoSubspace> m_subspaceForFunctionTemplate;
std::unique_ptr<IsoSubspace> m_subspaceForV8Function;
std::unique_ptr<IsoSubspace> m_subspaceForJSNodeHTTPServerSocket;
#include "ZigGeneratedClasses+DOMIsoSubspaces.h"
/*-- BUN --*/
@@ -898,7 +899,7 @@ public:
// std::unique_ptr<IsoSubspace> m_subspaceForXPathNSResolver;
// std::unique_ptr<IsoSubspace> m_subspaceForXPathResult;
// std::unique_ptr<IsoSubspace> m_subspaceForXSLTProcessor;
std::unique_ptr<IsoSubspace> m_subspaceForBakeGlobalScope;
std::unique_ptr<IsoSubspace> m_subspaceForAbortController;

View File

@@ -299,6 +299,20 @@ pub const Body = struct {
Error: ValueError,
Null,
// We may not have all the data yet
// So we can't know for sure if it's empty or not
// We CAN know that it is definitely empty.
pub fn isDefinitelyEmpty(this: *const Value) bool {
return switch (this.*) {
.Null => true,
.Used, .Empty => true,
.InternalBlob => this.InternalBlob.slice().len == 0,
.Blob => this.Blob.size == 0,
.WTFStringImpl => this.WTFStringImpl.length() == 0,
.Error, .Locked => false,
};
}
pub const heap_breakdown_label = "BodyValue";
pub const ValueError = union(enum) {
AbortReason: JSC.CommonAbortReason,

View File

@@ -119,10 +119,8 @@ pub const Request = struct {
pub const InternalJSEventCallback = struct {
function: JSC.Strong = .{},
pub const EventType = enum(u8) {
timeout = 0,
abort = 1,
};
pub const EventType = JSC.API.NodeHTTPResponse.AbortEvent;
pub fn init(function: JSC.JSValue, globalThis: *JSC.JSGlobalObject) InternalJSEventCallback {
return InternalJSEventCallback{
.function = JSC.Strong.create(function, globalThis),

View File

@@ -112,6 +112,58 @@ pub const Response = struct {
return &this.body.value;
}
pub export fn jsFunctionRequestOrResponseHasBodyValue(globalObject: *JSC.JSGlobalObject, callframe: *JSC.CallFrame) callconv(JSC.conv) JSC.JSValue {
_ = globalObject; // autofix
const arguments = callframe.arguments(1);
const this_value = arguments.ptr[0];
if (this_value.isEmptyOrUndefinedOrNull()) {
return .false;
}
if (this_value.as(Response)) |response| {
return JSC.JSValue.jsBoolean(!response.body.value.isDefinitelyEmpty());
} else if (this_value.as(Request)) |request| {
return JSC.JSValue.jsBoolean(!request.body.value.isDefinitelyEmpty());
}
return .false;
}
pub export fn jsFunctionGetCompleteRequestOrResponseBodyValueAsArrayBuffer(globalObject: *JSC.JSGlobalObject, callframe: *JSC.CallFrame) callconv(JSC.conv) JSC.JSValue {
const arguments = callframe.arguments(1);
const this_value = arguments.ptr[0];
if (this_value.isEmptyOrUndefinedOrNull()) {
return .undefined;
}
const body: *Body.Value = brk: {
if (this_value.as(Response)) |response| {
break :brk &response.body.value;
} else if (this_value.as(Request)) |request| {
break :brk &request.body.value;
}
return .undefined;
};
// Get the body if it's available synchronously.
switch (body.*) {
.Used, .Empty, .Null => return .undefined,
.Blob => |*blob| {
if (blob.isBunFile()) {
return .undefined;
}
defer body.* = .{ .Used = {} };
return blob.toArrayBuffer(globalObject, .transfer);
},
.WTFStringImpl, .InternalBlob => {
var any_blob = body.useAsAnyBlob();
return any_blob.toArrayBufferTransfer(globalObject);
},
.Error, .Locked => return .undefined,
}
}
pub fn getFetchHeaders(
this: *Response,
) ?*FetchHeaders {

View File

@@ -2131,7 +2131,7 @@ pub fn HTTPServerWritable(comptime ssl: bool) type {
this.res.end(buf, false);
this.has_backpressure = false;
} else {
this.has_backpressure = !this.res.write(buf);
this.has_backpressure = this.res.write(buf) == .backpressure;
}
this.handleWrote(buf.len);
return true;

View File

@@ -3132,8 +3132,8 @@ pub fn NewRefCounted(comptime T: type, comptime deinit_fn: ?fn (self: *T) void)
const ptr = bun.new(T, t);
if (Environment.enable_logs) {
if (ptr.ref_count != 1) {
Output.panic("Expected ref_count to be 1, got {d}", .{ptr.ref_count});
if (ptr.ref_count == 0) {
Output.panic("Expected ref_count to be > 0, got {d}", .{ptr.ref_count});
}
}

View File

@@ -9,7 +9,7 @@ let $debug_log_enabled = ((env) => (
// The rationale for checking all these variables is just so you don't have to exactly remember which one you set.
(env.BUN_DEBUG_ALL && env.BUN_DEBUG_ALL !== '0')
|| (env.BUN_DEBUG_JS && env.BUN_DEBUG_JS !== '0')
|| (env.BUN_DEBUG_${pathToUpperSnakeCase(filepath)})
|| (env.BUN_DEBUG_${pathToUpperSnakeCase(publicName)})
|| (env.DEBUG_${pathToUpperSnakeCase(filepath)})
))(Bun.env);
let $debug_pid_prefix = Bun.env.SHOW_PID === '1';

View File

@@ -275,6 +275,19 @@ extern "C"
}
}
size_t uws_res_get_buffered_amount(int ssl, uws_res_t *res) nonnull_fn_decl;
size_t uws_res_get_buffered_amount(int ssl, uws_res_t *res)
{
if (ssl) {
uWS::HttpResponse<true> *uwsRes = (uWS::HttpResponse<true> *)res;
return uwsRes->getBufferedAmount();
} else {
uWS::HttpResponse<false> *uwsRes = (uWS::HttpResponse<false> *)res;
return uwsRes->getBufferedAmount();
}
}
void uws_app_any(int ssl, uws_app_t *app, const char *pattern_ptr, size_t pattern_len, uws_method_handler handler, void *user_data)
{
std::string pattern = std::string(pattern_ptr, pattern_len);
@@ -1241,17 +1254,17 @@ extern "C"
}
}
bool uws_res_write(int ssl, uws_res_r res, const char *data, size_t length) nonnull_fn_decl;
bool uws_res_write(int ssl, uws_res_r res, const char *data, size_t *length) nonnull_fn_decl;
bool uws_res_write(int ssl, uws_res_r res, const char *data, size_t length)
bool uws_res_write(int ssl, uws_res_r res, const char *data, size_t *length)
{
if (ssl)
{
uWS::HttpResponse<true> *uwsRes = (uWS::HttpResponse<true> *)res;
return uwsRes->write(std::string_view(data, length));
return uwsRes->write(std::string_view(data, *length), length);
}
uWS::HttpResponse<false> *uwsRes = (uWS::HttpResponse<false> *)res;
return uwsRes->write(std::string_view(data, length));
return uwsRes->write(std::string_view(data, *length), length);
}
uint64_t uws_res_get_write_offset(int ssl, uws_res_r res) nonnull_fn_decl;
uint64_t uws_res_get_write_offset(int ssl, uws_res_r res)

View File

@@ -17,6 +17,10 @@ const SSLWrapper = @import("../bun.js/api/bun/ssl_wrapper.zig").SSLWrapper;
const TextEncoder = @import("../bun.js/webcore/encoding.zig").Encoder;
const JSC = bun.JSC;
const EventLoopTimer = @import("../bun.js//api//Timer.zig").EventLoopTimer;
const WriteResult = union(enum) {
want_more: usize,
backpressure: usize,
};
pub const CloseCode = enum(i32) {
normal = 0,
@@ -3036,6 +3040,41 @@ pub const AnyResponse = union(enum) {
SSL: *NewApp(true).Response,
TCP: *NewApp(false).Response,
pub fn getRemoteSocketInfo(this: AnyResponse) ?SocketAddress {
return switch (this) {
.SSL => |resp| resp.getRemoteSocketInfo(),
.TCP => |resp| resp.getRemoteSocketInfo(),
};
}
pub fn getWriteOffset(this: AnyResponse) u64 {
return switch (this) {
.SSL => |resp| resp.getWriteOffset(),
.TCP => |resp| resp.getWriteOffset(),
};
}
pub fn getBufferedAmount(this: AnyResponse) u64 {
return switch (this) {
.SSL => |resp| resp.getBufferedAmount(),
.TCP => |resp| resp.getBufferedAmount(),
};
}
pub fn writeContinue(this: AnyResponse) void {
return switch (this) {
.SSL => |resp| resp.writeContinue(),
.TCP => |resp| resp.writeContinue(),
};
}
pub fn state(this: AnyResponse) State {
return switch (this) {
.SSL => |resp| resp.state(),
.TCP => |resp| resp.state(),
};
}
pub fn timeout(this: AnyResponse, seconds: u8) void {
switch (this) {
.SSL => |resp| resp.timeout(seconds),
@@ -3043,6 +3082,16 @@ pub const AnyResponse = union(enum) {
}
}
pub fn onData(this: AnyResponse, comptime UserDataType: type, comptime handler: fn (UserDataType, []const u8, bool) void, opcional_data: UserDataType) void {
return switch (this) {
inline .SSL, .TCP => |resp, ssl| resp.onData(UserDataType, struct {
pub fn onDataCallback(user_data: UserDataType, _: *uws.NewApp(ssl == .SSL).Response, data: []const u8, last: bool) void {
@call(.always_inline, handler, .{ user_data, data, last });
}
}.onDataCallback, opcional_data),
};
}
pub fn writeStatus(this: AnyResponse, status: []const u8) void {
return switch (this) {
.SSL => |resp| resp.writeStatus(status),
@@ -3057,7 +3106,7 @@ pub const AnyResponse = union(enum) {
};
}
pub fn write(this: AnyResponse, data: []const u8) void {
pub fn write(this: AnyResponse, data: []const u8) WriteResult {
return switch (this) {
.SSL => |resp| resp.write(data),
.TCP => |resp| resp.write(data),
@@ -3129,6 +3178,22 @@ pub const AnyResponse = union(enum) {
};
}
pub fn onTimeout(this: AnyResponse, comptime UserDataType: type, comptime handler: fn (UserDataType, AnyResponse) void, opcional_data: UserDataType) void {
const wrapper = struct {
pub fn ssl_handler(user_data: UserDataType, resp: *NewApp(true).Response) void {
handler(user_data, .{ .SSL = resp });
}
pub fn tcp_handler(user_data: UserDataType, resp: *NewApp(false).Response) void {
handler(user_data, .{ .TCP = resp });
}
};
return switch (this) {
.SSL => |resp| resp.onTimeout(UserDataType, wrapper.ssl_handler, opcional_data),
.TCP => |resp| resp.onTimeout(UserDataType, wrapper.tcp_handler, opcional_data),
};
}
pub fn onAborted(this: AnyResponse, comptime UserDataType: type, comptime handler: fn (UserDataType, AnyResponse) void, opcional_data: UserDataType) void {
const wrapper = struct {
pub fn ssl_handler(user_data: UserDataType, resp: *NewApp(true).Response) void {
@@ -3526,8 +3591,15 @@ pub fn NewApp(comptime ssl: bool) type {
pub fn resetTimeout(res: *Response) void {
uws_res_reset_timeout(ssl_flag, res.downcast());
}
pub fn write(res: *Response, data: []const u8) bool {
return uws_res_write(ssl_flag, res.downcast(), data.ptr, data.len);
pub fn getBufferedAmount(res: *Response) u64 {
return uws_res_get_buffered_amount(ssl_flag, res.downcast());
}
pub fn write(res: *Response, data: []const u8) WriteResult {
var len: usize = data.len;
return switch (uws_res_write(ssl_flag, res.downcast(), data.ptr, &len)) {
true => .{ .want_more = len },
false => .{ .backpressure = len },
};
}
pub fn getWriteOffset(res: *Response) u64 {
return uws_res_get_write_offset(ssl_flag, res.downcast());
@@ -3952,7 +4024,8 @@ extern fn uws_res_end_without_body(ssl: i32, res: *uws_res, close_connection: bo
extern fn uws_res_end_sendfile(ssl: i32, res: *uws_res, write_offset: u64, close_connection: bool) void;
extern fn uws_res_timeout(ssl: i32, res: *uws_res, timeout: u8) void;
extern fn uws_res_reset_timeout(ssl: i32, res: *uws_res) void;
extern fn uws_res_write(ssl: i32, res: *uws_res, data: [*c]const u8, length: usize) bool;
extern fn uws_res_get_buffered_amount(ssl: i32, res: *uws_res) u64;
extern fn uws_res_write(ssl: i32, res: *uws_res, data: ?[*]const u8, length: *usize) bool;
extern fn uws_res_get_write_offset(ssl: i32, res: *uws_res) u64;
extern fn uws_res_override_write_offset(ssl: i32, res: *uws_res, u64) void;
extern fn uws_res_has_responded(ssl: i32, res: *uws_res) bool;
@@ -4049,6 +4122,7 @@ pub const State = enum(u8) {
HTTP_END_CALLED = 4,
HTTP_RESPONSE_PENDING = 8,
HTTP_CONNECTION_CLOSE = 16,
HTTP_WROTE_CONTENT_LENGTH_HEADER = 32,
_,
@@ -4056,6 +4130,10 @@ pub const State = enum(u8) {
return @intFromEnum(this) & @intFromEnum(State.HTTP_RESPONSE_PENDING) != 0;
}
pub inline fn hasWrittenContentLengthHeader(this: State) bool {
return @intFromEnum(this) & @intFromEnum(State.HTTP_WROTE_CONTENT_LENGTH_HEADER) != 0;
}
pub inline fn isHttpEndCalled(this: State) bool {
return @intFromEnum(this) & @intFromEnum(State.HTTP_END_CALLED) != 0;
}

View File

@@ -299,7 +299,7 @@ export function initializeNextTickQueue(process, nextTickQueue, drainMicrotasksF
setup = undefined;
};
function nextTick(cb, args) {
function nextTick(cb, ...args) {
validateFunction(cb, "callback");
if (setup) {
setup();
@@ -309,7 +309,9 @@ export function initializeNextTickQueue(process, nextTickQueue, drainMicrotasksF
queue.push({
callback: cb,
args: $argumentCount() > 1 ? Array.prototype.slice.$call(arguments, 1) : undefined,
// We want to avoid materializing the args if there are none because it's
// a waste of memory and Array.prototype.slice shows up in profiling.
args: $argumentCount() > 1 ? args : undefined,
frame: $getInternalField($asyncContext, 0),
});
$putInternalField(nextTickQueue, 0, 1);

File diff suppressed because it is too large Load Diff

View File

@@ -29,6 +29,7 @@ pub const Jest = @import("./bun.js/test/jest.zig");
pub const Expect = @import("./bun.js/test/expect.zig");
pub const Snapshot = @import("./bun.js/test/snapshot.zig");
pub const API = struct {
pub const NodeHTTPResponse = @import("./bun.js/api/server.zig").NodeHTTPResponse;
pub const Glob = @import("./bun.js/api/glob.zig");
pub const Shell = @import("./shell/shell.zig");
pub const JSBundler = @import("./bun.js/api/JSBundler.zig").JSBundler;

View File

@@ -100,7 +100,7 @@ pub fn TaggedPointerUnion(comptime Types: anytype) type {
const TagType: type = result.tag_type;
return struct {
return packed struct {
pub const Tag = TagType;
pub const TagInt = TagSize;
pub const type_map: TypeMap(Types) = result.ty_map;
@@ -147,6 +147,10 @@ pub fn TaggedPointerUnion(comptime Types: anytype) type {
return @as(TagType, @enumFromInt(this.repr.data));
}
pub fn case(comptime Type: type) Tag {
return @field(Tag, typeBaseName(@typeName(Type)));
}
/// unsafely cast a tagged pointer to a specific type, without checking that it's really that type
pub inline fn as(this: This, comptime Type: type) *Type {
comptime assert_type(Type);

View File

@@ -79,7 +79,7 @@ async function getDevServerURL() {
readStream()
.catch(e => reject(e))
.finally(() => {
dev_server.unref?.();
dev_server?.unref?.();
});
await promise;
return baseUrl;

View File

@@ -10,7 +10,7 @@ const options = {
const req = http.request(options, res => {
patchEmitter(res, "res");
console.log(`STATUS: ${res.statusCode}`);
console.log(`"STATUS: ${res.statusCode}"`);
res.setEncoding("utf8");
});
patchEmitter(req, "req");
@@ -21,7 +21,10 @@ function patchEmitter(emitter, prefix) {
var oldEmit = emitter.emit;
emitter.emit = function () {
console.log([prefix, arguments[0]]);
if (typeof arguments[0] !== "symbol") {
console.log([prefix, arguments[0]]);
}
oldEmit.apply(emitter, arguments);
};
}

View File

@@ -77,10 +77,8 @@ describe("node:http", () => {
}
});
it("request & response body streaming (large)", async () => {
const input = Buffer.alloc("hello world, hello world".length * 9000, "hello world, hello world");
try {
const bodyBlob = new Blob(["hello world", "hello world".repeat(9000)]);
const input = await bodyBlob.text();
var server = createServer((req, res) => {
res.writeHead(200, { "Content-Type": "text/plain" });
req.on("data", chunk => {
@@ -94,11 +92,11 @@ describe("node:http", () => {
const url = await listen(server);
const res = await fetch(url, {
method: "POST",
body: bodyBlob,
body: input,
});
const out = await res.text();
expect(out).toBe(input);
expect(out).toBe(input.toString());
} finally {
server.close();
}
@@ -416,6 +414,7 @@ describe("node:http", () => {
const req = request(`http://localhost:${port}`, res => {
let data = "";
res.setEncoding("utf8");
res.on("data", chunk => {
data += chunk;
});
@@ -1279,22 +1278,7 @@ describe("server.address should be valid IP", () => {
done(err);
}
});
test("ServerResponse reply", done => {
const createDone = createDoneDotAll(done);
const doneRequest = createDone();
try {
const req = {};
const sendedText = "Bun\n";
const res = new ServerResponse(req, async (res: Response) => {
expect(await res.text()).toBe(sendedText);
doneRequest();
});
res.write(sendedText);
res.end();
} catch (err) {
doneRequest(err);
}
});
test("ServerResponse instanceof OutgoingMessage", () => {
expect(new ServerResponse({}) instanceof OutgoingMessage).toBe(true);
});
@@ -1885,19 +1869,18 @@ it("should emit events in the right order", async () => {
expect(err).toBeEmpty();
const out = await new Response(stdout).text();
// TODO prefinish and socket are not emitted in the right order
expect(out.split("\n")).toEqual([
`[ "req", "prefinish" ]`,
`[ "req", "socket" ]`,
`[ "req", "finish" ]`,
`[ "req", "response" ]`,
expect(out.split("\n").filter(Boolean).map(JSON.parse)).toStrictEqual([
["req", "socket"],
["req", "prefinish"],
["req", "finish"],
["req", "response"],
"STATUS: 200",
// `[ "res", "resume" ]`,
// `[ "res", "readable" ]`,
// `[ "res", "end" ]`,
`[ "req", "close" ]`,
`[ "res", Symbol(kConstruct) ]`,
// `[ "res", "close" ]`,
"",
// TODO: not totally right:
["req", "close"],
["res", "resume"],
["res", "readable"],
["res", "end"],
["res", "close"],
]);
});
@@ -2332,6 +2315,7 @@ it("should emit close, and complete should be true only after close #13373", asy
const [req, res] = await once(server, "request");
expect(req.complete).toBe(false);
console.log("ok 1");
const closeEvent = once(req, "close");
res.end("hi");

View File

@@ -221,7 +221,7 @@ describe("fetch() with streaming", () => {
expect(true).toBe(true);
} finally {
server?.close();
server?.closeAllConnections();
}
});
}

View File

@@ -5,11 +5,13 @@ const server = createServer((req, res) => {
throw new Error("Oops!");
});
server.listen({ port: 0 }, async (err, host, port) => {
server.listen({ port: 0 }, async err => {
const { port, address: host } = server.address();
if (err) {
console.error(err);
process.exit(1);
}
const hostname = isIPv6(host) ? `[${host}]` : host;
process.send(`http://${hostname}:${port}/`);
(process?.connected ? process.send : console.log)(`http://${hostname}:${port}/`);
});

View File

@@ -2,13 +2,13 @@ import { spawn } from "bun";
import { expect, test } from "bun:test";
import { bunEnv, bunExe } from "harness";
test("node:http should not crash when server throws", async () => {
test("node:http should not crash when server throws, and should abruptly close the socket", async () => {
const { promise, resolve, reject } = Promise.withResolvers();
await using server = spawn({
cwd: import.meta.dirname,
cmd: [bunExe(), "04298.fixture.js"],
env: bunEnv,
stderr: "pipe",
stderr: "inherit",
ipc(url) {
resolve(url);
},
@@ -20,5 +20,4 @@ test("node:http should not crash when server throws", async () => {
});
const url = await promise;
const response = await fetch(url);
expect(response.status).toBe(500);
});

View File

@@ -0,0 +1,41 @@
const { spawn } = await import("child_process");
const assert = await import("assert");
const http = await import("http");
async function runTest() {
return new Promise((resolve, reject) => {
const server = spawn("node", ["04298.fixture.js"], {
cwd: import.meta.dirname,
stdio: ["inherit", "inherit", "inherit", "ipc"],
});
server.on("message", url => {
http
.get(url, res => {
assert.strictEqual(res.statusCode, 500);
server.kill();
resolve();
})
.on("error", reject);
});
server.on("error", reject);
server.on("exit", (code, signal) => {
if (code !== null && code !== 0) {
reject(new Error(`Server exited with code ${code}`));
} else if (signal) {
reject(new Error(`Server was killed with signal ${signal}`));
}
});
});
}
runTest()
.then(() => {
console.log("Test passed");
process.exit(0);
})
.catch(error => {
console.error("Test failed:", error);
process.exit(1);
});