diff --git a/examples/Upgrade.js b/examples/Upgrade.js new file mode 100644 index 0000000..c37dc64 --- /dev/null +++ b/examples/Upgrade.js @@ -0,0 +1,45 @@ +/* A quite detailed WebSockets example */ + +const uWS = require('../dist/uws.js'); +const port = 9001; + +const app = uWS./*SSL*/App({ + key_file_name: 'misc/key.pem', + cert_file_name: 'misc/cert.pem', + passphrase: '1234' +}).ws('/*', { + /* Options */ + compression: 0, + maxPayloadLength: 16 * 1024 * 1024, + idleTimeout: 10, + /* Handlers */ + upgrade: (res, req, context) => { + console.log('An Http connection wants to become WebSocket, URL: ' + req.getUrl() + '!'); + + res.upgrade({url: req.getUrl()}, req.getHeader('sec-websocket-key'), + req.getHeader('sec-websocket-protocol'), + req.getHeader('sec-websocket-extensions'), + context); + }, + open: (ws) => { + console.log('A WebSocket connected with URL: ' + ws.userData.url); + }, + message: (ws, message, isBinary) => { + /* Ok is false if backpressure was built up, wait for drain */ + let ok = ws.send(message, isBinary); + }, + drain: (ws) => { + console.log('WebSocket backpressure: ' + ws.getBufferedAmount()); + }, + close: (ws, code, message) => { + console.log('WebSocket closed'); + } +}).any('/*', (res, req) => { + res.end('Nothing to see here!'); +}).listen(port, (token) => { + if (token) { + console.log('Listening to port ' + port); + } else { + console.log('Failed to listen to port ' + port); + } +}); diff --git a/examples/WebSockets.js b/examples/WebSockets.js index a758497..c5bf0de 100644 --- a/examples/WebSockets.js +++ b/examples/WebSockets.js @@ -13,8 +13,8 @@ const app = uWS./*SSL*/App({ maxPayloadLength: 16 * 1024 * 1024, idleTimeout: 10, /* Handlers */ - open: (ws, req) => { - console.log('A WebSocket connected via URL: ' + req.getUrl() + '!'); + open: (ws) => { + console.log('A WebSocket connected!'); }, message: (ws, message, isBinary) => { /* Ok is false if backpressure was built up, wait for drain */ diff --git a/src/AppWrapper.h b/src/AppWrapper.h index e198ce9..1dce051 100644 --- a/src/AppWrapper.h +++ b/src/AppWrapper.h @@ -20,6 +20,7 @@ void uWS_App_ws(const FunctionCallbackInfo &args) { return; } + UniquePersistent upgradePf; UniquePersistent openPf; UniquePersistent messagePf; UniquePersistent drainPf; @@ -27,10 +28,6 @@ void uWS_App_ws(const FunctionCallbackInfo &args) { UniquePersistent pingPf; UniquePersistent pongPf; - struct PerSocketData { - UniquePersistent *socketPf; - }; - /* Get the behavior object */ if (args.Length() == 2) { Local behaviorObject = Local::Cast(args[1]); @@ -59,6 +56,8 @@ void uWS_App_ws(const FunctionCallbackInfo &args) { behavior.maxBackpressure = maybeMaxBackpressure.ToLocalChecked()->Int32Value(isolate->GetCurrentContext()).ToChecked(); } + /* Upgrade */ + upgradePf.Reset(args.GetIsolate(), Local::Cast(behaviorObject->Get(isolate->GetCurrentContext(), String::NewFromUtf8(isolate, "upgrade", NewStringType::kNormal).ToLocalChecked()).ToLocalChecked())); /* Open */ openPf.Reset(args.GetIsolate(), Local::Cast(behaviorObject->Get(isolate->GetCurrentContext(), String::NewFromUtf8(isolate, "open", NewStringType::kNormal).ToLocalChecked()).ToLocalChecked())); /* Message */ @@ -74,28 +73,58 @@ void uWS_App_ws(const FunctionCallbackInfo &args) { } + /* Upgrade handler is always optional */ + if (upgradePf != Undefined(isolate)) { + behavior.upgrade = [upgradePf = std::move(upgradePf), perContextData](auto *res, auto *req, auto *context) { + Isolate *isolate = perContextData->isolate; + HandleScope hs(isolate); + + Local upgradeLf = Local::New(isolate, upgradePf); + Local resObject = perContextData->resTemplate[getAppTypeIndex()].Get(isolate)->Clone(); + resObject->SetAlignedPointerInInternalField(0, res); + + Local reqObject = perContextData->reqTemplate.Get(isolate)->Clone(); + reqObject->SetAlignedPointerInInternalField(0, req); + + Local argv[3] = {resObject, reqObject, External::New(isolate, (void *) context)}; + CallJS(isolate, upgradeLf, 3, argv); + + /* Properly invalidate req */ + reqObject->SetAlignedPointerInInternalField(0, nullptr); + + /* µWS itself will terminate if not responded and not attached + * onAborted handler, so we can assume it's done */ + }; + } + /* Open handler is NOT optional for the wrapper */ - behavior.open = [openPf = std::move(openPf), perContextData](auto *ws, auto *req) { + behavior.open = [openPf = std::move(openPf), perContextData](auto *ws) { Isolate *isolate = perContextData->isolate; HandleScope hs(isolate); + printf("Open event called!\n"); + + /* Retrieve temporary userData object */ + PerSocketData *perSocketData = (PerSocketData *) ws->getUserData(); + + // if socketPf is nullptr we have nothing to copy + Local userData = Local::New(isolate, *(perSocketData->socketPf)); + /* Create a new websocket object */ Local wsObject = perContextData->wsTemplate[getAppTypeIndex()].Get(isolate)->Clone(); wsObject->SetAlignedPointerInInternalField(0, ws); - /* Create the HttpRequest wrapper */ - Local reqObject = perContextData->reqTemplate.Get(isolate)->Clone(); - reqObject->SetAlignedPointerInInternalField(0, req); + /* Copy entires from userData */ + wsObject->Set(isolate->GetCurrentContext(), String::NewFromUtf8(isolate, "userData"), userData); /* Attach a new V8 object with pointer to us, to us */ - PerSocketData *perSocketData = (PerSocketData *) ws->getUserData(); perSocketData->socketPf = new UniquePersistent; perSocketData->socketPf->Reset(isolate, wsObject); Local openLf = Local::New(isolate, openPf); if (!openLf->IsUndefined()) { - Local argv[] = {wsObject, reqObject}; - CallJS(isolate, openLf, 2, argv); + Local argv[] = {wsObject}; + CallJS(isolate, openLf, 1, argv); } }; diff --git a/src/HttpResponseWrapper.h b/src/HttpResponseWrapper.h index c9099f5..2f91739 100644 --- a/src/HttpResponseWrapper.h +++ b/src/HttpResponseWrapper.h @@ -226,7 +226,7 @@ struct HttpResponseWrapper { if (value.isInvalid(args)) { return; } - res->writeHeader(header.getString(),value.getString()); + res->writeHeader(header.getString(), value.getString()); args.GetReturnValue().Set(args.Holder()); } @@ -248,6 +248,58 @@ struct HttpResponseWrapper { } } + template + static void res_upgrade(const FunctionCallbackInfo &args) { + Isolate *isolate = args.GetIsolate(); + auto *res = getHttpResponse(args); + if (res) { + + printf("Calling upgrade!\n"); + + if (args.Length() != 5) { + return; + } + + /* We are being passed userData (wsObject) */ + //Local wsObject = args[0]; + //Local secWebSocketKey = args[1]; + //Local secWebSocketProtocol = args[2]; + //Local secWebSocketExtensions = args[3]; + //Local context = args[4]; + + + NativeString secWebSocketKey(args.GetIsolate(), args[1]); + if (secWebSocketKey.isInvalid(args)) { + return; + } + + NativeString secWebSocketProtocol(args.GetIsolate(), args[2]); + if (secWebSocketProtocol.isInvalid(args)) { + return; + } + + NativeString secWebSocketExtensions(args.GetIsolate(), args[3]); + if (secWebSocketExtensions.isInvalid(args)) { + return; + } + + auto *context = (struct us_socket_context_t *) Local::Cast(args[4])->Value(); + + invalidateResObject(args); + + UniquePersistent userData; + userData.Reset(isolate, Local::Cast(args[0])); + + printf("Upgrading now!\n"); + res->template upgrade({ + .socketPf = &userData + }, secWebSocketKey.getString(), secWebSocketProtocol.getString(), + secWebSocketExtensions.getString(), context); + + /* Nothing is returned */ + } + } + template static Local init(Isolate *isolate) { Local resTemplateLocal = FunctionTemplate::New(isolate); @@ -271,10 +323,11 @@ struct HttpResponseWrapper { resTemplateLocal->PrototypeTemplate()->Set(String::NewFromUtf8(isolate, "onData", NewStringType::kNormal).ToLocalChecked(), FunctionTemplate::New(isolate, res_onData)); resTemplateLocal->PrototypeTemplate()->Set(String::NewFromUtf8(isolate, "getRemoteAddress", NewStringType::kNormal).ToLocalChecked(), FunctionTemplate::New(isolate, res_getRemoteAddress)); resTemplateLocal->PrototypeTemplate()->Set(String::NewFromUtf8(isolate, "cork", NewStringType::kNormal).ToLocalChecked(), FunctionTemplate::New(isolate, res_cork)); + resTemplateLocal->PrototypeTemplate()->Set(String::NewFromUtf8(isolate, "upgrade", NewStringType::kNormal).ToLocalChecked(), FunctionTemplate::New(isolate, res_upgrade)); /* Create our template */ Local resObjectLocal = resTemplateLocal->GetFunction(isolate->GetCurrentContext()).ToLocalChecked()->NewInstance(isolate->GetCurrentContext()).ToLocalChecked(); - + return resObjectLocal; } }; diff --git a/src/Utilities.h b/src/Utilities.h index 036567b..98bbf6d 100644 --- a/src/Utilities.h +++ b/src/Utilities.h @@ -17,6 +17,10 @@ MaybeLocal CallJS(Isolate *isolate, Local f, int argc, Local *socketPf = nullptr; +}; + struct PerContextData { Isolate *isolate; UniquePersistent reqTemplate; diff --git a/uWebSockets b/uWebSockets index fb7a330..9e3a75b 160000 --- a/uWebSockets +++ b/uWebSockets @@ -1 +1 @@ -Subproject commit fb7a3303d627812651f7a4f87fe25ea6e74f86b3 +Subproject commit 9e3a75b19ac560ea03e5064043f25a2857cc7105