From acf0b682992abdfa8bdc902d648bf283f6071c72 Mon Sep 17 00:00:00 2001 From: Jarred Sumner Date: Fri, 11 Apr 2025 20:59:38 -0700 Subject: [PATCH] Make request.method getter not allocate memory (#18961) --- bench/snippets/request-method-getter.mjs | 14 ++ src/bun.js/api/server.zig | 6 + src/bun.js/bindings/BunCommonStrings.cpp | 126 +++++++++++++++++ src/bun.js/bindings/BunCommonStrings.h | 75 +++++++--- src/bun.js/bindings/CommonStrings.zig | 38 ++--- src/bun.js/bindings/NodeHTTP.cpp | 132 +++++------------- src/bun.js/bindings/ZigGlobalObject.cpp | 15 -- src/bun.js/webcore/request.zig | 2 +- src/http/method.zig | 82 ++++++----- .../web/request/request-method-getter.test.ts | 56 ++++++++ 10 files changed, 354 insertions(+), 192 deletions(-) create mode 100644 bench/snippets/request-method-getter.mjs create mode 100644 test/js/web/request/request-method-getter.test.ts diff --git a/bench/snippets/request-method-getter.mjs b/bench/snippets/request-method-getter.mjs new file mode 100644 index 0000000000..f28cc2135e --- /dev/null +++ b/bench/snippets/request-method-getter.mjs @@ -0,0 +1,14 @@ +import { bench, run } from "../runner.mjs"; + +const url = "http://localhost:3000/"; +const clonable = new Request(url); + +bench("request.clone().method", () => { + return clonable.clone().method; +}); + +bench("new Request(url).method", () => { + return new Request(url).method; +}); + +await run(); diff --git a/src/bun.js/api/server.zig b/src/bun.js/api/server.zig index a815e9f4a3..d283fea8c9 100644 --- a/src/bun.js/api/server.zig +++ b/src/bun.js/api/server.zig @@ -6460,6 +6460,10 @@ pub fn NewServer(comptime NamespaceType: type, comptime ssl_enabled_: bool, comp globalThis, thisObject, this.config.onNodeHTTPRequest, + if (bun.http.Method.find(req.method())) |method| + method.toJS(globalThis) + else + .undefined, req, resp, upgrade_ctx, @@ -7626,6 +7630,7 @@ extern fn NodeHTTPServer__onRequest_http( globalThis: *JSC.JSGlobalObject, this: JSC.JSValue, callback: JSC.JSValue, + methodString: JSC.JSValue, request: *uws.Request, response: *uws.NewApp(false).Response, upgrade_ctx: ?*uws.uws_socket_context_t, @@ -7637,6 +7642,7 @@ extern fn NodeHTTPServer__onRequest_https( globalThis: *JSC.JSGlobalObject, this: JSC.JSValue, callback: JSC.JSValue, + methodString: JSC.JSValue, request: *uws.Request, response: *uws.NewApp(true).Response, upgrade_ctx: ?*uws.uws_socket_context_t, diff --git a/src/bun.js/bindings/BunCommonStrings.cpp b/src/bun.js/bindings/BunCommonStrings.cpp index a0ed5922ad..7b7ff2cdd7 100644 --- a/src/bun.js/bindings/BunCommonStrings.cpp +++ b/src/bun.js/bindings/BunCommonStrings.cpp @@ -45,4 +45,130 @@ void CommonStrings::visit(Visitor& visitor) template void CommonStrings::visit(JSC::AbstractSlotVisitor&); template void CommonStrings::visit(JSC::SlotVisitor&); +// Must be kept in sync with method.zig +enum class HTTPMethod : uint8_t { + httpACL = 0, + httpBIND = 1, + httpCHECKOUT = 2, + httpCONNECT = 3, + httpCOPY = 4, + // "DELETE" is defined in one of the windows headers + httpDELETE = 5, + httpGET = 6, + httpHEAD = 7, + httpLINK = 8, + httpLOCK = 9, + httpMSEARCH = 10, + httpMERGE = 11, + httpMKACTIVITY = 12, + httpMKCALENDAR = 13, + httpMKCOL = 14, + httpMOVE = 15, + httpNOTIFY = 16, + httpOPTIONS = 17, + httpPATCH = 18, + httpPOST = 19, + httpPROPFIND = 20, + httpPROPPATCH = 21, + httpPURGE = 22, + httpPUT = 23, + httpQUERY = 24, + httpREBIND = 25, + httpREPORT = 26, + httpSEARCH = 27, + httpSOURCE = 28, + httpSUBSCRIBE = 29, + httpTRACE = 30, + httpUNBIND = 31, + httpUNLINK = 32, + httpUNLOCK = 33, + httpUNSUBSCRIBE = 34, +}; + +static JSC::JSValue toJS(Zig::GlobalObject* globalObject, HTTPMethod method) +{ +#define FOR_EACH_METHOD(method) \ + case HTTPMethod::http##method: \ + return globalObject->commonStrings().http##method##String(globalObject); + + switch (method) { + FOR_EACH_METHOD(ACL) + FOR_EACH_METHOD(BIND) + FOR_EACH_METHOD(CHECKOUT) + FOR_EACH_METHOD(CONNECT) + FOR_EACH_METHOD(COPY) + FOR_EACH_METHOD(DELETE) + FOR_EACH_METHOD(GET) + FOR_EACH_METHOD(HEAD) + FOR_EACH_METHOD(LINK) + FOR_EACH_METHOD(LOCK) + FOR_EACH_METHOD(MSEARCH) + FOR_EACH_METHOD(MERGE) + FOR_EACH_METHOD(MKACTIVITY) + FOR_EACH_METHOD(MKCALENDAR) + FOR_EACH_METHOD(MKCOL) + FOR_EACH_METHOD(MOVE) + FOR_EACH_METHOD(NOTIFY) + FOR_EACH_METHOD(OPTIONS) + FOR_EACH_METHOD(PATCH) + FOR_EACH_METHOD(POST) + FOR_EACH_METHOD(PROPFIND) + FOR_EACH_METHOD(PROPPATCH) + FOR_EACH_METHOD(PURGE) + FOR_EACH_METHOD(PUT) + FOR_EACH_METHOD(QUERY) + FOR_EACH_METHOD(REBIND) + FOR_EACH_METHOD(REPORT) + FOR_EACH_METHOD(SEARCH) + FOR_EACH_METHOD(SOURCE) + FOR_EACH_METHOD(SUBSCRIBE) + FOR_EACH_METHOD(TRACE) + FOR_EACH_METHOD(UNBIND) + FOR_EACH_METHOD(UNLINK) + FOR_EACH_METHOD(UNLOCK) + FOR_EACH_METHOD(UNSUBSCRIBE) + + default: { + ASSERT_NOT_REACHED(); + return jsUndefined(); + } + } +#undef FOR_EACH_METHOD +} + +extern "C" JSC::EncodedJSValue Bun__HTTPMethod__toJS(HTTPMethod method, Zig::GlobalObject* globalObject) +{ + return JSValue::encode(toJS(globalObject, method)); +} + +enum class CommonStringsForZig : uint8_t { + IPv4 = 0, + IPv6 = 1, + IN4Loopback = 2, + IN6Any = 3, +}; + +static JSC::JSValue toJS(Zig::GlobalObject* globalObject, CommonStringsForZig commonString) +{ + switch (commonString) { + case CommonStringsForZig::IPv4: + return globalObject->commonStrings().IPv4String(globalObject); + case CommonStringsForZig::IPv6: + return globalObject->commonStrings().IPv6String(globalObject); + case CommonStringsForZig::IN4Loopback: + return globalObject->commonStrings().IN4LoopbackString(globalObject); + case CommonStringsForZig::IN6Any: + return globalObject->commonStrings().IN6AnyString(globalObject); + default: { + ASSERT_NOT_REACHED(); + return jsUndefined(); + } + } +} + +extern "C" JSC::EncodedJSValue Bun__CommonStringsForZig__toJS(CommonStringsForZig commonString, Zig::GlobalObject* globalObject) +{ + return JSValue::encode(toJS(globalObject, commonString)); +} + } // namespace Bun diff --git a/src/bun.js/bindings/BunCommonStrings.h b/src/bun.js/bindings/BunCommonStrings.h index 4d44948130..c2afbd122a 100644 --- a/src/bun.js/bindings/BunCommonStrings.h +++ b/src/bun.js/bindings/BunCommonStrings.h @@ -11,33 +11,68 @@ // These ones don't need to be in BunBuiltinNames.h // If we don't use it as an identifier name, but we want to avoid allocating the string frequently, put it in this list. #define BUN_COMMON_STRINGS_EACH_NAME_NOT_BUILTIN_NAMES(macro) \ - macro(systemError, "SystemError") \ - macro(s3Error, "S3Error") \ - macro(utf8, "utf8") \ - macro(ucs2, "ucs2") \ - macro(utf16le, "utf16le") \ - macro(latin1, "latin1") \ + macro(httpACL, "ACL") \ + macro(httpBIND, "BIND") \ + macro(httpCHECKOUT, "CHECKOUT") \ + macro(httpCONNECT, "CONNECT") \ + macro(httpCOPY, "COPY") \ + macro(ConnectionWasClosed, "The connection was closed.") \ + macro(httpDELETE, "DELETE") \ + macro(httpGET, "GET") \ + macro(httpHEAD, "HEAD") \ + macro(IN4Loopback, "127.0.0.1") \ + macro(IN6Any, "::") \ + macro(IPv4, "IPv4") \ + macro(IPv6, "IPv6") \ + macro(httpLINK, "LINK") \ + macro(httpLOCK, "LOCK") \ + macro(httpMERGE, "MERGE") \ + macro(httpMKACTIVITY, "MKACTIVITY") \ + macro(httpMKCALENDAR, "MKCALENDAR") \ + macro(httpMKCOL, "MKCOL") \ + macro(httpMOVE, "MOVE") \ + macro(httpMSEARCH, "M-SEARCH") \ + macro(httpNOTIFY, "NOTIFY") \ + macro(httpOPTIONS, "OPTIONS") \ + macro(OperationFailed, "The operation failed.") \ + macro(OperationTimedOut, "The operation timed out.") \ + macro(OperationWasAborted, "The operation was aborted.") \ + macro(httpPATCH, "PATCH") \ + macro(httpPOST, "POST") \ + macro(httpPROPFIND, "PROPFIND") \ + macro(httpPROPPATCH, "PROPPATCH") \ + macro(httpPURGE, "PURGE") \ + macro(httpPUT, "PUT") \ + macro(httpQUERY, "QUERY") \ + macro(httpREBIND, "REBIND") \ + macro(httpREPORT, "REPORT") \ + macro(httpSEARCH, "SEARCH") \ + macro(httpSOURCE, "SOURCE") \ + macro(httpSUBSCRIBE, "SUBSCRIBE") \ + macro(httpTRACE, "TRACE") \ + macro(httpUNBIND, "UNBIND") \ + macro(httpUNLINK, "UNLINK") \ + macro(httpUNLOCK, "UNLOCK") \ + macro(httpUNSUBSCRIBE, "UNSUBSCRIBE") \ macro(ascii, "ascii") \ macro(base64, "base64") \ macro(base64url, "base64url") \ - macro(hex, "hex") \ macro(buffer, "buffer") \ + macro(ec, "ec") \ + macro(ed25519, "ed25519") \ + macro(hex, "hex") \ + macro(latin1, "latin1") \ + macro(lax, "lax") \ + macro(none, "none") \ macro(rsa, "rsa") \ macro(rsaPss, "rsa-pss") \ - macro(ec, "ec") \ - macro(x25519, "x25519") \ - macro(ed25519, "ed25519") \ - macro(IPv4, "IPv4") \ - macro(IPv6, "IPv6") \ - macro(IN4Loopback, "127.0.0.1") \ - macro(IN6Any, "::") \ - macro(OperationWasAborted, "The operation was aborted.") \ - macro(OperationTimedOut, "The operation timed out.") \ - macro(ConnectionWasClosed, "The connection was closed.") \ - macro(OperationFailed, "The operation failed.") \ + macro(s3Error, "S3Error") \ macro(strict, "strict") \ - macro(lax, "lax") \ - macro(none, "none") + macro(systemError, "SystemError") \ + macro(ucs2, "ucs2") \ + macro(utf16le, "utf16le") \ + macro(utf8, "utf8") \ + macro(x25519, "x25519") // clang-format on diff --git a/src/bun.js/bindings/CommonStrings.zig b/src/bun.js/bindings/CommonStrings.zig index 283f23c7d3..a7d0a5559d 100644 --- a/src/bun.js/bindings/CommonStrings.zig +++ b/src/bun.js/bindings/CommonStrings.zig @@ -4,38 +4,28 @@ pub const CommonStrings = struct { globalObject: *JSC.JSGlobalObject, + const CommonStringsForZig = enum(u8) { + IPv4 = 0, + IPv6 = 1, + IN4Loopback = 2, + IN6Any = 3, + + extern "c" fn Bun__CommonStringsForZig__toJS(commonString: CommonStringsForZig, globalObject: *JSC.JSGlobalObject) JSC.JSValue; + pub const toJS = Bun__CommonStringsForZig__toJS; + }; + pub inline fn IPv4(this: CommonStrings) JSValue { - return this.getString("IPv4"); + return CommonStringsForZig.IPv4.toJS(this.globalObject); } pub inline fn IPv6(this: CommonStrings) JSValue { - return this.getString("IPv6"); + return CommonStringsForZig.IPv6.toJS(this.globalObject); } pub inline fn @"127.0.0.1"(this: CommonStrings) JSValue { - return this.getString("IN4Loopback"); + return CommonStringsForZig.IN4Loopback.toJS(this.globalObject); } pub inline fn @"::"(this: CommonStrings) JSValue { - return this.getString("IN6Any"); + return CommonStringsForZig.IN6Any.toJS(this.globalObject); } - - inline fn getString(this: CommonStrings, comptime name: anytype) JSValue { - JSC.markMemberBinding("CommonStrings", @src()); - const str: JSC.JSValue = @call( - .auto, - @field(CommonStrings, "JSC__JSGlobalObject__commonStrings__get" ++ name), - .{this.globalObject}, - ); - bun.assert(str != .zero); - if (comptime bun.Environment.isDebug) { - bun.assertWithLocation(str != .zero, @src()); - bun.assertWithLocation(str.isStringLiteral(), @src()); - } - return str; - } - - extern "C" fn JSC__JSGlobalObject__commonStrings__getIPv4(global: *JSC.JSGlobalObject) JSC.JSValue; - extern "C" fn JSC__JSGlobalObject__commonStrings__getIPv6(global: *JSC.JSGlobalObject) JSC.JSValue; - extern "C" fn JSC__JSGlobalObject__commonStrings__getIN4Loopback(global: *JSC.JSGlobalObject) JSC.JSValue; - extern "C" fn JSC__JSGlobalObject__commonStrings__getIN6Any(global: *JSC.JSGlobalObject) JSC.JSValue; }; const std = @import("std"); diff --git a/src/bun.js/bindings/NodeHTTP.cpp b/src/bun.js/bindings/NodeHTTP.cpp index 73a87667af..e6642c1470 100644 --- a/src/bun.js/bindings/NodeHTTP.cpp +++ b/src/bun.js/bindings/NodeHTTP.cpp @@ -561,103 +561,22 @@ static EncodedJSValue assignHeadersFromFetchHeaders(FetchHeaders& impl, JSObject return JSValue::encode(tuple); } -static void assignHeadersFromUWebSocketsForCall(uWS::HttpRequest* request, MarkedArgumentBuffer& args, JSC::JSGlobalObject* globalObject, JSC::VM& vm) +static void assignHeadersFromUWebSocketsForCall(uWS::HttpRequest* request, JSValue methodString, 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(fullURLStdStr.data()), fullURLStdStr.length() }); - - // Get the URL. { - args.append(jsString(vm, fullURL)); + std::string_view fullURLStdStr = request->getFullUrl(); + String fullURL = String::fromUTF8ReplacingInvalidSequences({ reinterpret_cast(fullURLStdStr.data()), fullURLStdStr.length() }); + args.append(jsString(vm, WTFMove(fullURL))); } // Get the method. - { + if (UNLIKELY(methodString.isUndefinedOrNull())) { 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(methodView.data()), methodView.length() }); - } - - args.append(jsString(vm, methodString)); + WTF::String methodString = String::fromUTF8ReplacingInvalidSequences({ reinterpret_cast(methodView.data()), methodView.length() }); + args.append(jsString(vm, WTFMove(methodString))); + } else { + args.append(methodString); } size_t size = 0; @@ -740,12 +659,12 @@ static EncodedJSValue assignHeadersFromUWebSockets(uWS::HttpRequest* request, JS { auto scope = DECLARE_THROW_SCOPE(vm); auto& builtinNames = WebCore::builtinNames(vm); - std::string_view fullURLStdStr = request->getFullUrl(); - String fullURL = String::fromUTF8ReplacingInvalidSequences({ reinterpret_cast(fullURLStdStr.data()), fullURLStdStr.length() }); { + std::string_view fullURLStdStr = request->getFullUrl(); + String fullURL = String::fromUTF8ReplacingInvalidSequences({ reinterpret_cast(fullURLStdStr.data()), fullURLStdStr.length() }); PutPropertySlot slot(objectValue, false); - objectValue->put(objectValue, globalObject, builtinNames.urlPublicName(), jsString(vm, fullURL), slot); + objectValue->put(objectValue, globalObject, builtinNames.urlPublicName(), jsString(vm, WTFMove(fullURL)), slot); RETURN_IF_EXCEPTION(scope, {}); } @@ -938,6 +857,7 @@ static EncodedJSValue NodeHTTPServer__onRequest( Zig::GlobalObject* globalObject, JSValue thisValue, JSValue callback, + JSValue methodString, uWS::HttpRequest* request, uWS::HttpResponse* response, void* upgrade_ctx, @@ -950,7 +870,7 @@ static EncodedJSValue NodeHTTPServer__onRequest( MarkedArgumentBuffer args; args.append(thisValue); - assignHeadersFromUWebSocketsForCall(request, args, globalObject, vm); + assignHeadersFromUWebSocketsForCall(request, methodString, args, globalObject, vm); if (scope.exception()) { auto* exception = scope.exception(); response->endWithoutBody(); @@ -1185,12 +1105,22 @@ extern "C" EncodedJSValue NodeHTTPServer__onRequest_http( Zig::GlobalObject* globalObject, EncodedJSValue thisValue, EncodedJSValue callback, + EncodedJSValue methodString, uWS::HttpRequest* request, uWS::HttpResponse* response, void* upgrade_ctx, void** nodeHttpResponsePtr) { - return NodeHTTPServer__onRequest(any_server, globalObject, JSValue::decode(thisValue), JSValue::decode(callback), request, response, upgrade_ctx, nodeHttpResponsePtr); + return NodeHTTPServer__onRequest( + any_server, + globalObject, + JSValue::decode(thisValue), + JSValue::decode(callback), + JSValue::decode(methodString), + request, + response, + upgrade_ctx, + nodeHttpResponsePtr); } extern "C" EncodedJSValue NodeHTTPServer__onRequest_https( @@ -1198,12 +1128,22 @@ extern "C" EncodedJSValue NodeHTTPServer__onRequest_https( Zig::GlobalObject* globalObject, EncodedJSValue thisValue, EncodedJSValue callback, + EncodedJSValue methodString, uWS::HttpRequest* request, uWS::HttpResponse* response, void* upgrade_ctx, void** nodeHttpResponsePtr) { - return NodeHTTPServer__onRequest(any_server, globalObject, JSValue::decode(thisValue), JSValue::decode(callback), request, response, upgrade_ctx, nodeHttpResponsePtr); + return NodeHTTPServer__onRequest( + any_server, + globalObject, + JSValue::decode(thisValue), + JSValue::decode(callback), + JSValue::decode(methodString), + request, + response, + upgrade_ctx, + nodeHttpResponsePtr); } JSC_DEFINE_HOST_FUNCTION(jsHTTPAssignHeaders, (JSGlobalObject * globalObject, CallFrame* callFrame)) diff --git a/src/bun.js/bindings/ZigGlobalObject.cpp b/src/bun.js/bindings/ZigGlobalObject.cpp index 33c2e280cd..94714cfc70 100644 --- a/src/bun.js/bindings/ZigGlobalObject.cpp +++ b/src/bun.js/bindings/ZigGlobalObject.cpp @@ -3957,21 +3957,6 @@ extern "C" EncodedJSValue JSC__JSGlobalObject__getHTTP2CommonString(Zig::GlobalO return JSValue::encode(JSValue::JSUndefined); } -#define IMPL_GET_COMMON_STRING(name) \ - extern "C" EncodedJSValue JSC__JSGlobalObject__commonStrings__get##name(Zig::GlobalObject* globalObject) \ - { \ - JSC::JSString* value = globalObject->commonStrings().name##String(globalObject); \ - ASSERT(value != nullptr); \ - return JSValue::encode(value); \ - } - -IMPL_GET_COMMON_STRING(IPv4) -IMPL_GET_COMMON_STRING(IPv6) -IMPL_GET_COMMON_STRING(IN4Loopback) -IMPL_GET_COMMON_STRING(IN6Any) - -#undef IMPL_GET_COMMON_STRING - template void GlobalObject::visitChildrenImpl(JSCell* cell, Visitor& visitor) { diff --git a/src/bun.js/webcore/request.zig b/src/bun.js/webcore/request.zig index dcbfcf58af..fb97c4aad5 100644 --- a/src/bun.js/webcore/request.zig +++ b/src/bun.js/webcore/request.zig @@ -352,7 +352,7 @@ pub const Request = struct { this: *Request, globalThis: *JSC.JSGlobalObject, ) JSC.JSValue { - return bun.String.static(@tagName(this.method)).toJS(globalThis); + return this.method.toJS(globalThis); } pub fn getMode( diff --git a/src/http/method.zig b/src/http/method.zig index 67103c523e..2be3af24ce 100644 --- a/src/http/method.zig +++ b/src/http/method.zig @@ -10,43 +10,43 @@ const default_allocator = bun.default_allocator; const C = bun.C; const std = @import("std"); -pub const Method = enum { - ACL, - BIND, - CHECKOUT, - CONNECT, - COPY, - DELETE, - GET, - HEAD, - LINK, - LOCK, - @"M-SEARCH", - MERGE, - MKACTIVITY, - MKCALENDAR, - MKCOL, - MOVE, - NOTIFY, - OPTIONS, - PATCH, - POST, - PROPFIND, - PROPPATCH, - PURGE, - PUT, +pub const Method = enum(u8) { + ACL = 0, + BIND = 1, + CHECKOUT = 2, + CONNECT = 3, + COPY = 4, + DELETE = 5, + GET = 6, + HEAD = 7, + LINK = 8, + LOCK = 9, + @"M-SEARCH" = 10, + MERGE = 11, + MKACTIVITY = 12, + MKCALENDAR = 13, + MKCOL = 14, + MOVE = 15, + NOTIFY = 16, + OPTIONS = 17, + PATCH = 18, + POST = 19, + PROPFIND = 20, + PROPPATCH = 21, + PURGE = 22, + PUT = 23, /// https://httpwg.org/http-extensions/draft-ietf-httpbis-safe-method-w-body.html - QUERY, - REBIND, - REPORT, - SEARCH, - SOURCE, - SUBSCRIBE, - TRACE, - UNBIND, - UNLINK, - UNLOCK, - UNSUBSCRIBE, + QUERY = 24, + REBIND = 25, + REPORT = 26, + SEARCH = 27, + SOURCE = 28, + SUBSCRIBE = 29, + TRACE = 30, + UNBIND = 31, + UNLINK = 32, + UNLOCK = 33, + UNSUBSCRIBE = 34, pub const fromJS = Map.fromJS; @@ -74,6 +74,10 @@ pub const Method = enum { return with_request_body.contains(this); } + pub fn find(str: []const u8) ?Method { + return Map.get(str); + } + const Map = bun.ComptimeStringMap(Method, .{ .{ "ACL", Method.ACL }, .{ "BIND", Method.BIND }, @@ -151,4 +155,10 @@ pub const Method = enum { pub fn which(str: []const u8) ?Method { return Map.get(str); } + + extern "c" fn Bun__HTTPMethod__toJS(method: Method, globalObject: *JSC.JSGlobalObject) JSC.JSValue; + + pub const toJS = Bun__HTTPMethod__toJS; + + const JSC = bun.JSC; }; diff --git a/test/js/web/request/request-method-getter.test.ts b/test/js/web/request/request-method-getter.test.ts new file mode 100644 index 0000000000..8ccad3c69d --- /dev/null +++ b/test/js/web/request/request-method-getter.test.ts @@ -0,0 +1,56 @@ +import { test, expect } from "bun:test"; +import { heapStats } from "bun:jsc"; + +const requestOptions = [ + ["http://localhost:3000/"], + [ + "http://localhost:3000/", + { + method: "GET", + }, + ], + [ + "http://localhost:3000/", + { + method: "POST", + }, + ], +] as const; +test.each(requestOptions)("new Request(%s).clone().method doesnt create a new JSString every time", function () { + // Start at a clean state. + Bun.gc(true); + + // @ts-expect-error + const request = new Request(...arguments); + + const { + objectTypeCounts: { string: initialStrings }, + } = heapStats(); + for (let i = 0; i < 1024 * 512; i++) { + request.clone().method; + } + const { + objectTypeCounts: { string: finalStrings }, + } = heapStats(); + + expect(finalStrings - initialStrings).toBeLessThan(512); +}); + +test.each(requestOptions)("new Request(%s).method doesnt create a new JSString every time", function () { + // Start at a clean state. + Bun.gc(true); + + const { + objectTypeCounts: { string: initialStrings }, + } = heapStats(); + for (let i = 0; i < 1024 * 128; i++) { + // @ts-expect-error + const request = new Request(...arguments); + request.method; + } + const { + objectTypeCounts: { string: finalStrings }, + } = heapStats(); + + expect(finalStrings - initialStrings).toBeLessThan(512); +});