diff --git a/docs/index.d.ts b/docs/index.d.ts index 5199f608..3c465244 100644 --- a/docs/index.d.ts +++ b/docs/index.d.ts @@ -329,6 +329,9 @@ export interface TemplatedApp { missingServerName(cb: (hostname: string) => void) : TemplatedApp; /** Attaches a "filter" function to track socket connections / disconnections */ filter(cb: (res: HttpResponse, count: Number) => void | Promise) : TemplatedApp; + /** Registers an HTTP parsing error handler that is called when malformed HTTP requests are encountered. + * The handler receives the request object (or null if request is too malformed), HTTP status code, and complete response body. */ + log(cb: (req: HttpRequest | null, statusCode: number, responseBody: ArrayBuffer) => void | Promise) : TemplatedApp; /** Closes all sockets including listen sockets. This will forcefully terminate all connections. */ close() : TemplatedApp; } diff --git a/examples/HttpParsingLogHandling.js b/examples/HttpParsingLogHandling.js new file mode 100644 index 00000000..e9d73e17 --- /dev/null +++ b/examples/HttpParsingLogHandling.js @@ -0,0 +1,30 @@ +const uWS = require('../src/uws.js'); + +const app = uWS.App({ + // Optional SSL configuration +}).log((req, httpStatus, responseBody) => { + console.log('HTTP parsing error occurred with status:', httpStatus); + + if (req) { + console.log('Request URL:', req.getUrl()); + console.log('Request method:', req.getMethod()); + console.log('Request headers:', req.getHeader('host')); + } else { + console.log('Request information not available (parsing failed early)'); + } + + // Log the response body that will be sent to the client + console.log('Response body length:', responseBody.byteLength); + console.log('Response preview:', Buffer.from(responseBody).toString().substring(0, 100) + '...'); + + // High-level program can now handle the error gracefully + // The socket will be closed automatically after this handler runs +}).get('/*', (res, req) => { + res.end('Hello World!'); +}).listen(9001, (token) => { + if (token) { + console.log('Listening to port 9001 with HTTP parsing error handling'); + } else { + console.log('Failed to listen to port 9001'); + } +}); diff --git a/src/AppWrapper.h b/src/AppWrapper.h index 63bb97de..bc51640f 100644 --- a/src/AppWrapper.h +++ b/src/AppWrapper.h @@ -570,6 +570,45 @@ void uWS_App_filter(const FunctionCallbackInfo &args) { args.GetReturnValue().Set(args.This()); } +template +void uWS_App_log(const FunctionCallbackInfo &args) { + APP *app = (APP *) args.This()->GetAlignedPointerFromInternalField(0); + + /* Handler */ + Callback checkedCallback(args.GetIsolate(), args[0]); + if (checkedCallback.isInvalid(args)) { + return; + } + UniquePersistent cb = checkedCallback.getFunction(); + + /* This function requires perContextData */ + PerContextData *perContextData = (PerContextData *) Local::Cast(args.Data())->Value(); + + app->log([cb = std::move(cb), perContextData](auto *req, int statusCode, std::string_view responseBody) { + Isolate *isolate = perContextData->isolate; + HandleScope hs(isolate); + + Local reqObject; + if (req) { + reqObject = perContextData->reqTemplate[getAppTypeIndex()].Get(isolate)->Clone(); + reqObject->SetAlignedPointerInInternalField(0, req); + } else { + reqObject = Local::Cast(Null(isolate)); + } + + Local responseBodyBuffer = ArrayBuffer_New(isolate, (void *) responseBody.data(), responseBody.length()); + + Local argv[] = {reqObject, Local::Cast(Integer::New(isolate, statusCode)), responseBodyBuffer}; + CallJS(isolate, cb.Get(isolate), 3, argv); + + /* Important: we clear the ArrayBuffer to make sure it is not invalidly used after return */ + responseBodyBuffer->Detach(); + }); + + args.GetReturnValue().Set(args.This()); +} + + template void uWS_App_domain(const FunctionCallbackInfo &args) { APP *app = (APP *) args.This()->GetAlignedPointerFromInternalField(0); @@ -996,6 +1035,7 @@ void uWS_App(const FunctionCallbackInfo &args) { appTemplate->PrototypeTemplate()->Set(String::NewFromUtf8(isolate, "close", NewStringType::kNormal).ToLocalChecked(), FunctionTemplate::New(isolate, uWS_App_close, args.Data())); appTemplate->PrototypeTemplate()->Set(String::NewFromUtf8(isolate, "listen_unix", NewStringType::kNormal).ToLocalChecked(), FunctionTemplate::New(isolate, uWS_App_listen_unix, args.Data())); appTemplate->PrototypeTemplate()->Set(String::NewFromUtf8(isolate, "filter", NewStringType::kNormal).ToLocalChecked(), FunctionTemplate::New(isolate, uWS_App_filter, args.Data())); + appTemplate->PrototypeTemplate()->Set(String::NewFromUtf8(isolate, "log", NewStringType::kNormal).ToLocalChecked(), FunctionTemplate::New(isolate, uWS_App_log, args.Data())); /* load balancing */ appTemplate->PrototypeTemplate()->Set(String::NewFromUtf8(isolate, "removeChildAppDescriptor", NewStringType::kNormal).ToLocalChecked(), FunctionTemplate::New(isolate, uWS_App_removeChildApp, args.Data()));