There appears to be some confusion surrounding the NodeJS WebSocket broker service and the Browser WebSocket client.
For clarification, the WebSocket broker runs on the backend as a server alongside Apache, in order to listen for messages being sent from browser WebSocket clients. In the same way that Apache listens for HTTP Requests from browser clients.
The WebSocket broker server receives a message then forwards the data as an HTTP Request so that Symfony can handle the request and send back a response that the broker passes back to the WebSocket client.
The process is identical to how Ratchet listens but without the overhead of Symfony and Doctrine being loaded in a perpetually running state by using php bin/console app:command
. With the exception that instead of being in the Symfony environment already, bin/ws-broker.js creates an HTTP request to send to Symfony.
The bin/ws-broker.js process breakdown goes like this.
Browser HTTP Request -> /path/to/index ->
Symfony AppController::index() ->
return Response(Twig::render('index.html.twig')) ->
WebSocket Client - socket.send(message) ->
[node bin/ws-broker.js WebSocket Server - ws.on('message')] ->
[node bin/ws-broker.js http.request()] -> /path/to/score-handler ->
Symfony AppController::scoreHandler() ->
return JsonResponse(data) ->
[node bin/ws-broker.js WebSocket Server - ws.send(response) to WebSocket Client] ->
WebSocket Client - socket.onmessage()
The bin/console app:command Ratchet process breakdown goes like this.
Browser HTTP Request -> /path/to/index ->
Symfony AppController::index() ->
return Response(Twig::render('index.html.twig')) ->
WebSocket Client - socket.send(message) ->
[php bin/console app:command - Ratchet\MessageComponentInterface::onMessage()] ->
[php bin/console app:command - $client->send(response) to WebSocket Client] ->
WebSocket Client - socket.onmessage()
Add the Symfony HTTP Request and Response handler to the NodeJS WebSocket Listener Service
// bin/ws-broker.js
const WebSocket = require('ws');
const qs = require('querystring');
const http = require('http');
const wss = new WebSocket.Server({ port: 8080 });
wss.on('connection', function connection(ws, req) {
console.log('Connection Received from IP: ' + req.socket.remoteAddress);
ws.on('message', function incoming(message) {
if ('' === message) {
//empty message
return;
}
try {
//process message as JSON object
message = JSON.parse(message);
} catch (e) {
//failed parsing the message as JSON
return;
}
//convert the WS message to a query string for the Symfony Request object
let postData = qs.stringify({
"name": message.name,
"message": message.message
});
let http_options = {
host: 'localhost',
path: '/' + message.token,
port: 8000,
method: 'POST',
headers: {
'Content-Type': 'application/x-www-form-urlencoded',
'Content-Length': postData.length
}
};
//forward the message to Symfony using an HTTP Request
let req = http.request(http_options, function(res) {
res.setEncoding('utf8');
//process the Symfony response data
let data = '';
res.on('data', (chunk) => {
data += chunk
});
//send the Symfony Response back to the WebSocket client
res.on('end', function() {
ws.send(data);
});
});
//send the requested message to Symfony
req.write(postData);
req.end();
});
});
console.log('Listening on port 8080');
Run the WebSocket Broker Server
node bin/ws-broker.js &
Add a route to handle the Broker Request and send a Response back to the Broker. Add the token to the home/index.html.twig context.
class AppController extends AbstractController
{
/**
* @Route("/{token}", name="score_handler", requirements={ "token":"\w+" } methods={ "POST" })
*/
public function scoreHandler(string $token): JsonResponse
{
//called by bin/ws-broker.js
//do things here...
return $this->json([
'data' => 'Message Received!'
]);
}
/**
* @Route("/{token}", name="home", requirements={ "token":"\w+" } methods={ "GET" })
*/
public function index(string $token): Response
{
//called by Apache/Browser
return $this->render('home/index.html.twig', [
'controller_name' => 'HomeController',
'token' => $token
]);
}
}
Remove the bin/ws-broker.js from the frontend, send the token to the Broker and add a response handler
<!-- home/index.html.twig -->
<div id="chat"></div>
<div>
<div class="form-group">
<label for="name">Name:</label> <input type="text" id="name">
</div>
<div class="form-group">
<label for="message">Message:</label> <input type="text" id="message">
</div>
<button type="button" id="sendBtn" class="btn-primary">Send</button>
</div>
<script type="text/javascript">
const socket = new WebSocket("ws://localhost:8080");
socket.onmessage = function(evt) {
window.console.log(evt.data);
//handle WebSocket Server Response...
};
document.getElementById("sendBtn").addEventListener("click", function() {
const message = {
name: document.getElementById("name").value,
message: document.getElementById("message").value,
token: "{{ token }}" //<------ SEND TOKEN to WebSocket Server
};
socket.send(JSON.stringify(message));
});
</script>