I know Javascript but very new to NodeJS. I want to reuse http request anywhere in my code, outside of server.js. Please take a look:
server.js
var http = require('http');
var mongodb = require('./db');
var settings = require('./settings');
var oauth = require('./oauth');
//start mongodb connection
mongodb.connect(function(error){
if (error) console.error(error);
else {
//set db as mongodb client
var db = mongodb.use();
//start http server
http.createServer(function(request,response){
//set POST body
request.body = '';
request.on('error',function(error){
console.error(error);
response.statusCode = 404;
response.end();
}).on('data',function(chunk){
request.body += chunk;
}).on('end',function(){
//database and http server ready, start logic
//Now I want to "require" a file here, such as test.js and call "request.headers" to work
});
}).listen(settings.httpPort,function(){
console.log('Server listening on '+settings.httpServer+':'+settings.httpPort);
});
}
});
And now I want to require test.js which do the following thing
test.js
console.log(request.headers);
Note: I don't want to use any framework like Express. I want to write my own things from scratch. Please help me, thank you so much.
If I understand correctly, the problem seems to be this: You want to access the
result of an asynchronous function (the HTTP request) in other modules. However,
Node's require() is synchronous; there is no asynchronous require(). There
are a few solutions to this, none of which will be unfamiliar if you know
JavaScript.
The simplest solution is to wrap your server.js module in a function that takes
a callback. Then, call the callback once the request is available, like so:
// server.js
'use strict';
// ...
module.exports = function(callback) {
// ...
http.createServer((req, res) => {
req.body = '';
// Call end with error on error
req.on('error', err => res.end(err));
// Append chunks to body
req.on('data', chunk => req.body += chunk);
// Call callback here
req.on('end', err => {
// Call with error as first argument on error
if (err) callback(err);
// Call with request as second argument on success
else callback(null, req);
});
}).listen(/*...*/);
// ...
};
And in your test.js file:
// test.js
'use strict';
const server = require('./server');
// Do something with the request here.
server((err, req) => {
if (err) console.error(err);
else console.log(req.headers);
});
There is a problem with this approach. Every time you want to access the
request, your server function will run all over again. What if you want to run
the server once and then have access to the request as many times as you want?
Consider using Node's events module for this. In your server.js file, you can
export an EventEmitter instance instead of a function. Emit appropriate events
in that module, and then you can add listeners in any other module that needs
access to the request. Your server.js file will look something like this:
// server.js
'use strict';
const EventEmitter = require('events');
const emitter = new EventEmitter();
// ...
http.createServer((req, res) => {
req.body = '';
req.on('error', err => res.end(err));
req.on('data', chunk => req.body += chunk);
// Emit events here:
req.on('end', err => {
// Emit 'error' event on error.
if (err) emitter.emit('error', err);
// Emit 'data' event on success.
else emitter.emit('data', req);
});
}).listen(/*...*/);
// ...
module.exports = emitter;
And in your test.js file:
// test.js
'use strict';
const server = require('./server');
// Do something on error event.
server.on('error', console.error);
// Do something on data event.
server.on('data', req => {
console.log(req.headers);
});
Related
I have a fairly simple express server that is designed to take external client data and publish it via mqtt to a gateway. It works perfectly with a hardcoded variable but I can't figure out how to extract the actual data from the POST request, which is as follows (it prints to the console just fine):
const postData = app.post('/send-data', function (req, res) {
console.log('connected', req.body);
res.status(200).json(req.body)
});
I need to get the req.body data out of that and into the following code that publishes the data to the topic:
client.on('connect', function () {
console.log('connected!');
client.publish('iot-2/type/wtlType/id/channel100/evt/event/fmt/json', publishData);
client.end();
});
publishData will just be the stringified json response.
This is the create server code if that helps:
https.createServer(options, app).listen(30002, () => {
console.log('Listening')
});
If I understand correctly your question is about the logic of getting the req.body published by the client. If so, then something like this should work:
let connected = false;
client.on('connect', function () {
console.log('connected!');
connected = true;
});
const postData = app.post('/send-data', function (req, res) {
console.log('connected', req.body);
res.status(200).json(req.body)
client.publish('iot-2/type/wtlType/id/channel100/evt/event/fmt/json', JSON.stringify(req.body));
client.end(); // are you sure you want this? can there not be more messages to broadcast?
});
I'm trying to build an endpoint that will receive a request, emit the request data to a WebSocket client, wait for an event, then send back the response using express + socketio. This question is similar to it: Wait for socketio event inside express route
1) Receive request at http://localhost:3000/endpoint
2) Emit the event to web sockets as 'req'
3) Wait for 'res' event from ws
4) Send the received events details as the response of express.
Here is how I'm implemented:
server.js
var app = require('express')();
var http = require('http').Server(app);
var io = require('socket.io')(http);
app.get('/', (req, res) => {
res.sendFile(__dirname + '/index.html');
});
var socket;
io.on('connection', function (s) {
socket = s;
});
http.listen(3000);
app.get('/endpoint', function (req, res) {
console.log('new request')
io.emit('req', { data: 'hello' });
socket.on('res', function (data) {
res.status(200).json(data);
});
});
index.html
<script src="/socket.io/socket.io.js"></script>
<script>
var socket = io();
socket.on('req', (data) => {
console.log(data)
socket.emit('res', data);
});
</script>
The script works fine for the first request on /endpoint. But if i hit the url again, it says
Error [ERR_HTTP_HEADERS_SENT]: Cannot set headers after they are sent
to the client
Please note that:
socket.on('res', function (data) {
res.status(200).json(data);
});
Is being called each time a socket is sending a response, thus showing the above error. You should unbind the listener inside the callback function.
Keep an array of express responses and set an id to each request. So it can be used later and delete if needed.
var app = require('express')();
var http = require('http').Server(app);
var io = require('socket.io')(http);
var timeout = require('connect-timeout');
var uuid = require('uuidv4');
var _ = require('lodash');
app.use(timeout('10s'));
app.get('/', (req, res) => {
res.sendFile(__dirname + '/index.html');
});
let responses = []
io.on('connection', (socket) => {
socket.on('res', (e) => {
var obj = _.find(responses, r => r.id === e.id);
obj.res.send(e)
_.remove(responses, r => r.id === e.id);
})
})
app.get('/endpoint', (req, res) => {
const id = uuid()
io.emit('req', { id, ip: req.ip, header: req.headers, method: req.method });
responses.push({ id, res })
});
http.listen(3000);
You're trying to do two different async tasks for the same data.
First, take your socket.on('res'...) out of the app.get().
Send back res.status(200) immediately with express to say you received the request and it is processing. Then send the socket message to the client using socket.io when it's complete. You'll want to save the connected users socket client ID and use io.to(socketId).emit(...data...) to do this
the other option is what I always do (assuming it's not a crazy large payload of data you're sending) Just use socket.io for the whole process.
client
function makeRequest () {
socket.on('data-complete--error', function ( error ) {
// ... message to user :(
// also remove these handlers when finished
socket.off('data-complete--error');
socket.off('data-complete--success');
});
socket.on('data-complete--success', function ( data ) {
// ... message to user :)
// ... handle data
// also remove these handlers when finished
socket.off('data-complete--error');
socket.off('data-complete--success');
});
socket.emit('request-data');
}
makeRequest();
server
move your stuff out and handle without using express at all
Currently, I have a lot of linux based clients downloading firmware updates from my webserver.
After the client has successfully downloaded the firmware file, my server needs to execute a few scripts, which logs in to the client and performs some tasks.
Is there a way for a node server to keep track of the clients download progress, so I can execute the needed scripts once the file has been downloaded?
Ok so I will try.
If you serve your Firmware as static files through Apache/Nginx and direct url call. You don't get the progress inside your NodeJS script.
If you serve your files via stream inside a express controller you can listen to the progress. Look at this answer here https://stackoverflow.com/a/42273080/3168392
You will have to use a socket connection to make sure the node server gets update from the client of the progress of the file being downloaded.
Something like this
CLIENT_CODE
var socket = io('http://localhost');
socket.on('connect', function(){});
socket.on('data_reciving', parts.pop(),function(percentage){
if(parse_data(percentage) === 100){
client.socket.emit('downloadCompleted', {fileName:'test'});
}else{
//do nothing
}
});
SERVER_CODE:
sockets.on('connection', function (socket) {
//listen to the event from client
socket.on('downloadCompleted', function (data) {
connect_to_client();
do_some_operation();
socket.emit('ALLDONE',{some_data});
});
});
I guess this helps ,you can use this post for reference.
If you just want to run some code when a download has finished, you can use on-finished:
const onFinished = require('on-finished');
app.use((req, res, next) => {
onFinished(res, (err, res) => {
...log some data, perform some housekeeping, etc...
});
next();
});
As is, it will attach a "finished" listener to all responses, which is probably not what you want. Since this is plain Express middleware, you can attach it to specific routes instead (but how depends on how exactly the files are being served).
I found some code that seems to fit my needs.
With the code below, I can detect both the progress of a user's download from the server-side, and fire an event once the file transfer completes.
var http = require("http");
var fs = require("fs");
var filename = "./large-file";
var serv = http.createServer(function (req, res) {
var sent = 0;
var lastChunkSize = 0;
var stat = fs.statSync(filename);
res.setHeader('Content-disposition', 'attachment; filename=large-file.iso');
res.setHeader('Accept-Ranges', 'bytes');
res.setHeader('Keep-Alive', 'timeout=5, max=100');
res.writeHeader(200, {"Content-Length": stat.size});
var fReadStream = fs.createReadStream(filename, { highWaterMark: 128 * 1024 });
fReadStream.on('data', function (chunk) {
if(!res.write(chunk)){
fReadStream.pause();
lastChunkSize = chunk.length;
console.log('Sent', sent, 'of', stat.size);
}
});
fReadStream.on('end', function () {
console.log('Transfer complete.');
res.end();
});
res.on("drain", function () {
sent += lastChunkSize;
fReadStream.resume();
});
});
serv.listen(3001);
I'm new to node and got stuck with handling multiple async tasks.
Except from node, I've got another server (S1) which doesn't return data immediately to requests, it can returns multiple types of data and also can send notifications without requesting them specifically, so node have to listen to data from it , parse it and act accordingly.
The connection to this server (S1) is done by using:
S1 = net.createConnection({'host':S1Host, 'port': S1Port});
And node listens to data with:
S1.on('data', function(data){
S1DataParse(data);
});
I have to route the correct data (after parsing it) to a specific POST request.
app.post('/GetFooFromS1', function(req, res){
// Send request to S1
S1.write({'type':'foo'});
// If got the correct data sometime in the future, send response to the browser
res.setHeader('Content-Type', 'application/json');
res.json({'status':'success', 'value':S1FooData});
});
I tried to use the async module for that, but with no success.
What I was trying to do:
var asyncTasks = [];
app.post('/GetFooFromS1', function(req, res){
asyncTasks.push(function(callback){
// Send request to S1
S1.write({'type':'foo'});
});
async.parallel(asyncTasks, function(response){
res.setHeader('Content-Type', 'application/json');
res.json({'status':'success', 'value':response});
});
});
and another task in S1DataParse:
function S1DataParse(){
if(data.type='foo'){
asyncTasks.push(function(callback){
callback(data);
});
}
}
But, of course, the second task never added to the asyncTasks array. I really got stuck with that.
Can you please help me with that?
Thanks
-=-=-=- Edit -=-=-=-
Eventually, I came accross with events and EventEmitter().
From the POST request I call the function that sends requests to the data server (DataServerClientGet).
In this function I register a listener which will get the future data.
eventEmitter.on('getData', returnDataServerData);
It all works great except for one thing. Whenever I refresh the page or add other POST requests, I get an error:
Error: Can't set headers after they are sent.
It would be great if I solve this problem. Help me, please.
Thanks ;)
The whole code looks like this:
var express = require('express');
var app = express();
var http = require('http').Server(app);
var bodyParser = require('body-parser')
var net = require('net');
var events = require('events');
var dataServerHost = '127.0.0.1';
var dataServerPort = 12345;
var dataServerClient;
var logMsg;
var eventEmitter = new events.EventEmitter();
/*******************************************/
// Init
/*******************************************/
app.use(bodyParser.json());
app.use(bodyParser.urlencoded({extended: true}));
app.use(express.static(__dirname + '/public'));
/*******************************************/
// Connect to the data server
/*******************************************/
DataServerConnect();
/*******************************************/
// Open listener on port 3000 (to browser)
/*******************************************/
http.listen(3000, function(){
logMsg = 'listening on *:3000';
console.log(logMsg);
});
/*******************************************/
// Routing
/*******************************************/
app.get('/', function(req, res){
res.sendFile(__dirname + '/index.html');
});
app.post('/GetDataFoo', function(req, res){
var msg;
var size;
msg ='\n{"Type":"Query", "SubType":"GetDataFoo","SearchFilter":""}';
size = msg.length;
logMsg = 'Client to DataServer: GetDataFoo';
console.log(logMsg);
DataServerClientGet('GetDataFoo', size, msg, res);
});
/*******************************************/
// Functions
/*******************************************/
function DataServerConnect(){
dataServerClient = net.createConnection({'host':dataServerHost, 'port': dataServerPort}, function(){
logMsg = 'Connected to DataServer ['+dataServerHost+':'+dataServerPort+']';
console.log(logMsg);
});
dataServerClient.on('data', function(data){
logMsg = 'DataServerData>>>\n'+data.toString()+'DataServerData<<<';
console.log(logMsg);
DataServerDataParse(data.toString());
});
dataServerClient.on('end', function(){
logMsg = 'Disconnected from DataServer';
console.log(logMsg);
});
}
function DataServerClientGet(type, size, msg, res){
dataServerClient.write('Type: Json\nSize: '+size+'\n\n'+msg, function(err){
var returnDataServerData = function returnDataServerData(results){
res.setHeader('Content-Type', 'application/json');
res.json({'status':'success', 'value':results});
}
eventEmitter.on('getData', returnDataServerData);
}
function DataServerDataParse(json){
if(json.Type=='GetDataFoo')
{
var MessageList = json.MessageList;
eventEmitter.emit('getData', MessageList);
}
}
-=-=-=- Edit -=-=-=-
The Error: Can't set headers after they are sent. caused by adding the same listener of the same type each time the DataServerClientGet was called and the res was sending multiple times.
I solved this one by adding: removeListener(event, listener)
right after the res, inside the function. Anyway, I think it's wrong and can cause problems if there will be multiple calling to DataServerClientGet with the same type etc.
There is a optional callback parameter that you can pass to write function(docs), something like :
S1.write({'type':'foo'},function(err){
if(err){
//Handle error
}else{
res.setHeader('Content-Type', 'application/json');
res.json({'status':'success', 'value':response});
}
})
This can work with post route , but in your 'data' listener ,you cant send data from server to client when there is not connection initialized by client (it is not bidireccional ) if you want bidireccional behavior you can check socket.io
Tried different methods, but the data is sent to a maximum of one or two clients. How to send data to all the clients connected to the server ? What am I doing wrong?
Server.js:
var PORT = 3000;
var options = {
// 'log level': 0
};
var express = require('express');
var app = express();
var http = require('http');
var server = http.createServer(app);
var io = require('socket.io').listen(server, options);
server.listen(PORT);
app.get('/', function (req, res) {
res.sendfile(__dirname + '/attantions/templates/.default/template.php');
});
io.sockets.on('connection', function (client) {
client.on('attantion', function (data) {
try {
// Tried so
io.sockets.volatile.emit('attantion', data);
// And tried so
io.sockets.emit('attantion', data);
client.emit('attantion', data);
client.broadcast.emit('attantion', data );
} catch (e) {
console.log(e);
client.disconnect();
}
});
});
Client.js:
socket.emit("attantion", data);
socket.on('attantion', function (data) {
pushData(data);
});
See this post for different options for socket.io messages
Send response to all clients except sender (Socket.io)
io.sockets.on('connection', function (client) {
client.on('attantion', function (data) {
//client.emit('attantion', data ); // This will send it to only the client
//client.broadcast.emit('attantion', data); // This will send it to everyone but this client
io.emit('attantion', data); // This will send it to all attached sockets.
});
});
Edit
I wonder if this post can help you?
Socket.io - Cannot load file
I was curious how sending the php file to the client through node.js works? are you using another framework?
Could you show more of what your client code looks like? loading the lib and the instantiation of the socket.