mirror of
https://github.com/oven-sh/bun
synced 2026-03-03 05:51:03 +01:00
Compare commits
15 Commits
claude/imp
...
jarred/uws
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
00e017c274 | ||
|
|
8212e3afc6 | ||
|
|
da3e7160a1 | ||
|
|
2de2cb607b | ||
|
|
3852109ea0 | ||
|
|
8ca6dba524 | ||
|
|
d9a1dc78f9 | ||
|
|
1f93522b88 | ||
|
|
4a53dee5c1 | ||
|
|
44f1870b17 | ||
|
|
7b295c0d18 | ||
|
|
432e92fd27 | ||
|
|
68ad269ef0 | ||
|
|
deb44aa80b | ||
|
|
a660692743 |
@@ -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",
|
||||
|
||||
13
bench/snippets/express-hello.mjs
Normal file
13
bench/snippets/express-hello.mjs
Normal 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}`);
|
||||
});
|
||||
20
bench/snippets/fastify.mjs
Normal file
20
bench/snippets/fastify.mjs
Normal 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();
|
||||
@@ -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>();
|
||||
|
||||
@@ -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>{};
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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");
|
||||
@@ -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
@@ -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"],
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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*);
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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, {} });
|
||||
|
||||
@@ -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;
|
||||
};
|
||||
|
||||
3
src/bun.js/bindings/headers.h
generated
3
src/bun.js/bindings/headers.h
generated
@@ -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
|
||||
|
||||
@@ -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 --- */
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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),
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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});
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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';
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
1429
src/js/node/http.ts
1429
src/js/node/http.ts
File diff suppressed because it is too large
Load Diff
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -79,7 +79,7 @@ async function getDevServerURL() {
|
||||
readStream()
|
||||
.catch(e => reject(e))
|
||||
.finally(() => {
|
||||
dev_server.unref?.();
|
||||
dev_server?.unref?.();
|
||||
});
|
||||
await promise;
|
||||
return baseUrl;
|
||||
|
||||
@@ -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);
|
||||
};
|
||||
}
|
||||
|
||||
@@ -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");
|
||||
|
||||
|
||||
@@ -221,7 +221,7 @@ describe("fetch() with streaming", () => {
|
||||
|
||||
expect(true).toBe(true);
|
||||
} finally {
|
||||
server?.close();
|
||||
server?.closeAllConnections();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@@ -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}/`);
|
||||
});
|
||||
|
||||
@@ -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);
|
||||
});
|
||||
|
||||
41
test/regression/issue/04298/node-fixture.mjs
generated
Normal file
41
test/regression/issue/04298/node-fixture.mjs
generated
Normal 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);
|
||||
});
|
||||
Reference in New Issue
Block a user