How to send 'text/event-stream' data from express.js? - javascript

Trying to get a basic server-sent-event working with express.js on server side:
index.js:
const app = require('express')();
const index_html = require('path').join(__dirname, 'index.html');
app.get('/', (req, res) =>
{
res.sendFile(index_html);
});
app.get('/data', (req, res) =>
{
res.header('Cache-Control', 'no-cache');
res.header('Content-Type', 'text/event-stream');
setInterval(() =>
{
res.write(new Date().toISOString() + '\n\n');
}, 1000);
});
app.listen('7000');
index.html:
<!doctype html>
<head>
<title>Hello World</title>
</head>
<body>
<h1 id='result'>No Data Yet</h1>
<script>
var source = new EventSource('data');
source.onmessage = function(event)
{
console.log('invoked');
document.getElementById('result').innerHTML = event.data + '<br>';
};
</script>
</body>
The source.onmessage never triggers.

Server side events need to send data in a particular (text) format, specifically, to send data, you need a line of text in the format
data: text to send goes here
other types of data in a single event can be type, id, and retry
any line that doesn't start with
data:
type:
id:
retry:
is ignored, hence why you were not getting any events
simple change
setInterval(() =>
{
res.write('data: ' + new Date().toISOString() + '\n\n');
}, 1000);
Though, I'd write it like
setInterval(() => res.write(`data: ${new Date().toISOString()}\n\n`), 1000);
Further reading MDN documentation

Related

How to send data from Node js server to client side?

I currently set up a node server which gets some data submitted from a html page and uses it to fetch data from an API. now I would like to display this data in a graphic format to a new html page (or even the same if possible).
In order to do this I think I should first send the data to the client side js. So that it gets the data to create the graph onto the new html page. But how would I do this? I tried to look for some examples unsuccessfully.
Here's a failing attempt at this (I omitted some code that I think wasn't influencial):
//server (Node JS)
const app = express();
app.use(express.json());
app.use(express.urlencoded( {extended: true} ));
const port = process.env.PORT || 8080;
let values;
app.get('/', function(req, res) {
res.sendFile(path.join(__dirname, '/index.html'));
});
async function fillArrays (from, to) {
...
}
const fetchData = async () => {
...
values = ...;
}
app.post('/input', async function(req,res){
await fillArrays(req.body.a, req.body.b);
console.log("End");
res.sendFile(path.join(__dirname, '/graph.html'));
res.json(await fetchData());
});
app.listen(port);
console.log('Server started at http://localhost:' + port);
graph.html:
<head>
<script src='https://cdn.plot.ly/plotly-2.14.0.min.js'></script>
<script src='https://cdnjs.cloudflare.com/ajax/libs/d3/3.5.17/d3.min.js'></script>
<script type="text/javascript" src="chart.js"></script>
</head>
<body>
<div id='myDiv'></div>
</body>
chart.js :
let dataset;
//attempt at getting data from server side
const promise = fetch('/input');
promise.then(response => {
if(!response.ok){
console.error(response)
} else {
return console.log(response);
}
}).then(result => {
dataset = result;
})
let range1 = Math.min(dataset[0]);
let range2 = Math.max(dataset[0]);
var trace = {
...
}
var data = trace;
var layout = {
...
};
Plotly.newPlot('myDiv', data, layout);

How can I preview a PDF on the front end using it's data sent from the back-end with Expressjs?

I sent a PDF stored on my back-end using Expressjs.
// FILE: app.js
const express = require('express');
const app = express();
var fs = require('fs');
app.use(express.static('./methods-public'));
app.get('/api/pdf', (req, res, next) => {
const path = './dog.pdf';
if (fs.existsSync(path)) {
res.contentType('application/pdf');
fs.createReadStream(path).pipe(res);
} else {
res.status(500);
console.log('File not found');
res.send('File not found');
}
});
app.listen(5000, () => {
console.log('Server is listening on port 5000....');
});
My front-end received the raw data using Javascript with Axios.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
</head>
<body>
<div class="result"></div>
<script
src="https://cdnjs.cloudflare.com/ajax/libs/axios/0.21.1/axios.min.js"
integrity="sha512-bZS47S7sPOxkjU/4Bt0zrhEtWx0y0CRkhEp8IckzK+ltifIIE9EMIMTuT/mEzoIMewUINruDBIR/jJnbguonqQ=="
crossorigin="anonymous"
></script>
<script>
const result = document.querySelector('.result');
const fetchPDF = async () => {
try {
const { data } = await axios.get('/api/pdf');
result.innerHTML = '<h3>' + data + '</h3>';
} catch (error) {
result.innerHTML = `<div class="alert alert-danger">Can't Fetch PDF</div>`;
}
};
fetchPDF();
</script>
</body>
</html>
My front end-knowledge is very limited. I've been struggling to understand how I can use this data to render the pdf then display it within a Canvas.

"TypeError: Cannot read property 'replace' of undefined"

I have been trying to make my first express application and I have been getting a error with range.replace() and I tried searching for a fix and I couldn't find one
This accured while I was trying to stream video.
And this is my first time using express so ignore the html scripts at the app.send() :)
My code is:
const express = require('express');
const app = express();
const fs = require('fs')
const path = require('path')
require('dotenv').config();
const { auth, requiresAuth } = require('express-openid-connect');
app.use(
auth({
authRequired: false,
auth0Logout: true,
issuerBaseURL: process.env.ISSUER_BASE_URL,
baseURL: process.env.BASE_URL,
clientID: process.env.CLIENT_ID,
secret: process.env.SECRET,
})
);
app.get('/profil', requiresAuth(), (req, res) => {
const profileJSON = JSON.stringify(req.oidc.user);
var obj = JSON.parse(profileJSON);
function emailVerified(){
if (obj.email_verified == "true"){
return "Doğrulandı";
}
else {
return "Doğrulanmadı";
}
}
res.send(
`
<!DOCTYPE html>
<head>
<title>Profil sayfası</title>
</head>
<body>
<h1>${obj.nickname}</h1>
<img src="${obj.picture}"></img>
<h2>Gerçek isim: ${obj.name}</h2>
<h2>E-posta: ${obj.email}</h2>
<h2>E-Posta Doğeulanma Durumu: ${obj.email_verified}</h2>
<h2>Ülke: ${obj.locale}<h2>
</body>
`
);
})
app.get('/', (req, res)=>{
res.send(req.oidc.isAuthenticated() ? `
<!DOCTYPE html>
<head>
<title>Murat Ödev Sayfası</title>
</head>
<body>
<h1>Murat Ödev Sayfası</h1>
<h2>Giriş Durumu: Giriş yapıldı<h2>Çıkış yap
Profil sayfası
Video test
</body>
` : `
<!DOCTYPE html>
<head>
<title>Murat Ödev Sayfası</title>
</head>
<body>
<h1>Murat Ödev Sayfası</h1>
<h2>Giriş Durumu: Giriş yapılmadı<h2>Giriş yap
</body>
`)
})
app.get('/video', requiresAuth(),(req, res) => {
const range = req.headers.range;
if (!range) {
res.status(400).send("Requires Range header");
}
// get video stats (about 61MB)
const videoPath = "video.mp4";
const videoSize = fs.statSync("video.mp4").size;
// Parse Range
// Example: "bytes=32324-"
const CHUNK_SIZE = 10 ** 6; // 1MB
const start = Number(range.replace("/\D/g", ""));
const end = Math.min(start + CHUNK_SIZE, videoSize - 1);
// Create headers
const contentLength = end - start + 1;
const headers = {
"Content-Range": `bytes ${start}-${end}/${videoSize}`,
"Accept-Ranges": "bytes",
"Content-Length": contentLength,
"Content-Type": "video/mp4",
};
// HTTP Status 206 for Partial Content
res.writeHead(206, headers);
// create video read stream for this particular chunk
const videoStream = fs.createReadStream(videoPath, { start, end });
// Stream the video chunk to the client
videoStream.pipe(res);
})
const port = process.env.PORT || 3000;
app.listen(port, () => {
console.log(`Listening on port ${port}`);
})
and the error is:
TypeError: Cannot read property 'replace' of undefined
I hope theres someone that can help me
You do the following to see is range defined
if (!range) {
res.status(400).send("Requires Range header");
}
You are correctly looking for the error condition, but the problem here is you are not exiting out so it continues and hence why you are getting the error. Add return to exit the function
if (!range) {
res.status(400).send("Requires Range header");
return;
}
You are calling replace() on a variable that is undefined. If you debug your code you can easily see this.
You do check whether range is defined. If it is undefined you send a 400 status. But this does not end the function. Again, this can easily be seen when debugging your code.
You should return inside the then block or put the rest of the code inside an else block.
Why is range undefined? Apparently this header is not in the request. Also, the offical way to get a header according to the Express documentation is req.get('range').

How do I trigger the server to send an SSE by clicking a button on the page?

I'm learning about server-sent events and wanted to try my own example. All of the examples I've found set up a server that just uses an interval to send new events to the EventSource on the HTML page. What I'd like to do instead is to have a button on the page that makes a call to the server that will trigger a new event to be sent to the EventSource object.
index.html
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Home</title>
</head>
<body>
<h1>Home</h1>
<div id="click-container">
</div>
<script type="text/javascript">
var source = new EventSource('/clicks');
var clickContainer = document.getElementById("click-container");
source.addEventListener("message", function(e) {
clickContainer.innerHTML = e.data + '<br>';
});
</script>
<h1>Clicker</h1>
<button type="button" id="clicker">Click Me</button>
<script type="text/javascript">
var btn = document.getElementById("clicker");
btn.addEventListener("click", function(e) {
var req = new XMLHttpRequest();
req.onreadystatechange = function() {
if (xhr.readyState == XMLHttpRequest.DONE) {
console.log(req.responseText);
}
};
req.open('GET', '/click', true);
req.send(null);
});
</script>
</body>
But for the server, which I'm doing in Node, I have no idea what to do. I've tried sending the request to the same URL that the EventSource was listening to, but that just resulted in that request getting caught up in the open connection and never completing. I just can't wrap my head around what I need to do, could I please have an explanation of how this should work? Thanks
You need to use a something like Redis as your pub/sub broker. I wrote a two part blog series on how to do this with Node.
Part 1
Part 2
For this you can use fetch()
client.js:
document.getElementById("clicker").onclick = function() {
data = { something: "123"}
fetch("/sendData", {
method: "POST",
body: data,
headers: { "Content-Type": "application/json"
}).then(res => res.json())
.then(resp => {
//do something with returned data
alert(resp) // { message: "hello" }
})
}
server.js:
const express = require("express")
const app = express()
const bodyParser = require("body-parser")
app.use(bodyParser.json())
app.use(bodyParser.urlencoded({ extended: true }))
app.post("/sendData", (req, res) => {
console.log(req.body.something) // "123"
//return a response to the client side
res.send({ message: "hello" })
})

Simple Way to Implement Server Sent Events in Node.js?

I've looked around and it seems as if all the ways to implement SSEs in Node.js are through more complex code, but it seems like there should be an easier way to send and receive SSEs. Are there any APIs or modules that make this simpler?
Here is an express server that sends one Server-Sent Event (SSE) per second, counting down from 10 to 0:
const express = require('express')
const app = express()
app.use(express.static('public'))
app.get('/countdown', function(req, res) {
res.writeHead(200, {
'Content-Type': 'text/event-stream',
'Cache-Control': 'no-cache',
'Connection': 'keep-alive'
})
countdown(res, 10)
})
function countdown(res, count) {
res.write("data: " + count + "\n\n")
if (count)
setTimeout(() => countdown(res, count-1), 1000)
else
res.end()
}
app.listen(3000, () => console.log('SSE app listening on port 3000!'))
Put the above code into a file (index.js) and run it: node index
Next, put the following HTML into a file (public/index.html):
<html>
<head>
<script>
if (!!window.EventSource) {
var source = new EventSource('/countdown')
source.addEventListener('message', function(e) {
document.getElementById('data').innerHTML = e.data
}, false)
source.addEventListener('open', function(e) {
document.getElementById('state').innerHTML = "Connected"
}, false)
source.addEventListener('error', function(e) {
const id_state = document.getElementById('state')
if (e.eventPhase == EventSource.CLOSED)
source.close()
if (e.target.readyState == EventSource.CLOSED) {
id_state.innerHTML = "Disconnected"
}
else if (e.target.readyState == EventSource.CONNECTING) {
id_state.innerHTML = "Connecting..."
}
}, false)
} else {
console.log("Your browser doesn't support SSE")
}
</script>
</head>
<body>
<h1>SSE: <span id="state"></span></h1>
<h3>Data: <span id="data"></span></h3>
</body>
</html>
In your browser, open localhost:3000 and watch the SSE countdown.
I'm adding a simple implementation of SSE here. It's just one Node.js file.
You can have a look at the result here: https://glossy-ox.glitch.me/
const http = require('http');
const port = process.env.PORT || 3000;
const server = http.createServer((req, res) => {
// Server-sent events endpoint
if (req.url === '/events') {
res.writeHead(200, {
'Content-Type': 'text/event-stream',
'Cache-Control': 'no-cache',
...(req.httpVersionMajor === 1 && { 'Connection': 'keep-alive' })
});
const refreshRate = 1000; // in milliseconds
return setInterval(() => {
const id = Date.now();
const data = `Hello World ${id}`;
const message =
`retry: ${refreshRate}\nid:${id}\ndata: ${data}\n\n`;
res.write(message);
}, refreshRate);
}
// Client side
res.writeHead(200, {'Content-Type': 'text/html'});
res.end(`
<!DOCTYPE html>
<html lang="en" dir="ltr">
<head>
<meta charset="utf-8">
<title>SSE</title>
</head>
<body>
<pre id="log"></pre>
</body>
<script>
var eventSource = new EventSource('/events');
eventSource.onmessage = function(event) {
document.getElementById('log').innerHTML += event.data + '<br>';
};
</script>
</html>
`);
});
server.listen(port);
server.on('error', (err) => {
console.log(err);
process.exit(1);
});
server.on('listening', () => {
console.log(`Listening on port ${port}`);
});
If you're using express this is the easiest way https://www.npmjs.com/package/express-sse
on BE:
const SSE = require('express-sse');
const sse = new SSE();
...
app.get('/sse', sse.init);
...
sse.send('message', 'event-name');
on FE:
const EventSource = require('eventsource');
const es = new EventSource('http://localhost:3000/sse');
es.addEventListener('event-name', function (message) {
console.log('message:', message)
});
I found SSE implementation in node.js.
Github link: https://github.com/einaros/sse.js
NPM module:https://www.npmjs.com/package/sse
Will above link helps you ?
**client.js**
var eventSource = new EventSource("/route/events");
eventSource.addEventListner("ping", function(e){log(e.data)});
//if no events specified
eventSource.addEventListner("message", function(e){log(e.data)});
**server.js**
http.createServer((req, res)=>{
if(req.url.indexOf("/route/events")>=){
res.setHeader('Connection', 'keep-alive');
res.setHeader("Cache-Control", "no-cache");
res.setHeader("Content-Type", "text/event-stream");
let event = "event: ping";
let id = `id: ${Date.now()}`;
let data = {
message:`hello #${new Date().toString()}`
}
data = "data: "+JSON.stringify(data);
res.end(`${event}\n${id}\n${data}\n\n`);
}
}).listen(PORT)
After looking at the other answers I finally got this working, but what I ended up having to do was a little different.
[package.json] Use express-sse:
The exact version of express-sse is very important. The latest tries to use res.flush(), but fails and crashes the http server.
"express-sse": "0.5.1",
[Terminal] Install express-sse:
npm install
[app.js] Use the router:
app.use(app.baseUri, require('./lib/server-sent-events').router);
[server-sent-events.js] Create sse library:
The call to pause() is the equivalent of flush(), which was removed from express. It ensures you'll keep getting messages as they are sent.
var express = require('express');
const SSE = require('express-sse');
const sse = new SSE();
var router = express.Router();
router.get('/sse', sse.init)
module.exports = {
send,
router
};
async function send(message) {
sse.send(message.toProperCase(), 'message');
await pause();
}
function pause() {
return new Promise((resolve, reject) => {
setImmediate(resolve)
})
}
[your-router.js] Use the sse library and call send:
var express = require('express');
var serverSentEvents = require('../lib/server-sent-events');
var router = express.Router();
router.get('/somepath', yourhandler);
module.exports = router;
async function yourhandler (req, res, next) {
await serverSentEvents.send('hello sse!'); // <<<<<
}
[your-client-side.js] Receive the sse updates:
I recommend you keep the event.data.replace(/"/g,'') because express-sse tacks on enclosing quotes and we don't want those.
const eventSource = new EventSource('http://yourserver/sse');
eventSource.onmessage = function(event) {
document.getElementById("result").innerHTML = event.data.replace(/"/g,'') + '...';
};
You should be able to do such a thing using Socket.io. First, you will need to install it with npm install socket.io. From there, in your code you will want to have var io = require(socket.io);
You can see more in-depth examples given by Socket.IO
You could use something like this on the server:
var express = require('express');
var app = express();
var server = require('http').createServer(app);
var io = require('../..')(server);
var port = process.env.PORT || 3000;
server.listen(port, function () {
console.log('Server listening at port ' + port);
});
app.use(express.static(__dirname + '/public'));
io.on('connection', function (socket) {
socket.emit('EVENT_NAME', {data});
});
And something like this on the client:
<script src="socket_src_file_path_here"></script>
<script>
var socket = io('http://localhost');
socket.on('EVENT_NAME', function (data) {
console.log(data);
//Do whatever you want with the data on the client
});
</script>

Categories