I have an Express server that listens for webhook events from an external API. When it received these events, I want the handler for that http request to send a message to a WebSocket client. Here is some basic code to illustrate what I mean
The external API will send a HTTP POST request to an endpoint on my Express server, lets say it looks like this:
app.post('/external-api', (req, res) => {
// webhook payload will be in req.body
})
In that handler for /external-api I'd like to send a message to a client who's connected to the server via WebSocket. As of right now, I'm using the npm ws library, so I want the logic to look something like this
app.post('/external-api', (req, res) => {
ws.broadcast(req.body);
})
Is there a way to do this? I'm open to using other libraries but I just need a way to send a message to a WebSocket client from a HTTP POST request handler in Express.
Here is an example:
index.ts:
import express from 'express';
import WebSocket from 'ws';
import http from 'http';
import path from 'path';
import faker from 'faker';
const app = express();
const port = 3000;
const server = http.createServer(app);
const wss = new WebSocket.Server({ server });
wss.on('connection', (ws: WebSocket) => {
console.log('establish websocket connection');
ws.on('message', (message) => {
console.log('received: %s', message);
});
});
app.get('/client/:id', (req, res) => {
res.sendFile(path.resolve(__dirname, `./public/client-${req.params.id}.html`));
});
app.get('/external-api', (req, res) => {
wss.clients.forEach((client) => {
if (client.readyState === WebSocket.OPEN) {
client.send(faker.internet.email());
}
});
res.sendStatus(200);
});
server.listen(port, () => console.log(`http server is listening on http://localhost:${port}`));
client-1.html:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>client 1</title>
</head>
<body>
<p>client 1</p>
<button id="test">Test</button>
<script>
(function() {
window.onload = function() {
const socket = new WebSocket('ws://localhost:3000');
// Connection opened
socket.addEventListener('open', function(event) {
socket.send('Hello Server! Client - 1');
});
// Listen for messages
socket.addEventListener('message', function(event) {
console.log('Message from server ', event.data);
});
const btn = document.getElementById('test');
btn.addEventListener('click', async () => {
try {
const res = await fetch('http://localhost:3000/external-api');
console.log(res);
} catch (error) {
console.log(error);
}
});
};
})();
</script>
</body>
</html>
client-2.html:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>client 2</title>
</head>
<body>
<p>client 2</p>
<button id="test">Test</button>
<script>
(function() {
window.onload = function() {
const socket = new WebSocket('ws://localhost:3000');
// Connection opened
socket.addEventListener('open', function(event) {
socket.send('Hello Server! Client - 2');
});
// Listen for messages
socket.addEventListener('message', function(event) {
console.log('Message from server ', event.data);
});
const btn = document.getElementById('test');
btn.addEventListener('click', async () => {
try {
const res = await fetch('http://localhost:3000/external-api');
console.log(res);
} catch (error) {
console.log(error);
}
});
};
})();
</script>
</body>
</html>
Now, click the button of client 1, send HTTP request to /external-api.
The console logs of client 1:
The console logs of client 2:
The logs of server:
http server is listening on http://localhost:3000
establish websocket connection
received: Hello Server! Client - 1
establish websocket connection
received: Hello Server! Client - 2
As you can see, the server broadcast fake emails to client 1 and client 2.
Related
I'm having issues with Node SocketIo client not emitting data. So when the client connects in the index.html does log the "Connected This Is A Test", however it does not socket.emit('cool'), no errors nor does it seem to log on server.js. I'm not sure why its not emitting or the server isnt listening.
Server.js
const path = require('path');
const http = require('http');
const express = require('express');
const socketio = require('socket.io');
const app = express();
const server = http.createServer(app);
const io = socketio(server);
const PORT = 3002;
app.use(express.static(path.join(__dirname, 'public')));
// run when client connects
io.on('connection', () => {
console.log('New WS connection...');
io.emit('connection', 'This Is A Test');
});
io.on('cool', (msg) => {
console.log(msg);
});
server.listen(PORT, () => console.log(`server running on port ${PORT}`));
index.html
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="content-type" content="text/html; charset=utf-8" />
<title></title>
</head>
<body>
<script src="/socket.io/socket.io.js"></script>
<script>
var socket = io.connect('http://' + document.domain + ':' + location.port);
socket.on('connection', function(data){
console.log("connected", data);
socket.emit('cool', 'MSG');
});
</script>
</body>
</html>
On your server, you need to be listening for the cool message on a specific connected socket, not on the io object. The io object does not have specific socket messages other than announcing a newly connected socket. To listen for messages from a specific socket, you need a listener on the connected socket itself. The usual place to add that listener is in the connection event where you are presented with the newly connected socket object.
So change this:
// run when client connects
io.on('connection', () => {
console.log('New WS connection...');
io.emit('connection', 'This Is A Test');
});
io.on('cool', (msg) => {
console.log(msg);
});
to this:
// run when client connects
io.on('connection', (socket) => {
console.log('New WS connection...');
// send a test event back to the socket that just connected
socket.emit('test', 'This Is A Test');
// listen for the cool message on this new socket
socket.on('cool', (msg) => {
console.log(msg);
});
});
Also, you really should not be emitting event names used by the system like connection. That's why I changed the event name to test so it won't conflict with names that socket.io itself is using.
I tried many different npm web socket libraries (WebSocket, ws, express-ws and more), and in EVERYONE of them I have the same problem. When I try to send a websocket message, the connection closes.
I have no problem receiving messeges, only sending.
Here is one simple example of one test with express and ws libraries:
Node.JS side:
var server = require('http').createServer()
, url = require('url')
, WebSocketServer = require('ws').Server
, wss = new WebSocketServer({ server: server })
, express = require('express')
, app = express()
, port = 8080
, fs = require('fs');
app.use(function (req, res) {
var index = fs.readFileSync('./interface/index.html');
res.end(index);
});
wss.on('connection', function connection(ws) {
var location = url.parse(ws.upgradeReq.url, true);
console.log('open ws');
ws.on('message', function incoming(message) {
console.log('received: %s', message);
ws.send('test back');
});
ws.on('close', function () {
console.log('ws connection closed.');
})
});
server.on('request', app);
server.listen(port, function () { console.log('Listening on ' + server.address().port) });
and the browser side ("./interface/index.html"):
window.WebSocket = window.WebSocket || window.MozWebSocket;
var port = 8080;
var ip = window.location.hostname;
var connection;
connection = new WebSocket('ws://' + ip + ':' + port);
connection.onopen = function () {
// connection is opened and ready to use
console.log('Web socket connection with', ip + ':' + port);
//sendQueue(msgQueue);
};
connection.onerror = function (error) {
// an error occurred when sending/receiving data
console.log('Web socket erorr with', ip + ':' + port + ':', error);
};
connection.onmessage = function (message) {
// try to decode json (I assume that each message from server is json)
console.log("Received ws");
// handle incoming message
};
connection.onclose = function (){
console.log("Web socket connection lost.");
};
function sendws() {
connection.send("test");
console.log("sent ws");
return false;
}
<head>
<meta charset="utf-8">
<title>Simple Web Socket test</title>
<!--<meta name="description" content="Simple Web Socket">-->
<meta name="author" content="Binoman">
<link rel="stylesheet" href="css/styles.css?v=1.0">
<!--[if lt IE 9]>
<script src="https://cdnjs.cloudflare.com/ajax/libs/html5shiv/3.7.3/html5shiv.js"></script>
<![endif]-->
</head>
<body>
<button type="button" name="button" onclick="sendws()">Send</button>
</body>
I have no idea why this is happening. I updated Node.js to 6.7.
Running on windows 10.
I appreciate the help!
Thanks!
I discovered it was my antivirus's web protection that blocked the connection. I made an exception for my localhost address and it working perfectly now.
I use the following module and it works fine for reverse proxy
https://github.com/nodejitsu/node-http-proxy
currently I've used the code like the following example
httpProxy.createServer({
target: 'ws://localhost:9014',
ws: true
}).listen(8014);
my question is how can I check/simulate that the websockets are working?
Any test will be helpful...
In response to the OP's request for browser test, I modified my original solution to proxy both HTTP and WS traffic to a server where an index.html file is served. This file then connects the browser to the proxy server via WebSocket, which the proxy then proxies to the main server. A simple message is printed on the browser document from the main server.
So that there is no need to copy/paste anything, I created this repo with full instruction: https://github.com/caasjj/httpproxy.git
Here is the code in case others want to look at it here. To run the whole thing, create the two server files and the index.html file, start the servers with node proxyreceiver.js and node proxyserver.js and then navigate to localhost:8014/index.html.
(proxyserver.js):
var httpProxy = require('http-proxy');
var http = require('http');
var proxy = new httpProxy.createProxyServer({
target: {
host: 'localhost',
port: 9014
}
});
var proxyServer = http.createServer(function (req, res) {
proxy.web(req, res);
});
//
// Listen to the `upgrade` event and proxy the
// WebSocket requests as well.
//
proxyServer.on('upgrade', function (req, socket, head) {
proxy.ws(req, socket, head);
});
proxyServer.listen(8014);
(proxyreceiver.js):
var app = require('http').createServer(handler)
var io = require('socket.io')(app);
var fs = require('fs');
app.listen(9014);
function handler (req, res) {
res.writeHead(200);
fs.readFile('index.html', function(err, data){
res.end(data);
})
}
io.on('connection', function (socket) {
socket.emit('data', { message: 'Hello World!' });
socket.on('resp', function(msg) {
console.log('Got message: ', msg);
});
});
(index.html):
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Web Socket Proxy Test</title>
<script src="https://cdn.socket.io/socket.io-1.3.5.js"></script>
<script>
var socket = io('http://localhost:8014');
var p = document.createElement("p")
socket.on('data', function (data) {
console.log('Got', data);
p.innerHTML = "Received:" + data.message;
document.body.appendChild(p);
});
</script>
</head>
<body>
<h1>Test ProxyServer</h1>
</body>
</html>
The best way to test is to create a client to connect to it.
there are many ws modules around. Or you can use this: https://www.websocket.org/echo.html just put your url there and test it.
I’m having a problem getting started with Node.js.
I’ve created a basic server that I know works, because if I navigate to http://localhost:5000 in my browser I get the expected message. However, I’m having trouble then connecting to this server on the client side with a basic HTML page.
My Node.js app looks like this:
var http = require('http');
var socket = require('socket.io');
var port = process.env.PORT || 5000;
var players;
var app = http.createServer(function(request, response) {
response.write('Server listening to port: ' + port);
response.end();
}).listen(port);
var io = socket.listen(app);
function init() {
io.configure(function() {
io.set('transports', [ 'xhr-polling' ]);
io.set('polling duration', 10);
});
io.sockets.on('connection', onSocketConnection);
};
function onSocketConnection(client) {
console.log('New connection');
console.log(client);
};
init();
My HTML page looks like this (based on https://github.com/mongolab/tractorpush-server/blob/master/index.html):
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8" />
</head>
<body>
<script src="/socket.io/socket.io.js"></script>
<script>
var socket = io.connect('/');
socket.on('all', function(data) {
console.log(data);
});
socket.on('complex', function(data) {
console.log(data);
});
</script>
</body>
</html>
I understand that the sockets.io.js file is automatically generated by socket.io, but I just get the following error when I view my index.html file:
Uncaught ReferenceError: io is not defined
How do I actually connect to my server?
I'm starting playing with node.js and as everybody, I want do a chat.
My idea is run node.js with socket.io in the port 9090, for example, and my client html in the port 8080. My html client will be served independent.
My server:
var sys = require('sys');
var express = require('express');
var io = require('socket.io');
var app = express.createServer();
app.listen(8080);
var socket = io.listen(app);
socket.on('connection', function (client) {
client.on('message', function (msg) {
socket.broadcast(msg);
});
client.on('disconnect', function () {
});
});
My client:
<!DOCTYPE html>
<html>
<head>
<script src="http://ajax.googleapis.com/ajax/libs/jquery/1.6.2/jquery.min.js"></script>
<script src="http://cdn.socket.io/stable/socket.io.js"></script>
<script>
$(document).ready(function () {
var socket = new io.Socket("localhost", {port: 8080});
socket.on('connect', function () {
socket.send('A client connected.');
});
socket.on('message', function (message) {
$('div#messages').append($('<p>'), message);
});
socket.on('disconnect', function () {
console.log('disconnected');
});
socket.connect();
$('input').keydown(function (event) {
if(event.keyCode === 13) {
socket.send($('input').val());
$('input').val('');
}
});
});
</script>
</head>
<body>
<input type="text" style="width: 300px;" />
<div id="messages" style="border:solid 1px #000;"> </div>
</body>
</html>
I'm running in ubuntu 11.04 with node.js v0.4.10.
The server works fine, but the client can't do connection, in the console.log on google Chrome I received this message:
XMLHttpRequest cannot load http://localhost:8080/socket.io/xhr-polling//1311465961485. Origin http://localhost is not allowed by Access-Control-Allow-Origin.
The server.js is in a folder in /var/www/cliente/chat/public.
What's the problem?
Your client code is not actually being served from port 8080 as you want.
var sys = require('sys');
var express = require('express');
var io = require('socket.io');
var app = express.createServer();
app.listen(8080);
app.use(express.static(__dirname));
app.get('/', function(req, res){
res.render('index.html', { title: 'Chat' });
});
var socket = io.listen(app);
socket.on('connection', function (client) {
client.on('message', function (msg) {
socket.broadcast(msg);
});
client.on('disconnect', function () {
});
});
This should fix your Access-Control-Allow-Origin errors. Execute node server.js and connect to http://localhost:8080. A couple additional notes:
Make sure you have installed socket.io 0.6.x since that's what you are including in your html file. 0.7.x is backwards incompatible.
With this configuration you'll be running socket.io on the same port you are serving your page from (as opposed to 9090).
When I updated my client to:
<!DOCTYPE html>
<html>
<head>
<script src="http://ajax.googleapis.com/ajax/libs/jquery/1.6.2/jquery.min.js"></script>
<script src="http://localhost:8080/socket.io/socket.io.js"></script>
<script>
var socket = io.connect("http://localhost", {port: 8080});
socket.on('connect', function () {
socket.send('A client connected.');
});
socket.on('message', function (msg) {
$('div#messages').append($('<p>'), msg);
});
socket.on('disconnect', function () {
console.log('disconnected');
});
$(document).ready(function(){
$('#btn_send').click(function (event) {
socket.send($('#txt_msg').val());
$('#txt_msg').val('');
});
});
</script>
</head>
<body>
<input type="text" id="txt_msg" style="width: 300px;" /><input type="button" id="btn_send" value="send" />
<div id="messages" style="border:solid 1px #000;"> </div>
</body>
</html>
Everything worked.
I was using a version 0.7 of the socket.io that was the problem: https://github.com/LearnBoost/Socket.IO/wiki/Migrating-0.6-to-0.7
You cannot make AJAX requests to URLs that are not on the same hostname and port as the current page. It's a security restriction in all web browsers.