diff --git a/src/bun.js/bindings/NodeHTTP.cpp b/src/bun.js/bindings/NodeHTTP.cpp index 3b939e5929..116183e7f7 100644 --- a/src/bun.js/bindings/NodeHTTP.cpp +++ b/src/bun.js/bindings/NodeHTTP.cpp @@ -21,770 +21,14 @@ #include #include #include "JSSocketAddressDTO.h" - -extern "C" { -struct us_socket_stream_buffer_t { - char* list_ptr = nullptr; - size_t list_cap = 0; - size_t listLen = 0; - size_t total_bytes_written = 0; - size_t cursor = 0; - - size_t bufferedSize() const - { - return listLen - cursor; - } - size_t totalBytesWritten() const - { - return total_bytes_written; - } -}; -} - -extern "C" uint64_t uws_res_get_remote_address_info(void* res, const char** dest, int* port, bool* is_ipv6); -extern "C" uint64_t uws_res_get_local_address_info(void* res, const char** dest, int* port, bool* is_ipv6); - -extern "C" void Bun__NodeHTTPResponse_setClosed(void* zigResponse); -extern "C" void Bun__NodeHTTPResponse_onClose(void* zigResponse, JSC::EncodedJSValue jsValue); -extern "C" EncodedJSValue us_socket_buffered_js_write(void* socket, bool is_ssl, bool ended, us_socket_stream_buffer_t* streamBuffer, JSC::JSGlobalObject* globalObject, JSC::EncodedJSValue data, JSC::EncodedJSValue encoding); -extern "C" void us_socket_free_stream_buffer(us_socket_stream_buffer_t* streamBuffer); +#include "node/JSNodeHTTPServerSocket.h" +#include "node/JSNodeHTTPServerSocketPrototype.h" namespace Bun { using namespace JSC; using namespace WebCore; -JSC_DEFINE_CUSTOM_SETTER(noOpSetter, (JSGlobalObject * globalObject, JSC::EncodedJSValue thisValue, JSC::EncodedJSValue value, PropertyName propertyName)) -{ - return false; -} - -JSC_DECLARE_CUSTOM_GETTER(jsNodeHttpServerSocketGetterOnClose); -JSC_DECLARE_CUSTOM_GETTER(jsNodeHttpServerSocketGetterOnDrain); -JSC_DECLARE_CUSTOM_GETTER(jsNodeHttpServerSocketGetterClosed); -JSC_DECLARE_CUSTOM_SETTER(jsNodeHttpServerSocketSetterOnClose); -JSC_DECLARE_CUSTOM_SETTER(jsNodeHttpServerSocketSetterOnDrain); -JSC_DECLARE_CUSTOM_SETTER(jsNodeHttpServerSocketSetterOnData); -JSC_DECLARE_CUSTOM_GETTER(jsNodeHttpServerSocketGetterOnData); -JSC_DECLARE_CUSTOM_GETTER(jsNodeHttpServerSocketGetterBytesWritten); -JSC_DECLARE_HOST_FUNCTION(jsFunctionNodeHTTPServerSocketClose); -JSC_DECLARE_HOST_FUNCTION(jsFunctionNodeHTTPServerSocketWrite); -JSC_DECLARE_HOST_FUNCTION(jsFunctionNodeHTTPServerSocketEnd); -JSC_DECLARE_CUSTOM_GETTER(jsNodeHttpServerSocketGetterResponse); -JSC_DECLARE_CUSTOM_GETTER(jsNodeHttpServerSocketGetterRemoteAddress); -JSC_DECLARE_CUSTOM_GETTER(jsNodeHttpServerSocketGetterLocalAddress); - BUN_DECLARE_HOST_FUNCTION(Bun__drainMicrotasksFromJS); -JSC_DECLARE_CUSTOM_GETTER(jsNodeHttpServerSocketGetterDuplex); -JSC_DECLARE_CUSTOM_SETTER(jsNodeHttpServerSocketSetterDuplex); -JSC_DECLARE_CUSTOM_GETTER(jsNodeHttpServerSocketGetterIsSecureEstablished); -// Create a static hash table of values containing an onclose DOMAttributeGetterSetter and a close function -static const HashTableValue JSNodeHTTPServerSocketPrototypeTableValues[] = { - { "onclose"_s, static_cast(PropertyAttribute::CustomAccessor), NoIntrinsic, { HashTableValue::GetterSetterType, jsNodeHttpServerSocketGetterOnClose, jsNodeHttpServerSocketSetterOnClose } }, - { "ondrain"_s, static_cast(PropertyAttribute::CustomAccessor), NoIntrinsic, { HashTableValue::GetterSetterType, jsNodeHttpServerSocketGetterOnDrain, jsNodeHttpServerSocketSetterOnDrain } }, - { "ondata"_s, static_cast(PropertyAttribute::CustomAccessor), NoIntrinsic, { HashTableValue::GetterSetterType, jsNodeHttpServerSocketGetterOnData, jsNodeHttpServerSocketSetterOnData } }, - { "bytesWritten"_s, static_cast(PropertyAttribute::CustomAccessor), NoIntrinsic, { HashTableValue::GetterSetterType, jsNodeHttpServerSocketGetterBytesWritten, noOpSetter } }, - { "closed"_s, static_cast(PropertyAttribute::CustomAccessor | PropertyAttribute::ReadOnly), NoIntrinsic, { HashTableValue::GetterSetterType, jsNodeHttpServerSocketGetterClosed, noOpSetter } }, - { "response"_s, static_cast(PropertyAttribute::CustomAccessor | PropertyAttribute::ReadOnly), NoIntrinsic, { HashTableValue::GetterSetterType, jsNodeHttpServerSocketGetterResponse, noOpSetter } }, - { "duplex"_s, static_cast(PropertyAttribute::CustomAccessor), NoIntrinsic, { HashTableValue::GetterSetterType, jsNodeHttpServerSocketGetterDuplex, jsNodeHttpServerSocketSetterDuplex } }, - { "remoteAddress"_s, static_cast(PropertyAttribute::CustomAccessor | PropertyAttribute::ReadOnly), NoIntrinsic, { HashTableValue::GetterSetterType, jsNodeHttpServerSocketGetterRemoteAddress, noOpSetter } }, - { "localAddress"_s, static_cast(PropertyAttribute::CustomAccessor | PropertyAttribute::ReadOnly), NoIntrinsic, { HashTableValue::GetterSetterType, jsNodeHttpServerSocketGetterLocalAddress, noOpSetter } }, - { "close"_s, static_cast(PropertyAttribute::Function | PropertyAttribute::DontEnum), NoIntrinsic, { HashTableValue::NativeFunctionType, jsFunctionNodeHTTPServerSocketClose, 0 } }, - { "write"_s, static_cast(PropertyAttribute::Function | PropertyAttribute::DontEnum), NoIntrinsic, { HashTableValue::NativeFunctionType, jsFunctionNodeHTTPServerSocketWrite, 2 } }, - { "end"_s, static_cast(PropertyAttribute::Function | PropertyAttribute::DontEnum), NoIntrinsic, { HashTableValue::NativeFunctionType, jsFunctionNodeHTTPServerSocketEnd, 0 } }, - { "secureEstablished"_s, static_cast(PropertyAttribute::CustomAccessor | PropertyAttribute::ReadOnly), NoIntrinsic, { HashTableValue::GetterSetterType, jsNodeHttpServerSocketGetterIsSecureEstablished, noOpSetter } }, -}; - -class JSNodeHTTPServerSocketPrototype final : public JSC::JSNonFinalObject { -public: - using Base = JSC::JSNonFinalObject; - - static JSNodeHTTPServerSocketPrototype* create(VM& vm, Structure* structure) - { - JSNodeHTTPServerSocketPrototype* prototype = new (NotNull, allocateCell(vm)) JSNodeHTTPServerSocketPrototype(vm, structure); - prototype->finishCreation(vm); - return prototype; - } - - DECLARE_INFO; - - static constexpr bool needsDestruction = false; - static constexpr unsigned StructureFlags = Base::StructureFlags | HasStaticPropertyTable; - - template - 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) - { - Base::finishCreation(vm); - ASSERT(inherits(info())); - reifyStaticProperties(vm, info(), JSNodeHTTPServerSocketPrototypeTableValues, *this); - this->structure()->setMayBePrototype(true); - } -}; - -class JSNodeHTTPServerSocket : public JSC::JSDestructibleObject { -public: - using Base = JSC::JSDestructibleObject; - us_socket_stream_buffer_t streamBuffer = {}; - us_socket_t* socket = nullptr; - unsigned is_ssl : 1 = 0; - unsigned ended : 1 = 0; - unsigned upgraded : 1 = 0; - JSC::Strong strongThis = {}; - - static JSNodeHTTPServerSocket* create(JSC::VM& vm, JSC::Structure* structure, us_socket_t* socket, bool is_ssl, WebCore::JSNodeHTTPResponse* response) - { - auto* object = new (JSC::allocateCell(vm)) JSNodeHTTPServerSocket(vm, structure, socket, is_ssl, response); - object->finishCreation(vm); - return object; - } - - static JSNodeHTTPServerSocket* create(JSC::VM& vm, Zig::GlobalObject* globalObject, us_socket_t* socket, bool is_ssl, WebCore::JSNodeHTTPResponse* response) - { - auto* structure = globalObject->m_JSNodeHTTPServerSocketStructure.getInitializedOnMainThread(globalObject); - return create(vm, structure, socket, is_ssl, response); - } - - static void destroy(JSC::JSCell* cell) - { - static_cast(cell)->JSNodeHTTPServerSocket::~JSNodeHTTPServerSocket(); - } - - template - static void clearSocketData(bool upgraded, us_socket_t* socket) - { - if (upgraded) { - auto* webSocket = (uWS::WebSocketData*)us_socket_ext(SSL, socket); - webSocket->socketData = nullptr; - } else { - auto* httpResponseData = (uWS::HttpResponseData*)us_socket_ext(SSL, socket); - httpResponseData->socketData = nullptr; - } - } - - void close() - { - if (socket) { - us_socket_close(is_ssl, socket, 0, nullptr); - } - } - - bool isClosed() const - { - return !socket || us_socket_is_closed(is_ssl, socket); - } - // This means: - // - [x] TLS - // - [x] Handshake has completed - // - [x] Handshake marked the connection as authorized - bool isAuthorized() const - { - // is secure means that tls was established successfully - if (!is_ssl || !socket) return false; - auto* context = us_socket_context(is_ssl, socket); - if (!context) return false; - auto* data = (uWS::HttpContextData*)us_socket_context_ext(is_ssl, context); - if (!data) return false; - return data->flags.isAuthorized; - } - ~JSNodeHTTPServerSocket() - { - if (socket) { - if (is_ssl) { - clearSocketData(this->upgraded, socket); - } else { - clearSocketData(this->upgraded, socket); - } - } - us_socket_free_stream_buffer(&streamBuffer); - } - - JSNodeHTTPServerSocket(JSC::VM& vm, JSC::Structure* structure, us_socket_t* socket, bool is_ssl, WebCore::JSNodeHTTPResponse* response) - : JSC::JSDestructibleObject(vm, structure) - , socket(socket) - , is_ssl(is_ssl) - { - currentResponseObject.setEarlyValue(vm, this, response); - } - - mutable WriteBarrier functionToCallOnClose; - mutable WriteBarrier functionToCallOnDrain; - mutable WriteBarrier functionToCallOnData; - mutable WriteBarrier currentResponseObject; - mutable WriteBarrier m_remoteAddress; - mutable WriteBarrier m_localAddress; - mutable WriteBarrier m_duplex; - - DECLARE_INFO; - DECLARE_VISIT_CHILDREN; - - template static JSC::GCClient::IsoSubspace* subspaceFor(JSC::VM& vm) - { - if constexpr (mode == JSC::SubspaceAccess::Concurrently) - return nullptr; - - return WebCore::subspaceForImpl( - vm, - [](auto& spaces) { return spaces.m_clientSubspaceForJSNodeHTTPServerSocket.get(); }, - [](auto& spaces, auto&& space) { spaces.m_clientSubspaceForJSNodeHTTPServerSocket = std::forward(space); }, - [](auto& spaces) { return spaces.m_subspaceForJSNodeHTTPServerSocket.get(); }, - [](auto& spaces, auto&& space) { spaces.m_subspaceForJSNodeHTTPServerSocket = std::forward(space); }); - } - - void detach() - { - this->m_duplex.clear(); - this->currentResponseObject.clear(); - this->strongThis.clear(); - } - - void onClose() - { - - this->socket = nullptr; - if (auto* res = this->currentResponseObject.get(); res != nullptr && res->m_ctx != nullptr) { - Bun__NodeHTTPResponse_setClosed(res->m_ctx); - } - - // This function can be called during GC! - Zig::GlobalObject* globalObject = static_cast(this->globalObject()); - if (!functionToCallOnClose) { - if (auto* res = this->currentResponseObject.get(); res != nullptr && res->m_ctx != nullptr) { - Bun__NodeHTTPResponse_onClose(res->m_ctx, JSValue::encode(res)); - } - this->detach(); - return; - } - - WebCore::ScriptExecutionContext* scriptExecutionContext = globalObject->scriptExecutionContext(); - - if (scriptExecutionContext) { - scriptExecutionContext->postTask([self = this](ScriptExecutionContext& context) { - WTF::NakedPtr exception; - auto* globalObject = defaultGlobalObject(context.globalObject()); - auto* thisObject = self; - auto* callbackObject = thisObject->functionToCallOnClose.get(); - if (!callbackObject) { - if (auto* res = thisObject->currentResponseObject.get(); res != nullptr && res->m_ctx != nullptr) { - Bun__NodeHTTPResponse_onClose(res->m_ctx, JSValue::encode(res)); - } - thisObject->detach(); - return; - } - auto callData = JSC::getCallData(callbackObject); - MarkedArgumentBuffer args; - EnsureStillAliveScope ensureStillAlive(self); - - if (globalObject->scriptExecutionStatus(globalObject, thisObject) == ScriptExecutionStatus::Running) { - if (auto* res = thisObject->currentResponseObject.get(); res != nullptr && res->m_ctx != nullptr) { - Bun__NodeHTTPResponse_onClose(res->m_ctx, JSValue::encode(res)); - } - - profiledCall(globalObject, JSC::ProfilingReason::API, callbackObject, callData, thisObject, args, exception); - - if (auto* ptr = exception.get()) { - exception.clear(); - globalObject->reportUncaughtExceptionAtEventLoop(globalObject, ptr); - } - } - thisObject->detach(); - }); - } - } - - void onDrain() - { - // This function can be called during GC! - Zig::GlobalObject* globalObject = static_cast(this->globalObject()); - if (!functionToCallOnDrain) { - return; - } - - auto bufferedSize = this->streamBuffer.bufferedSize(); - if (bufferedSize > 0) { - - auto* globalObject = defaultGlobalObject(this->globalObject()); - auto scope = DECLARE_CATCH_SCOPE(globalObject->vm()); - us_socket_buffered_js_write(this->socket, this->is_ssl, this->ended, &this->streamBuffer, globalObject, JSValue::encode(JSC::jsUndefined()), JSValue::encode(JSC::jsUndefined())); - if (scope.exception()) { - globalObject->reportUncaughtExceptionAtEventLoop(globalObject, scope.exception()); - return; - } - bufferedSize = this->streamBuffer.bufferedSize(); - - if (bufferedSize > 0) { - // need to drain more - return; - } - } - WebCore::ScriptExecutionContext* scriptExecutionContext = globalObject->scriptExecutionContext(); - - if (scriptExecutionContext) { - scriptExecutionContext->postTask([self = this](ScriptExecutionContext& context) { - WTF::NakedPtr exception; - auto* globalObject = defaultGlobalObject(context.globalObject()); - auto* thisObject = self; - auto* callbackObject = thisObject->functionToCallOnDrain.get(); - if (!callbackObject) { - return; - } - auto callData = JSC::getCallData(callbackObject); - MarkedArgumentBuffer args; - EnsureStillAliveScope ensureStillAlive(self); - - if (globalObject->scriptExecutionStatus(globalObject, thisObject) == ScriptExecutionStatus::Running) { - profiledCall(globalObject, JSC::ProfilingReason::API, callbackObject, callData, thisObject, args, exception); - - if (auto* ptr = exception.get()) { - exception.clear(); - globalObject->reportUncaughtExceptionAtEventLoop(globalObject, ptr); - } - } - }); - } - } - - void - onData(const char* data, int length, bool last) - { - // This function can be called during GC! - Zig::GlobalObject* globalObject = static_cast(this->globalObject()); - if (!functionToCallOnData) { - return; - } - - WebCore::ScriptExecutionContext* scriptExecutionContext = globalObject->scriptExecutionContext(); - - if (scriptExecutionContext) { - auto scope = DECLARE_CATCH_SCOPE(globalObject->vm()); - JSC::JSUint8Array* buffer = WebCore::createBuffer(globalObject, std::span(reinterpret_cast(data), length)); - auto chunk = JSC::JSValue(buffer); - if (scope.exception()) { - globalObject->reportUncaughtExceptionAtEventLoop(globalObject, scope.exception()); - return; - } - gcProtect(chunk); - scriptExecutionContext->postTask([self = this, chunk = chunk, last = last](ScriptExecutionContext& context) { - WTF::NakedPtr exception; - auto* globalObject = defaultGlobalObject(context.globalObject()); - auto* thisObject = self; - auto* callbackObject = thisObject->functionToCallOnData.get(); - EnsureStillAliveScope ensureChunkStillAlive(chunk); - gcUnprotect(chunk); - if (!callbackObject) { - return; - } - - auto callData = JSC::getCallData(callbackObject); - MarkedArgumentBuffer args; - args.append(chunk); - args.append(JSC::jsBoolean(last)); - EnsureStillAliveScope ensureStillAlive(self); - - if (globalObject->scriptExecutionStatus(globalObject, thisObject) == ScriptExecutionStatus::Running) { - profiledCall(globalObject, JSC::ProfilingReason::API, callbackObject, callData, thisObject, args, exception); - - if (auto* ptr = exception.get()) { - exception.clear(); - globalObject->reportUncaughtExceptionAtEventLoop(globalObject, ptr); - } - } - }); - } - } - - static Structure* createStructure(JSC::VM& vm, JSC::JSGlobalObject* globalObject) - { - auto* structure = JSC::Structure::create(vm, globalObject, globalObject->objectPrototype(), JSC::TypeInfo(JSC::ObjectType, StructureFlags), JSNodeHTTPServerSocketPrototype::info()); - auto* prototype = JSNodeHTTPServerSocketPrototype::create(vm, structure); - return JSC::Structure::create(vm, globalObject, prototype, JSC::TypeInfo(JSC::ObjectType, StructureFlags), info()); - } - - void finishCreation(JSC::VM& vm) - { - Base::finishCreation(vm); - } -}; - -JSC_DEFINE_HOST_FUNCTION(jsFunctionNodeHTTPServerSocketClose, (JSC::JSGlobalObject * globalObject, JSC::CallFrame* callFrame)) -{ - auto* thisObject = jsDynamicCast(callFrame->thisValue()); - if (!thisObject) [[unlikely]] { - return JSValue::encode(JSC::jsUndefined()); - } - if (thisObject->isClosed()) { - return JSValue::encode(JSC::jsUndefined()); - } - thisObject->close(); - - return JSValue::encode(JSC::jsUndefined()); -} - -JSC_DEFINE_HOST_FUNCTION(jsFunctionNodeHTTPServerSocketWrite, (JSC::JSGlobalObject * globalObject, JSC::CallFrame* callFrame)) -{ - auto* thisObject = jsDynamicCast(callFrame->thisValue()); - if (!thisObject) [[unlikely]] { - return JSValue::encode(JSC::jsNumber(0)); - } - if (thisObject->isClosed() || thisObject->ended) { - return JSValue::encode(JSC::jsNumber(0)); - } - - return us_socket_buffered_js_write(thisObject->socket, thisObject->is_ssl, thisObject->ended, &thisObject->streamBuffer, globalObject, JSValue::encode(callFrame->argument(0)), JSValue::encode(callFrame->argument(1))); -} - -JSC_DEFINE_HOST_FUNCTION(jsFunctionNodeHTTPServerSocketEnd, (JSC::JSGlobalObject * globalObject, JSC::CallFrame* callFrame)) -{ - auto* thisObject = jsDynamicCast(callFrame->thisValue()); - if (!thisObject) [[unlikely]] { - return JSValue::encode(JSC::jsUndefined()); - } - if (thisObject->isClosed()) { - return JSValue::encode(JSC::jsUndefined()); - } - - thisObject->ended = true; - auto bufferedSize = thisObject->streamBuffer.bufferedSize(); - if (bufferedSize == 0) { - return us_socket_buffered_js_write(thisObject->socket, thisObject->is_ssl, thisObject->ended, &thisObject->streamBuffer, globalObject, JSValue::encode(JSC::jsUndefined()), JSValue::encode(JSC::jsUndefined())); - } - return JSValue::encode(JSC::jsUndefined()); -} - -JSC_DEFINE_CUSTOM_GETTER(jsNodeHttpServerSocketGetterIsSecureEstablished, (JSC::JSGlobalObject * globalObject, JSC::EncodedJSValue thisValue, JSC::PropertyName)) -{ - auto* thisObject = jsCast(JSC::JSValue::decode(thisValue)); - return JSValue::encode(JSC::jsBoolean(thisObject->isAuthorized())); -} -JSC_DEFINE_CUSTOM_GETTER(jsNodeHttpServerSocketGetterDuplex, (JSC::JSGlobalObject * globalObject, JSC::EncodedJSValue thisValue, JSC::PropertyName)) -{ - auto* thisObject = jsCast(JSC::JSValue::decode(thisValue)); - if (thisObject->m_duplex) { - return JSValue::encode(thisObject->m_duplex.get()); - } - return JSValue::encode(JSC::jsNull()); -} - -JSC_DEFINE_CUSTOM_SETTER(jsNodeHttpServerSocketSetterDuplex, (JSC::JSGlobalObject * globalObject, JSC::EncodedJSValue thisValue, JSC::EncodedJSValue encodedValue, JSC::PropertyName propertyName)) -{ - auto& vm = globalObject->vm(); - auto* thisObject = jsCast(JSC::JSValue::decode(thisValue)); - JSValue value = JSC::JSValue::decode(encodedValue); - if (auto* object = value.getObject()) { - thisObject->m_duplex.set(vm, thisObject, object); - - } else { - thisObject->m_duplex.clear(); - } - - return true; -} - -JSC_DEFINE_CUSTOM_GETTER(jsNodeHttpServerSocketGetterRemoteAddress, (JSC::JSGlobalObject * globalObject, JSC::EncodedJSValue thisValue, JSC::PropertyName)) -{ - auto& vm = globalObject->vm(); - auto* thisObject = jsCast(JSC::JSValue::decode(thisValue)); - if (thisObject->m_remoteAddress) { - return JSValue::encode(thisObject->m_remoteAddress.get()); - } - - us_socket_t* socket = thisObject->socket; - if (!socket) { - return JSValue::encode(JSC::jsNull()); - } - - const char* address = nullptr; - int port = 0; - bool is_ipv6 = false; - - uws_res_get_remote_address_info(socket, &address, &port, &is_ipv6); - - if (address == nullptr) { - return JSValue::encode(JSC::jsNull()); - } - - auto addressString = WTF::String::fromUTF8(address); - if (addressString.isEmpty()) { - return JSValue::encode(JSC::jsNull()); - } - - auto* object = JSSocketAddressDTO::create(defaultGlobalObject(globalObject), jsString(vm, addressString), port, is_ipv6); - thisObject->m_remoteAddress.set(vm, thisObject, object); - return JSValue::encode(object); -} - -JSC_DEFINE_CUSTOM_GETTER(jsNodeHttpServerSocketGetterLocalAddress, (JSC::JSGlobalObject * globalObject, JSC::EncodedJSValue thisValue, JSC::PropertyName)) -{ - auto& vm = globalObject->vm(); - auto* thisObject = jsCast(JSC::JSValue::decode(thisValue)); - if (thisObject->m_localAddress) { - return JSValue::encode(thisObject->m_localAddress.get()); - } - - us_socket_t* socket = thisObject->socket; - if (!socket) { - return JSValue::encode(JSC::jsNull()); - } - - const char* address = nullptr; - int port = 0; - bool is_ipv6 = false; - - uws_res_get_local_address_info(socket, &address, &port, &is_ipv6); - - if (address == nullptr) { - return JSValue::encode(JSC::jsNull()); - } - - auto addressString = WTF::String::fromUTF8(address); - if (addressString.isEmpty()) { - return JSValue::encode(JSC::jsNull()); - } - - auto* object = JSSocketAddressDTO::create(defaultGlobalObject(globalObject), jsString(vm, addressString), port, is_ipv6); - thisObject->m_localAddress.set(vm, thisObject, object); - return JSValue::encode(object); -} - -JSC_DEFINE_CUSTOM_GETTER(jsNodeHttpServerSocketGetterOnClose, (JSC::JSGlobalObject * globalObject, JSC::EncodedJSValue thisValue, JSC::PropertyName)) -{ - auto* thisObject = jsCast(JSC::JSValue::decode(thisValue)); - - if (thisObject->functionToCallOnClose) { - return JSValue::encode(thisObject->functionToCallOnClose.get()); - } - - return JSValue::encode(JSC::jsUndefined()); -} - -JSC_DEFINE_CUSTOM_GETTER(jsNodeHttpServerSocketGetterOnDrain, (JSC::JSGlobalObject * globalObject, JSC::EncodedJSValue thisValue, JSC::PropertyName)) -{ - auto* thisObject = jsCast(JSC::JSValue::decode(thisValue)); - - if (thisObject->functionToCallOnDrain) { - return JSValue::encode(thisObject->functionToCallOnDrain.get()); - } - - return JSValue::encode(JSC::jsUndefined()); -} -JSC_DEFINE_CUSTOM_SETTER(jsNodeHttpServerSocketSetterOnDrain, (JSC::JSGlobalObject * globalObject, JSC::EncodedJSValue thisValue, JSC::EncodedJSValue encodedValue, JSC::PropertyName propertyName)) -{ - auto& vm = globalObject->vm(); - auto scope = DECLARE_THROW_SCOPE(vm); - - auto* thisObject = jsCast(JSC::JSValue::decode(thisValue)); - JSValue value = JSC::JSValue::decode(encodedValue); - - if (value.isUndefined() || value.isNull()) { - thisObject->functionToCallOnDrain.clear(); - return true; - } - - if (!value.isCallable()) { - return false; - } - - thisObject->functionToCallOnDrain.set(vm, thisObject, value.getObject()); - return true; -} -JSC_DEFINE_CUSTOM_GETTER(jsNodeHttpServerSocketGetterOnData, (JSC::JSGlobalObject * globalObject, JSC::EncodedJSValue thisValue, JSC::PropertyName)) -{ - auto* thisObject = jsCast(JSC::JSValue::decode(thisValue)); - - if (thisObject->functionToCallOnData) { - return JSValue::encode(thisObject->functionToCallOnData.get()); - } - - return JSValue::encode(JSC::jsUndefined()); -} -JSC_DEFINE_CUSTOM_SETTER(jsNodeHttpServerSocketSetterOnData, (JSC::JSGlobalObject * globalObject, JSC::EncodedJSValue thisValue, JSC::EncodedJSValue encodedValue, JSC::PropertyName propertyName)) -{ - auto& vm = globalObject->vm(); - auto scope = DECLARE_THROW_SCOPE(vm); - - auto* thisObject = jsCast(JSC::JSValue::decode(thisValue)); - JSValue value = JSC::JSValue::decode(encodedValue); - - if (value.isUndefined() || value.isNull()) { - thisObject->functionToCallOnData.clear(); - return true; - } - - if (!value.isCallable()) { - return false; - } - - thisObject->functionToCallOnData.set(vm, thisObject, value.getObject()); - return true; -} -JSC_DEFINE_CUSTOM_SETTER(jsNodeHttpServerSocketSetterOnClose, (JSC::JSGlobalObject * globalObject, JSC::EncodedJSValue thisValue, JSC::EncodedJSValue encodedValue, JSC::PropertyName propertyName)) -{ - auto& vm = globalObject->vm(); - auto scope = DECLARE_THROW_SCOPE(vm); - - auto* thisObject = jsCast(JSC::JSValue::decode(thisValue)); - JSValue value = JSC::JSValue::decode(encodedValue); - - if (value.isUndefined() || value.isNull()) { - thisObject->functionToCallOnClose.clear(); - return true; - } - - if (!value.isCallable()) { - return false; - } - - thisObject->functionToCallOnClose.set(vm, thisObject, value.getObject()); - return true; -} - -JSC_DEFINE_CUSTOM_GETTER(jsNodeHttpServerSocketGetterClosed, (JSGlobalObject * globalObject, JSC::EncodedJSValue thisValue, PropertyName propertyName)) -{ - auto* thisObject = jsCast(JSC::JSValue::decode(thisValue)); - return JSValue::encode(JSC::jsBoolean(thisObject->isClosed())); -} - -JSC_DEFINE_CUSTOM_GETTER(jsNodeHttpServerSocketGetterBytesWritten, (JSGlobalObject * globalObject, JSC::EncodedJSValue thisValue, PropertyName propertyName)) -{ - auto* thisObject = jsCast(JSC::JSValue::decode(thisValue)); - return JSValue::encode(JSC::jsNumber(thisObject->streamBuffer.totalBytesWritten())); -} - -JSC_DEFINE_CUSTOM_GETTER(jsNodeHttpServerSocketGetterResponse, (JSGlobalObject * globalObject, JSC::EncodedJSValue thisValue, PropertyName propertyName)) -{ - auto* thisObject = jsCast(JSC::JSValue::decode(thisValue)); - if (!thisObject->currentResponseObject) { - return JSValue::encode(JSC::jsNull()); - } - - return JSValue::encode(thisObject->currentResponseObject.get()); -} - -template -void JSNodeHTTPServerSocket::visitChildrenImpl(JSCell* cell, Visitor& visitor) -{ - JSNodeHTTPServerSocket* fn = jsCast(cell); - ASSERT_GC_OBJECT_INHERITS(fn, info()); - Base::visitChildren(fn, visitor); - - visitor.append(fn->currentResponseObject); - visitor.append(fn->functionToCallOnClose); - visitor.append(fn->functionToCallOnDrain); - visitor.append(fn->functionToCallOnData); - visitor.append(fn->m_remoteAddress); - visitor.append(fn->m_localAddress); - visitor.append(fn->m_duplex); -} - -DEFINE_VISIT_CHILDREN(JSNodeHTTPServerSocket); - -template -static JSNodeHTTPServerSocket* getNodeHTTPServerSocket(us_socket_t* socket) -{ - auto* httpResponseData = (uWS::HttpResponseData*)us_socket_ext(SSL, socket); - return reinterpret_cast(httpResponseData->socketData); -} - -template -static WebCore::JSNodeHTTPResponse* getNodeHTTPResponse(us_socket_t* socket) -{ - auto* serverSocket = getNodeHTTPServerSocket(socket); - if (!serverSocket) { - return nullptr; - } - return serverSocket->currentResponseObject.get(); -} - -const JSC::ClassInfo JSNodeHTTPServerSocket::s_info = { "NodeHTTPServerSocket"_s, &Base::s_info, nullptr, nullptr, - CREATE_METHOD_TABLE(JSNodeHTTPServerSocket) }; - -const JSC::ClassInfo JSNodeHTTPServerSocketPrototype::s_info = { "NodeHTTPServerSocket"_s, &Base::s_info, nullptr, nullptr, - CREATE_METHOD_TABLE(JSNodeHTTPServerSocketPrototype) }; - -template -static void* getNodeHTTPResponsePtr(us_socket_t* socket) -{ - WebCore::JSNodeHTTPResponse* responseObject = getNodeHTTPResponse(socket); - if (!responseObject) { - return nullptr; - } - return responseObject->wrapped(); -} - -extern "C" EncodedJSValue Bun__getNodeHTTPResponseThisValue(bool is_ssl, us_socket_t* socket) -{ - if (is_ssl) { - return JSValue::encode(getNodeHTTPResponse(socket)); - } - return JSValue::encode(getNodeHTTPResponse(socket)); -} - -extern "C" EncodedJSValue Bun__getNodeHTTPServerSocketThisValue(bool is_ssl, us_socket_t* socket) -{ - if (is_ssl) { - return JSValue::encode(getNodeHTTPServerSocket(socket)); - } - return JSValue::encode(getNodeHTTPServerSocket(socket)); -} - -extern "C" void Bun__setNodeHTTPServerSocketUsSocketValue(EncodedJSValue thisValue, us_socket_t* socket) -{ - auto* response = jsCast(JSValue::decode(thisValue)); - response->socket = socket; -} - -extern "C" JSC::EncodedJSValue Bun__createNodeHTTPServerSocketForClientError(bool isSSL, us_socket_t* us_socket, Zig::GlobalObject* globalObject) -{ - auto& vm = globalObject->vm(); - auto scope = DECLARE_THROW_SCOPE(vm); - - RETURN_IF_EXCEPTION(scope, {}); - - if (isSSL) { - uWS::HttpResponse* response = reinterpret_cast*>(us_socket); - auto* currentSocketDataPtr = reinterpret_cast(response->getHttpResponseData()->socketData); - if (currentSocketDataPtr) { - return JSValue::encode(currentSocketDataPtr); - } - } else { - uWS::HttpResponse* response = reinterpret_cast*>(us_socket); - auto* currentSocketDataPtr = reinterpret_cast(response->getHttpResponseData()->socketData); - if (currentSocketDataPtr) { - return JSValue::encode(currentSocketDataPtr); - } - } - // socket without response because is not valid http - JSNodeHTTPServerSocket* socket = JSNodeHTTPServerSocket::create( - vm, - globalObject->m_JSNodeHTTPServerSocketStructure.getInitializedOnMainThread(globalObject), - us_socket, - isSSL, nullptr); - if (isSSL) { - uWS::HttpResponse* response = reinterpret_cast*>(us_socket); - response->getHttpResponseData()->socketData = socket; - } else { - uWS::HttpResponse* response = reinterpret_cast*>(us_socket); - response->getHttpResponseData()->socketData = socket; - } - RETURN_IF_EXCEPTION(scope, {}); - if (socket) { - socket->strongThis.set(vm, socket); - return JSValue::encode(socket); - } - - return JSValue::encode(JSC::jsNull()); -} - BUN_DECLARE_HOST_FUNCTION(jsFunctionRequestOrResponseHasBodyValue); BUN_DECLARE_HOST_FUNCTION(jsFunctionGetCompleteRequestOrResponseBodyValueAsArrayBuffer); extern "C" uWS::HttpRequest* Request__getUWSRequest(void*); @@ -1769,9 +1013,4 @@ extern "C" void WebCore__FetchHeaders__toUWSResponse(WebCore::FetchHeaders* arg0 } } -JSC::Structure* createNodeHTTPServerSocketStructure(JSC::VM& vm, JSC::JSGlobalObject* globalObject) -{ - return JSNodeHTTPServerSocket::createStructure(vm, globalObject); -} - } // namespace Bun diff --git a/src/bun.js/bindings/node/JSNodeHTTPServerSocket.cpp b/src/bun.js/bindings/node/JSNodeHTTPServerSocket.cpp new file mode 100644 index 0000000000..9006ddd1c0 --- /dev/null +++ b/src/bun.js/bindings/node/JSNodeHTTPServerSocket.cpp @@ -0,0 +1,375 @@ +#include "JSNodeHTTPServerSocket.h" +#include "JSNodeHTTPServerSocketPrototype.h" +#include "ZigGlobalObject.h" +#include "ZigGeneratedClasses.h" +#include "DOMIsoSubspaces.h" +#include "ScriptExecutionContext.h" +#include "helpers.h" +#include "JSSocketAddressDTO.h" +#include +#include +#include +#include + +extern "C" void Bun__NodeHTTPResponse_setClosed(void* zigResponse); +extern "C" void Bun__NodeHTTPResponse_onClose(void* zigResponse, JSC::EncodedJSValue jsValue); +extern "C" void us_socket_free_stream_buffer(us_socket_stream_buffer_t* streamBuffer); +extern "C" uint64_t uws_res_get_remote_address_info(void* res, const char** dest, int* port, bool* is_ipv6); +extern "C" uint64_t uws_res_get_local_address_info(void* res, const char** dest, int* port, bool* is_ipv6); +extern "C" EncodedJSValue us_socket_buffered_js_write(void* socket, bool is_ssl, bool ended, us_socket_stream_buffer_t* streamBuffer, JSC::JSGlobalObject* globalObject, JSC::EncodedJSValue data, JSC::EncodedJSValue encoding); + +namespace Bun { + +using namespace JSC; +using namespace WebCore; + +const JSC::ClassInfo JSNodeHTTPServerSocket::s_info = { "NodeHTTPServerSocket"_s, &Base::s_info, nullptr, nullptr, + CREATE_METHOD_TABLE(JSNodeHTTPServerSocket) }; + +JSNodeHTTPServerSocket* JSNodeHTTPServerSocket::create(JSC::VM& vm, JSC::Structure* structure, us_socket_t* socket, bool is_ssl, WebCore::JSNodeHTTPResponse* response) +{ + auto* object = new (JSC::allocateCell(vm)) JSNodeHTTPServerSocket(vm, structure, socket, is_ssl, response); + object->finishCreation(vm); + return object; +} + +JSNodeHTTPServerSocket* JSNodeHTTPServerSocket::create(JSC::VM& vm, Zig::GlobalObject* globalObject, us_socket_t* socket, bool is_ssl, WebCore::JSNodeHTTPResponse* response) +{ + auto* structure = globalObject->m_JSNodeHTTPServerSocketStructure.getInitializedOnMainThread(globalObject); + return create(vm, structure, socket, is_ssl, response); +} + +template +void JSNodeHTTPServerSocket::clearSocketData(bool upgraded, us_socket_t* socket) +{ + if (upgraded) { + auto* webSocket = (uWS::WebSocketData*)us_socket_ext(SSL, socket); + webSocket->socketData = nullptr; + } else { + auto* httpResponseData = (uWS::HttpResponseData*)us_socket_ext(SSL, socket); + httpResponseData->socketData = nullptr; + } +} + +void JSNodeHTTPServerSocket::close() +{ + if (socket) { + us_socket_close(is_ssl, socket, 0, nullptr); + } +} + +bool JSNodeHTTPServerSocket::isClosed() const +{ + return !socket || us_socket_is_closed(is_ssl, socket); +} + +bool JSNodeHTTPServerSocket::isAuthorized() const +{ + // is secure means that tls was established successfully + if (!is_ssl || !socket) + return false; + auto* context = us_socket_context(is_ssl, socket); + if (!context) + return false; + auto* data = (uWS::HttpContextData*)us_socket_context_ext(is_ssl, context); + if (!data) + return false; + return data->flags.isAuthorized; +} + +JSNodeHTTPServerSocket::~JSNodeHTTPServerSocket() +{ + if (socket) { + if (is_ssl) { + clearSocketData(this->upgraded, socket); + } else { + clearSocketData(this->upgraded, socket); + } + } + us_socket_free_stream_buffer(&streamBuffer); +} + +JSNodeHTTPServerSocket::JSNodeHTTPServerSocket(JSC::VM& vm, JSC::Structure* structure, us_socket_t* socket, bool is_ssl, WebCore::JSNodeHTTPResponse* response) + : JSC::JSDestructibleObject(vm, structure) + , socket(socket) + , is_ssl(is_ssl) +{ + currentResponseObject.setEarlyValue(vm, this, response); +} + +void JSNodeHTTPServerSocket::detach() +{ + this->m_duplex.clear(); + this->currentResponseObject.clear(); + this->strongThis.clear(); +} + +void JSNodeHTTPServerSocket::onClose() +{ + this->socket = nullptr; + if (auto* res = this->currentResponseObject.get(); res != nullptr && res->m_ctx != nullptr) { + Bun__NodeHTTPResponse_setClosed(res->m_ctx); + } + + // This function can be called during GC! + Zig::GlobalObject* globalObject = static_cast(this->globalObject()); + if (!functionToCallOnClose) { + if (auto* res = this->currentResponseObject.get(); res != nullptr && res->m_ctx != nullptr) { + Bun__NodeHTTPResponse_onClose(res->m_ctx, JSValue::encode(res)); + } + this->detach(); + return; + } + + WebCore::ScriptExecutionContext* scriptExecutionContext = globalObject->scriptExecutionContext(); + + if (scriptExecutionContext) { + scriptExecutionContext->postTask([self = this](ScriptExecutionContext& context) { + WTF::NakedPtr exception; + auto* globalObject = defaultGlobalObject(context.globalObject()); + auto* thisObject = self; + auto* callbackObject = thisObject->functionToCallOnClose.get(); + if (!callbackObject) { + if (auto* res = thisObject->currentResponseObject.get(); res != nullptr && res->m_ctx != nullptr) { + Bun__NodeHTTPResponse_onClose(res->m_ctx, JSValue::encode(res)); + } + thisObject->detach(); + return; + } + auto callData = JSC::getCallData(callbackObject); + MarkedArgumentBuffer args; + EnsureStillAliveScope ensureStillAlive(self); + + if (globalObject->scriptExecutionStatus(globalObject, thisObject) == ScriptExecutionStatus::Running) { + if (auto* res = thisObject->currentResponseObject.get(); res != nullptr && res->m_ctx != nullptr) { + Bun__NodeHTTPResponse_onClose(res->m_ctx, JSValue::encode(res)); + } + + profiledCall(globalObject, JSC::ProfilingReason::API, callbackObject, callData, thisObject, args, exception); + + if (auto* ptr = exception.get()) { + exception.clear(); + globalObject->reportUncaughtExceptionAtEventLoop(globalObject, ptr); + } + } + thisObject->detach(); + }); + } +} + +void JSNodeHTTPServerSocket::onDrain() +{ + // This function can be called during GC! + Zig::GlobalObject* globalObject = static_cast(this->globalObject()); + if (!functionToCallOnDrain) { + return; + } + + auto bufferedSize = this->streamBuffer.bufferedSize(); + if (bufferedSize > 0) { + auto* globalObject = defaultGlobalObject(this->globalObject()); + auto scope = DECLARE_CATCH_SCOPE(globalObject->vm()); + us_socket_buffered_js_write(this->socket, this->is_ssl, this->ended, &this->streamBuffer, globalObject, JSValue::encode(JSC::jsUndefined()), JSValue::encode(JSC::jsUndefined())); + if (scope.exception()) { + globalObject->reportUncaughtExceptionAtEventLoop(globalObject, scope.exception()); + return; + } + bufferedSize = this->streamBuffer.bufferedSize(); + + if (bufferedSize > 0) { + // need to drain more + return; + } + } + WebCore::ScriptExecutionContext* scriptExecutionContext = globalObject->scriptExecutionContext(); + + if (scriptExecutionContext) { + scriptExecutionContext->postTask([self = this](ScriptExecutionContext& context) { + WTF::NakedPtr exception; + auto* globalObject = defaultGlobalObject(context.globalObject()); + auto* thisObject = self; + auto* callbackObject = thisObject->functionToCallOnDrain.get(); + if (!callbackObject) { + return; + } + auto callData = JSC::getCallData(callbackObject); + MarkedArgumentBuffer args; + EnsureStillAliveScope ensureStillAlive(self); + + if (globalObject->scriptExecutionStatus(globalObject, thisObject) == ScriptExecutionStatus::Running) { + profiledCall(globalObject, JSC::ProfilingReason::API, callbackObject, callData, thisObject, args, exception); + + if (auto* ptr = exception.get()) { + exception.clear(); + globalObject->reportUncaughtExceptionAtEventLoop(globalObject, ptr); + } + } + }); + } +} + +void JSNodeHTTPServerSocket::onData(const char* data, int length, bool last) +{ + // This function can be called during GC! + Zig::GlobalObject* globalObject = static_cast(this->globalObject()); + if (!functionToCallOnData) { + return; + } + + WebCore::ScriptExecutionContext* scriptExecutionContext = globalObject->scriptExecutionContext(); + + if (scriptExecutionContext) { + auto scope = DECLARE_CATCH_SCOPE(globalObject->vm()); + JSC::JSUint8Array* buffer = WebCore::createBuffer(globalObject, std::span(reinterpret_cast(data), length)); + auto chunk = JSC::JSValue(buffer); + if (scope.exception()) { + globalObject->reportUncaughtExceptionAtEventLoop(globalObject, scope.exception()); + return; + } + gcProtect(chunk); + scriptExecutionContext->postTask([self = this, chunk = chunk, last = last](ScriptExecutionContext& context) { + WTF::NakedPtr exception; + auto* globalObject = defaultGlobalObject(context.globalObject()); + auto* thisObject = self; + auto* callbackObject = thisObject->functionToCallOnData.get(); + EnsureStillAliveScope ensureChunkStillAlive(chunk); + gcUnprotect(chunk); + if (!callbackObject) { + return; + } + + auto callData = JSC::getCallData(callbackObject); + MarkedArgumentBuffer args; + args.append(chunk); + args.append(JSC::jsBoolean(last)); + EnsureStillAliveScope ensureStillAlive(self); + + if (globalObject->scriptExecutionStatus(globalObject, thisObject) == ScriptExecutionStatus::Running) { + profiledCall(globalObject, JSC::ProfilingReason::API, callbackObject, callData, thisObject, args, exception); + + if (auto* ptr = exception.get()) { + exception.clear(); + globalObject->reportUncaughtExceptionAtEventLoop(globalObject, ptr); + } + } + }); + } +} + +JSC::Structure* JSNodeHTTPServerSocket::createStructure(JSC::VM& vm, JSC::JSGlobalObject* globalObject) +{ + auto* structure = JSC::Structure::create(vm, globalObject, globalObject->objectPrototype(), JSC::TypeInfo(JSC::ObjectType, StructureFlags), JSNodeHTTPServerSocketPrototype::info()); + auto* prototype = JSNodeHTTPServerSocketPrototype::create(vm, structure); + return JSC::Structure::create(vm, globalObject, prototype, JSC::TypeInfo(JSC::ObjectType, StructureFlags), info()); +} + +void JSNodeHTTPServerSocket::finishCreation(JSC::VM& vm) +{ + Base::finishCreation(vm); +} + +template +void JSNodeHTTPServerSocket::visitChildrenImpl(JSCell* cell, Visitor& visitor) +{ + JSNodeHTTPServerSocket* fn = jsCast(cell); + ASSERT_GC_OBJECT_INHERITS(fn, info()); + Base::visitChildren(fn, visitor); + + visitor.append(fn->currentResponseObject); + visitor.append(fn->functionToCallOnClose); + visitor.append(fn->functionToCallOnDrain); + visitor.append(fn->functionToCallOnData); + visitor.append(fn->m_remoteAddress); + visitor.append(fn->m_localAddress); + visitor.append(fn->m_duplex); +} + +DEFINE_VISIT_CHILDREN(JSNodeHTTPServerSocket); + +template +static JSNodeHTTPServerSocket* getNodeHTTPServerSocket(us_socket_t* socket) +{ + auto* httpResponseData = (uWS::HttpResponseData*)us_socket_ext(SSL, socket); + return reinterpret_cast(httpResponseData->socketData); +} + +template +static WebCore::JSNodeHTTPResponse* getNodeHTTPResponse(us_socket_t* socket) +{ + auto* serverSocket = getNodeHTTPServerSocket(socket); + if (!serverSocket) { + return nullptr; + } + return serverSocket->currentResponseObject.get(); +} + +extern "C" JSC::EncodedJSValue Bun__getNodeHTTPResponseThisValue(bool is_ssl, us_socket_t* socket) +{ + if (is_ssl) { + return JSValue::encode(getNodeHTTPResponse(socket)); + } + return JSValue::encode(getNodeHTTPResponse(socket)); +} + +extern "C" JSC::EncodedJSValue Bun__getNodeHTTPServerSocketThisValue(bool is_ssl, us_socket_t* socket) +{ + if (is_ssl) { + return JSValue::encode(getNodeHTTPServerSocket(socket)); + } + return JSValue::encode(getNodeHTTPServerSocket(socket)); +} + +extern "C" void Bun__setNodeHTTPServerSocketUsSocketValue(JSC::EncodedJSValue thisValue, us_socket_t* socket) +{ + auto* response = jsCast(JSValue::decode(thisValue)); + response->socket = socket; +} + +extern "C" JSC::EncodedJSValue Bun__createNodeHTTPServerSocketForClientError(bool isSSL, us_socket_t* us_socket, Zig::GlobalObject* globalObject) +{ + auto& vm = globalObject->vm(); + auto scope = DECLARE_THROW_SCOPE(vm); + + RETURN_IF_EXCEPTION(scope, {}); + + if (isSSL) { + uWS::HttpResponse* response = reinterpret_cast*>(us_socket); + auto* currentSocketDataPtr = reinterpret_cast(response->getHttpResponseData()->socketData); + if (currentSocketDataPtr) { + return JSValue::encode(currentSocketDataPtr); + } + } else { + uWS::HttpResponse* response = reinterpret_cast*>(us_socket); + auto* currentSocketDataPtr = reinterpret_cast(response->getHttpResponseData()->socketData); + if (currentSocketDataPtr) { + return JSValue::encode(currentSocketDataPtr); + } + } + // socket without response because is not valid http + JSNodeHTTPServerSocket* socket = JSNodeHTTPServerSocket::create( + vm, + globalObject->m_JSNodeHTTPServerSocketStructure.getInitializedOnMainThread(globalObject), + us_socket, + isSSL, nullptr); + if (isSSL) { + uWS::HttpResponse* response = reinterpret_cast*>(us_socket); + response->getHttpResponseData()->socketData = socket; + } else { + uWS::HttpResponse* response = reinterpret_cast*>(us_socket); + response->getHttpResponseData()->socketData = socket; + } + RETURN_IF_EXCEPTION(scope, {}); + if (socket) { + socket->strongThis.set(vm, socket); + return JSValue::encode(socket); + } + + return JSValue::encode(JSC::jsNull()); +} + +JSC::Structure* createNodeHTTPServerSocketStructure(JSC::VM& vm, JSC::JSGlobalObject* globalObject) +{ + return JSNodeHTTPServerSocket::createStructure(vm, globalObject); +} + +} // namespace Bun diff --git a/src/bun.js/bindings/node/JSNodeHTTPServerSocket.h b/src/bun.js/bindings/node/JSNodeHTTPServerSocket.h new file mode 100644 index 0000000000..111a27ac1d --- /dev/null +++ b/src/bun.js/bindings/node/JSNodeHTTPServerSocket.h @@ -0,0 +1,108 @@ +#pragma once + +#include "root.h" +#include +#include +#include "BunClientData.h" + +extern "C" { +struct us_socket_stream_buffer_t { + char* list_ptr = nullptr; + size_t list_cap = 0; + size_t listLen = 0; + size_t total_bytes_written = 0; + size_t cursor = 0; + + size_t bufferedSize() const + { + return listLen - cursor; + } + size_t totalBytesWritten() const + { + return total_bytes_written; + } +}; + +struct us_socket_t; +} + +namespace uWS { +template +struct HttpResponseData; +struct WebSocketData; +} + +namespace WebCore { +class JSNodeHTTPResponse; +} + +namespace Bun { + +class JSNodeHTTPServerSocketPrototype; + +class JSNodeHTTPServerSocket : public JSC::JSDestructibleObject { +public: + using Base = JSC::JSDestructibleObject; + static constexpr unsigned StructureFlags = Base::StructureFlags; + + us_socket_stream_buffer_t streamBuffer = {}; + us_socket_t* socket = nullptr; + unsigned is_ssl : 1 = 0; + unsigned ended : 1 = 0; + unsigned upgraded : 1 = 0; + JSC::Strong strongThis = {}; + + static JSNodeHTTPServerSocket* create(JSC::VM& vm, JSC::Structure* structure, us_socket_t* socket, bool is_ssl, WebCore::JSNodeHTTPResponse* response); + static JSNodeHTTPServerSocket* create(JSC::VM& vm, Zig::GlobalObject* globalObject, us_socket_t* socket, bool is_ssl, WebCore::JSNodeHTTPResponse* response); + + static void destroy(JSC::JSCell* cell) + { + static_cast(cell)->JSNodeHTTPServerSocket::~JSNodeHTTPServerSocket(); + } + + template + static void clearSocketData(bool upgraded, us_socket_t* socket); + + void close(); + bool isClosed() const; + bool isAuthorized() const; + + ~JSNodeHTTPServerSocket(); + + JSNodeHTTPServerSocket(JSC::VM& vm, JSC::Structure* structure, us_socket_t* socket, bool is_ssl, WebCore::JSNodeHTTPResponse* response); + + mutable JSC::WriteBarrier functionToCallOnClose; + mutable JSC::WriteBarrier functionToCallOnDrain; + mutable JSC::WriteBarrier functionToCallOnData; + mutable JSC::WriteBarrier currentResponseObject; + mutable JSC::WriteBarrier m_remoteAddress; + mutable JSC::WriteBarrier m_localAddress; + mutable JSC::WriteBarrier m_duplex; + + DECLARE_INFO; + DECLARE_VISIT_CHILDREN; + + template + static JSC::GCClient::IsoSubspace* subspaceFor(JSC::VM& vm) + { + if constexpr (mode == JSC::SubspaceAccess::Concurrently) + return nullptr; + + return WebCore::subspaceForImpl( + vm, + [](auto& spaces) { return spaces.m_clientSubspaceForJSNodeHTTPServerSocket.get(); }, + [](auto& spaces, auto&& space) { spaces.m_clientSubspaceForJSNodeHTTPServerSocket = std::forward(space); }, + [](auto& spaces) { return spaces.m_subspaceForJSNodeHTTPServerSocket.get(); }, + [](auto& spaces, auto&& space) { spaces.m_subspaceForJSNodeHTTPServerSocket = std::forward(space); }); + } + + void detach(); + void onClose(); + void onDrain(); + void onData(const char* data, int length, bool last); + + static JSC::Structure* createStructure(JSC::VM& vm, JSC::JSGlobalObject* globalObject); + void finishCreation(JSC::VM& vm); +}; + +} // namespace Bun diff --git a/src/bun.js/bindings/node/JSNodeHTTPServerSocketPrototype.cpp b/src/bun.js/bindings/node/JSNodeHTTPServerSocketPrototype.cpp new file mode 100644 index 0000000000..4695bb909c --- /dev/null +++ b/src/bun.js/bindings/node/JSNodeHTTPServerSocketPrototype.cpp @@ -0,0 +1,330 @@ +#include "JSNodeHTTPServerSocketPrototype.h" +#include "JSNodeHTTPServerSocket.h" +#include "JSSocketAddressDTO.h" +#include "ZigGlobalObject.h" +#include "ZigGeneratedClasses.h" +#include "helpers.h" +#include +#include + +extern "C" EncodedJSValue us_socket_buffered_js_write(void* socket, bool is_ssl, bool ended, us_socket_stream_buffer_t* streamBuffer, JSC::JSGlobalObject* globalObject, JSC::EncodedJSValue data, JSC::EncodedJSValue encoding); +extern "C" uint64_t uws_res_get_remote_address_info(void* res, const char** dest, int* port, bool* is_ipv6); +extern "C" uint64_t uws_res_get_local_address_info(void* res, const char** dest, int* port, bool* is_ipv6); + +namespace Bun { + +using namespace JSC; +using namespace WebCore; + +// Declare custom getters/setters and host functions +JSC_DECLARE_CUSTOM_GETTER(jsNodeHttpServerSocketGetterOnClose); +JSC_DECLARE_CUSTOM_GETTER(jsNodeHttpServerSocketGetterOnDrain); +JSC_DECLARE_CUSTOM_GETTER(jsNodeHttpServerSocketGetterClosed); +JSC_DECLARE_CUSTOM_SETTER(jsNodeHttpServerSocketSetterOnClose); +JSC_DECLARE_CUSTOM_SETTER(jsNodeHttpServerSocketSetterOnDrain); +JSC_DECLARE_CUSTOM_SETTER(jsNodeHttpServerSocketSetterOnData); +JSC_DECLARE_CUSTOM_GETTER(jsNodeHttpServerSocketGetterOnData); +JSC_DECLARE_CUSTOM_GETTER(jsNodeHttpServerSocketGetterBytesWritten); +JSC_DECLARE_HOST_FUNCTION(jsFunctionNodeHTTPServerSocketClose); +JSC_DECLARE_HOST_FUNCTION(jsFunctionNodeHTTPServerSocketWrite); +JSC_DECLARE_HOST_FUNCTION(jsFunctionNodeHTTPServerSocketEnd); +JSC_DECLARE_CUSTOM_GETTER(jsNodeHttpServerSocketGetterResponse); +JSC_DECLARE_CUSTOM_GETTER(jsNodeHttpServerSocketGetterRemoteAddress); +JSC_DECLARE_CUSTOM_GETTER(jsNodeHttpServerSocketGetterLocalAddress); +JSC_DECLARE_CUSTOM_GETTER(jsNodeHttpServerSocketGetterDuplex); +JSC_DECLARE_CUSTOM_SETTER(jsNodeHttpServerSocketSetterDuplex); +JSC_DECLARE_CUSTOM_GETTER(jsNodeHttpServerSocketGetterIsSecureEstablished); + +JSC_DEFINE_CUSTOM_SETTER(noOpSetter, (JSC::JSGlobalObject * globalObject, JSC::EncodedJSValue thisValue, JSC::EncodedJSValue value, JSC::PropertyName propertyName)) +{ + return false; +} + +const JSC::ClassInfo JSNodeHTTPServerSocketPrototype::s_info = { "NodeHTTPServerSocket"_s, &Base::s_info, nullptr, nullptr, + CREATE_METHOD_TABLE(JSNodeHTTPServerSocketPrototype) }; + +static const JSC::HashTableValue JSNodeHTTPServerSocketPrototypeTableValues[] = { + { "onclose"_s, static_cast(JSC::PropertyAttribute::CustomAccessor), JSC::NoIntrinsic, { JSC::HashTableValue::GetterSetterType, jsNodeHttpServerSocketGetterOnClose, jsNodeHttpServerSocketSetterOnClose } }, + { "ondrain"_s, static_cast(JSC::PropertyAttribute::CustomAccessor), JSC::NoIntrinsic, { JSC::HashTableValue::GetterSetterType, jsNodeHttpServerSocketGetterOnDrain, jsNodeHttpServerSocketSetterOnDrain } }, + { "ondata"_s, static_cast(JSC::PropertyAttribute::CustomAccessor), JSC::NoIntrinsic, { JSC::HashTableValue::GetterSetterType, jsNodeHttpServerSocketGetterOnData, jsNodeHttpServerSocketSetterOnData } }, + { "bytesWritten"_s, static_cast(JSC::PropertyAttribute::CustomAccessor), JSC::NoIntrinsic, { JSC::HashTableValue::GetterSetterType, jsNodeHttpServerSocketGetterBytesWritten, noOpSetter } }, + { "closed"_s, static_cast(JSC::PropertyAttribute::CustomAccessor | JSC::PropertyAttribute::ReadOnly), JSC::NoIntrinsic, { JSC::HashTableValue::GetterSetterType, jsNodeHttpServerSocketGetterClosed, noOpSetter } }, + { "response"_s, static_cast(JSC::PropertyAttribute::CustomAccessor | JSC::PropertyAttribute::ReadOnly), JSC::NoIntrinsic, { JSC::HashTableValue::GetterSetterType, jsNodeHttpServerSocketGetterResponse, noOpSetter } }, + { "duplex"_s, static_cast(JSC::PropertyAttribute::CustomAccessor), JSC::NoIntrinsic, { JSC::HashTableValue::GetterSetterType, jsNodeHttpServerSocketGetterDuplex, jsNodeHttpServerSocketSetterDuplex } }, + { "remoteAddress"_s, static_cast(JSC::PropertyAttribute::CustomAccessor | JSC::PropertyAttribute::ReadOnly), JSC::NoIntrinsic, { JSC::HashTableValue::GetterSetterType, jsNodeHttpServerSocketGetterRemoteAddress, noOpSetter } }, + { "localAddress"_s, static_cast(JSC::PropertyAttribute::CustomAccessor | JSC::PropertyAttribute::ReadOnly), JSC::NoIntrinsic, { JSC::HashTableValue::GetterSetterType, jsNodeHttpServerSocketGetterLocalAddress, noOpSetter } }, + { "close"_s, static_cast(JSC::PropertyAttribute::Function | JSC::PropertyAttribute::DontEnum), JSC::NoIntrinsic, { JSC::HashTableValue::NativeFunctionType, jsFunctionNodeHTTPServerSocketClose, 0 } }, + { "write"_s, static_cast(JSC::PropertyAttribute::Function | JSC::PropertyAttribute::DontEnum), JSC::NoIntrinsic, { JSC::HashTableValue::NativeFunctionType, jsFunctionNodeHTTPServerSocketWrite, 2 } }, + { "end"_s, static_cast(JSC::PropertyAttribute::Function | JSC::PropertyAttribute::DontEnum), JSC::NoIntrinsic, { JSC::HashTableValue::NativeFunctionType, jsFunctionNodeHTTPServerSocketEnd, 0 } }, + { "secureEstablished"_s, static_cast(JSC::PropertyAttribute::CustomAccessor | JSC::PropertyAttribute::ReadOnly), JSC::NoIntrinsic, { JSC::HashTableValue::GetterSetterType, jsNodeHttpServerSocketGetterIsSecureEstablished, noOpSetter } }, +}; + +void JSNodeHTTPServerSocketPrototype::finishCreation(JSC::VM& vm) +{ + Base::finishCreation(vm); + ASSERT(inherits(info())); + reifyStaticProperties(vm, info(), JSNodeHTTPServerSocketPrototypeTableValues, *this); + this->structure()->setMayBePrototype(true); +} + +// Implementation of host functions +JSC_DEFINE_HOST_FUNCTION(jsFunctionNodeHTTPServerSocketClose, (JSC::JSGlobalObject * globalObject, JSC::CallFrame* callFrame)) +{ + auto* thisObject = jsDynamicCast(callFrame->thisValue()); + if (!thisObject) [[unlikely]] { + return JSValue::encode(JSC::jsUndefined()); + } + if (thisObject->isClosed()) { + return JSValue::encode(JSC::jsUndefined()); + } + thisObject->close(); + + return JSValue::encode(JSC::jsUndefined()); +} + +JSC_DEFINE_HOST_FUNCTION(jsFunctionNodeHTTPServerSocketWrite, (JSC::JSGlobalObject * globalObject, JSC::CallFrame* callFrame)) +{ + auto* thisObject = jsDynamicCast(callFrame->thisValue()); + if (!thisObject) [[unlikely]] { + return JSValue::encode(JSC::jsNumber(0)); + } + if (thisObject->isClosed() || thisObject->ended) { + return JSValue::encode(JSC::jsNumber(0)); + } + + return us_socket_buffered_js_write(thisObject->socket, thisObject->is_ssl, thisObject->ended, &thisObject->streamBuffer, globalObject, JSValue::encode(callFrame->argument(0)), JSValue::encode(callFrame->argument(1))); +} + +JSC_DEFINE_HOST_FUNCTION(jsFunctionNodeHTTPServerSocketEnd, (JSC::JSGlobalObject * globalObject, JSC::CallFrame* callFrame)) +{ + auto* thisObject = jsDynamicCast(callFrame->thisValue()); + if (!thisObject) [[unlikely]] { + return JSValue::encode(JSC::jsUndefined()); + } + if (thisObject->isClosed()) { + return JSValue::encode(JSC::jsUndefined()); + } + + thisObject->ended = true; + auto bufferedSize = thisObject->streamBuffer.bufferedSize(); + if (bufferedSize == 0) { + return us_socket_buffered_js_write(thisObject->socket, thisObject->is_ssl, thisObject->ended, &thisObject->streamBuffer, globalObject, JSValue::encode(JSC::jsUndefined()), JSValue::encode(JSC::jsUndefined())); + } + return JSValue::encode(JSC::jsUndefined()); +} + +// Implementation of custom getters +JSC_DEFINE_CUSTOM_GETTER(jsNodeHttpServerSocketGetterIsSecureEstablished, (JSC::JSGlobalObject * globalObject, JSC::EncodedJSValue thisValue, JSC::PropertyName)) +{ + auto* thisObject = jsCast(JSC::JSValue::decode(thisValue)); + return JSValue::encode(JSC::jsBoolean(thisObject->isAuthorized())); +} + +JSC_DEFINE_CUSTOM_GETTER(jsNodeHttpServerSocketGetterDuplex, (JSC::JSGlobalObject * globalObject, JSC::EncodedJSValue thisValue, JSC::PropertyName)) +{ + auto* thisObject = jsCast(JSC::JSValue::decode(thisValue)); + if (thisObject->m_duplex) { + return JSValue::encode(thisObject->m_duplex.get()); + } + return JSValue::encode(JSC::jsNull()); +} + +JSC_DEFINE_CUSTOM_SETTER(jsNodeHttpServerSocketSetterDuplex, (JSC::JSGlobalObject * globalObject, JSC::EncodedJSValue thisValue, JSC::EncodedJSValue encodedValue, JSC::PropertyName propertyName)) +{ + auto& vm = globalObject->vm(); + auto* thisObject = jsCast(JSC::JSValue::decode(thisValue)); + JSValue value = JSC::JSValue::decode(encodedValue); + if (auto* object = value.getObject()) { + thisObject->m_duplex.set(vm, thisObject, object); + } else { + thisObject->m_duplex.clear(); + } + + return true; +} + +JSC_DEFINE_CUSTOM_GETTER(jsNodeHttpServerSocketGetterRemoteAddress, (JSC::JSGlobalObject * globalObject, JSC::EncodedJSValue thisValue, JSC::PropertyName)) +{ + auto& vm = globalObject->vm(); + auto* thisObject = jsCast(JSC::JSValue::decode(thisValue)); + if (thisObject->m_remoteAddress) { + return JSValue::encode(thisObject->m_remoteAddress.get()); + } + + us_socket_t* socket = thisObject->socket; + if (!socket) { + return JSValue::encode(JSC::jsNull()); + } + + const char* address = nullptr; + int port = 0; + bool is_ipv6 = false; + + uws_res_get_remote_address_info(socket, &address, &port, &is_ipv6); + + if (address == nullptr) { + return JSValue::encode(JSC::jsNull()); + } + + auto addressString = WTF::String::fromUTF8(address); + if (addressString.isEmpty()) { + return JSValue::encode(JSC::jsNull()); + } + + auto* object = JSSocketAddressDTO::create(defaultGlobalObject(globalObject), jsString(vm, addressString), port, is_ipv6); + thisObject->m_remoteAddress.set(vm, thisObject, object); + return JSValue::encode(object); +} + +JSC_DEFINE_CUSTOM_GETTER(jsNodeHttpServerSocketGetterLocalAddress, (JSC::JSGlobalObject * globalObject, JSC::EncodedJSValue thisValue, JSC::PropertyName)) +{ + auto& vm = globalObject->vm(); + auto* thisObject = jsCast(JSC::JSValue::decode(thisValue)); + if (thisObject->m_localAddress) { + return JSValue::encode(thisObject->m_localAddress.get()); + } + + us_socket_t* socket = thisObject->socket; + if (!socket) { + return JSValue::encode(JSC::jsNull()); + } + + const char* address = nullptr; + int port = 0; + bool is_ipv6 = false; + + uws_res_get_local_address_info(socket, &address, &port, &is_ipv6); + + if (address == nullptr) { + return JSValue::encode(JSC::jsNull()); + } + + auto addressString = WTF::String::fromUTF8(address); + if (addressString.isEmpty()) { + return JSValue::encode(JSC::jsNull()); + } + + auto* object = JSSocketAddressDTO::create(defaultGlobalObject(globalObject), jsString(vm, addressString), port, is_ipv6); + thisObject->m_localAddress.set(vm, thisObject, object); + return JSValue::encode(object); +} + +JSC_DEFINE_CUSTOM_GETTER(jsNodeHttpServerSocketGetterOnClose, (JSC::JSGlobalObject * globalObject, JSC::EncodedJSValue thisValue, JSC::PropertyName)) +{ + auto* thisObject = jsCast(JSC::JSValue::decode(thisValue)); + + if (thisObject->functionToCallOnClose) { + return JSValue::encode(thisObject->functionToCallOnClose.get()); + } + + return JSValue::encode(JSC::jsUndefined()); +} + +JSC_DEFINE_CUSTOM_GETTER(jsNodeHttpServerSocketGetterOnDrain, (JSC::JSGlobalObject * globalObject, JSC::EncodedJSValue thisValue, JSC::PropertyName)) +{ + auto* thisObject = jsCast(JSC::JSValue::decode(thisValue)); + + if (thisObject->functionToCallOnDrain) { + return JSValue::encode(thisObject->functionToCallOnDrain.get()); + } + + return JSValue::encode(JSC::jsUndefined()); +} + +JSC_DEFINE_CUSTOM_SETTER(jsNodeHttpServerSocketSetterOnDrain, (JSC::JSGlobalObject * globalObject, JSC::EncodedJSValue thisValue, JSC::EncodedJSValue encodedValue, JSC::PropertyName propertyName)) +{ + auto& vm = globalObject->vm(); + auto scope = DECLARE_THROW_SCOPE(vm); + + auto* thisObject = jsCast(JSC::JSValue::decode(thisValue)); + JSValue value = JSC::JSValue::decode(encodedValue); + + if (value.isUndefined() || value.isNull()) { + thisObject->functionToCallOnDrain.clear(); + return true; + } + + if (!value.isCallable()) { + return false; + } + + thisObject->functionToCallOnDrain.set(vm, thisObject, value.getObject()); + return true; +} + +JSC_DEFINE_CUSTOM_GETTER(jsNodeHttpServerSocketGetterOnData, (JSC::JSGlobalObject * globalObject, JSC::EncodedJSValue thisValue, JSC::PropertyName)) +{ + auto* thisObject = jsCast(JSC::JSValue::decode(thisValue)); + + if (thisObject->functionToCallOnData) { + return JSValue::encode(thisObject->functionToCallOnData.get()); + } + + return JSValue::encode(JSC::jsUndefined()); +} + +JSC_DEFINE_CUSTOM_SETTER(jsNodeHttpServerSocketSetterOnData, (JSC::JSGlobalObject * globalObject, JSC::EncodedJSValue thisValue, JSC::EncodedJSValue encodedValue, JSC::PropertyName propertyName)) +{ + auto& vm = globalObject->vm(); + auto scope = DECLARE_THROW_SCOPE(vm); + + auto* thisObject = jsCast(JSC::JSValue::decode(thisValue)); + JSValue value = JSC::JSValue::decode(encodedValue); + + if (value.isUndefined() || value.isNull()) { + thisObject->functionToCallOnData.clear(); + return true; + } + + if (!value.isCallable()) { + return false; + } + + thisObject->functionToCallOnData.set(vm, thisObject, value.getObject()); + return true; +} + +JSC_DEFINE_CUSTOM_SETTER(jsNodeHttpServerSocketSetterOnClose, (JSC::JSGlobalObject * globalObject, JSC::EncodedJSValue thisValue, JSC::EncodedJSValue encodedValue, JSC::PropertyName propertyName)) +{ + auto& vm = globalObject->vm(); + auto scope = DECLARE_THROW_SCOPE(vm); + + auto* thisObject = jsCast(JSC::JSValue::decode(thisValue)); + JSValue value = JSC::JSValue::decode(encodedValue); + + if (value.isUndefined() || value.isNull()) { + thisObject->functionToCallOnClose.clear(); + return true; + } + + if (!value.isCallable()) { + return false; + } + + thisObject->functionToCallOnClose.set(vm, thisObject, value.getObject()); + return true; +} + +JSC_DEFINE_CUSTOM_GETTER(jsNodeHttpServerSocketGetterClosed, (JSC::JSGlobalObject * globalObject, JSC::EncodedJSValue thisValue, JSC::PropertyName propertyName)) +{ + auto* thisObject = jsCast(JSC::JSValue::decode(thisValue)); + return JSValue::encode(JSC::jsBoolean(thisObject->isClosed())); +} + +JSC_DEFINE_CUSTOM_GETTER(jsNodeHttpServerSocketGetterBytesWritten, (JSC::JSGlobalObject * globalObject, JSC::EncodedJSValue thisValue, JSC::PropertyName propertyName)) +{ + auto* thisObject = jsCast(JSC::JSValue::decode(thisValue)); + return JSValue::encode(JSC::jsNumber(thisObject->streamBuffer.totalBytesWritten())); +} + +JSC_DEFINE_CUSTOM_GETTER(jsNodeHttpServerSocketGetterResponse, (JSC::JSGlobalObject * globalObject, JSC::EncodedJSValue thisValue, JSC::PropertyName propertyName)) +{ + auto* thisObject = jsCast(JSC::JSValue::decode(thisValue)); + if (!thisObject->currentResponseObject) { + return JSValue::encode(JSC::jsNull()); + } + + return JSValue::encode(thisObject->currentResponseObject.get()); +} + +} // namespace Bun diff --git a/src/bun.js/bindings/node/JSNodeHTTPServerSocketPrototype.h b/src/bun.js/bindings/node/JSNodeHTTPServerSocketPrototype.h new file mode 100644 index 0000000000..8aecf5f467 --- /dev/null +++ b/src/bun.js/bindings/node/JSNodeHTTPServerSocketPrototype.h @@ -0,0 +1,45 @@ +#pragma once + +#include "root.h" +#include +#include + +namespace Bun { + +class JSNodeHTTPServerSocketPrototype final : public JSC::JSNonFinalObject { +public: + using Base = JSC::JSNonFinalObject; + static constexpr unsigned StructureFlags = Base::StructureFlags | JSC::HasStaticPropertyTable; + + static JSNodeHTTPServerSocketPrototype* create(JSC::VM& vm, JSC::Structure* structure) + { + JSNodeHTTPServerSocketPrototype* prototype = new (NotNull, JSC::allocateCell(vm)) JSNodeHTTPServerSocketPrototype(vm, structure); + prototype->finishCreation(vm); + return prototype; + } + + template + static JSC::GCClient::IsoSubspace* subspaceFor(JSC::VM& vm) + { + return &vm.plainObjectSpace(); + } + + DECLARE_INFO; + + static JSC::Structure* createStructure(JSC::VM& vm, JSC::JSGlobalObject* globalObject, JSC::JSValue prototype) + { + auto* structure = JSC::Structure::create(vm, globalObject, prototype, JSC::TypeInfo(JSC::ObjectType, StructureFlags), info()); + structure->setMayBePrototype(true); + return structure; + } + +private: + JSNodeHTTPServerSocketPrototype(JSC::VM& vm, JSC::Structure* structure) + : Base(vm, structure) + { + } + + void finishCreation(JSC::VM&); +}; + +} // namespace Bun