How to proxy a media stream in Node? - node.js

I want to be able to proxy a remote icecast stream to client. I've been fiddling around a lot in the past few days to no avail.
Use case:
Be able to extract analyser data out of an <audio> tag src without running into CORS issues.
My solution so far
In order to address CORS issues preventing me to create an leverage sound data directly out of the <audio>'s source, I've tried to write a tiny proxy which would pipe requests to a specific stream and return statics in any other case.
Here is my code:
require('dotenv').config();
const http = require('http');
const express = require('express');
const app = express();
const PORT = process.env.PORT || 4000;
let target = 'http://direct.fipradio.fr/live/fip-midfi.mp3';
// figure out 'real' target if the server returns a 302 (redirect)
http.get(target, resp => {
if(resp.statusCode == 302) {
target = resp.headers.location;
}
});
app.use(express.static('dist'));
app.get('/api', (req, res) => {
http.get(target, audioFile => {
res.set(audioFile.headers);
audioFile.addListener('data', (chunk) => {
res.write(chunk);
});
audioFile.addListener('end', () => {
res.end();
});
}).on('error', err => {
console.error(err);
});
});
app.listen(PORT);
The problem
The client receives a response from the proxy but this gets stalled to 60kb of data about and subsequent chunks are not received, in spite of being received by the proxy:
Any suggestion is welcome!

I've found a solution, use stream pipe.
const app = express();
const PORT = process.env.PORT || 4000;
let target = 'http://direct.fipradio.fr/live/fip-midfi.mp3';
// figure out 'real' target if the server returns a 302 (redirect)
http.get(target, resp => {
if(resp.statusCode == 302) {
target = resp.headers.location;
}
});
app.use(express.static('dist'));
app.get('/api', (req, res) => {
req.pipe(request.get(target)).pipe(res);
});
app.listen(PORT);

Related

How can a TCP/IP server write data on a HTTP server?

Here is my tcp/ip server that listens to IoT sensors sending it data:
const net = require('net')
const server = net.createServer(socket => {
socket.on('data', data => {
console.log(data.toString())
})
}).listen(1234)
I receive the data fine. A line of data received looks like 'DATA,zoneA,20'.
Now I want to write that stream of data to this http server:
const express = require('express')
const morgan = require('morgan')
const app = express()
const bodyParser = require('body-parser')
const {PORT} = 3000
app.use(morgan('tiny'))
app.use(express.json())
app.use(bodyParser.json())
app.use(bodyParser.urlencoded({extended: true}))
const catchTCP = async dataLine => {
dataLine = 'DATA,zoneA,20'
try {
if (dataLine.indexOf("DATA") >= 0) {
var stream = dataLine.split(",")
var streamData = {
zone: stream[1],
battery: stream[2]
}
console.log(streamData)
}
} catch(err) {
console.log("error:", err)
}
}
catchTCP()
app.listen(PORT, () => console.log(`App listening at http://localhost:${PORT}`))
I would like to replace the 'dataLine' object by an entry point for this TCP/IP data. What would be the best way to forward TCP/IP data to the HTTP server's port?
Both servers run on the same network, so I've been told socket.io would not be the best fit.
Replace the catchTCP function on your HTTP server with a middleware route:
app.post("/receive", express.text(), function(req, res) {
var dataLine = req.body;
/* Your code from catchTCP */
res.end(); /* finish the HTTP response */
});
Then your TCP/IP server can send an HTTP request with the following code:
var s = net.createConnection(PORT, "localhost", function() {
s.end(`POST /receive HTTP/1.1
Content-Type: text/plain
Content-Length: ${data.toString().length}
${data}`);
});
This is the TCP/IP-level representation of the HTTP request. But I would not do it that way, I would rather use a proper HTTP client. Which is not a problem if your TCP/IP server is programmed in Node.js anyway:
const http = require("http");
http.request("http://localhost:${PORT}/receive", {
method: "POST",
headers: {"Content-Type": "text/plain"}
}).end(data.toString());

Read Serial port data using node js

I want to read data from serial port and get from data when reqested
Here is my code
const http = require('http');
const hostname = 'localhost';
const { SerialPort } = require('serialport')
const { ReadlineParser } = require('#serialport/parser-readline')
const { io } = require('socket.io');
let express = require('express')
const serialPort = new SerialPort({
path: 'COM4',
baudRate: 9600 ,
})
const parser = serialPort.pipe(new ReadlineParser({ delimiter: '\r\n' }))
let app = express();
var port = 8080;
const server = http.createServer(app);
server.listen(port, hostname, () => {
console.log(`Server running at http://${hostname}:${port}/`);
});
app.get('/get_data', function(req, res) {
parser.on('data', function(data) {
res.json({'weight': data});
});
});
When i am try to get data i got ERR_HTTP_HEADERS_SENT: Cannot set headers after they are sent to the client
I want serial port data when requested from localhost:8080/get_data anyone can help ?
Your data event from parser is probably firing more than once, which means you would be calling res.json more than once. As you can see in the express api documentation, res.json sets the content-type header...thus you can only call it once per request. Hence the error.
What I think would normally be done in this kind of situation is to set up a queuing system. A simple version might be done using an array, although if you were using this in a production server it might be better to use a proper message queuing system (e.g. rabbitMQ, kafka, AWS SQS, etc).
Here's an example of how you might use an array:
const queue = [];
parser.on('data', function(data) {
// push new data onto end of queue (array)
queue.push(data);
});
app.get('/get_data', function(req, res) {
if (req.params.getFullQueue === 1) {
// empty complete contents of current queue,
// sent to client as an array of { weight: x } objects
const data = queue.splice(0, queue.length)
.map(x => ({ weight: x }));
res.json(data);
} else {
// get oldest enqueued item, send it only
res.json({ weight: queue.shift() });
}
});
The if/else in the app.get is meant to illustrate these two options, depending on which you wanted to use. In production you'd probably want to implement pagination, or maybe even a websocket or EventSource so that data could be pushed as it became available.

How to recieve a file using request.get()?

I am writing a server that is meant to serve and receive files. It is written in node.js, using express.js. I also have a client, also written in node, which is meant to send a request to the server and receive the files on the server.
Server-side
const express = require("express");
const app = express();
const file = "./samplefiles/Helloworld.txt";
app.get("/", (res)=>{
res.download(file);
});
module.exports = app; //this exports to server.js
const http = require("http");
const app = require("./app.js);
const port = 8080;
const server = http.createServer(app);
server.listen(port, () => {
console.clear();
console.log("server running");
})
Client-side
const request = require("request");
request.get("http://localhost:8080/", (req, body) => {
console.log(body);
console.log(res);
});
If I try to access it by my browser I am asked what I want to do with the file, it works. However, Is I run my client-side code it prints the body and the res(being null). I expected the file name and it's content to be in the body but only the content of the file was in the body.
I want to receive the whole file, is possible, or at least get the name of it so that I can "make" a copy of it on the client-side.
Change code your server side to:
const port = 8080;
const express = require("express");
const app = express();
const path = require('path');
app.get("/", function(req, res){
res.sendFile(path.join(__dirname, 'app.js'));
});
app.listen(port, () => {
console.clear();
console.log("server running");
});
Change code your client-side to:
var request = require('request');
request('http://localhost:8080/', function (error, response, body) {
console.log('error:', error); // Print the error if one occurred
console.log('statusCode:', response && response.statusCode); // Print the response status code if a response was received
console.log('body:', body); // Print data of your file
});
You need to install request npm i request for client side
You can serve up any files you want with express static method:
app.use(express.static('public'))
in this case just put all the files you want to serve in folder called public and then you can access it by localhost:8080/Helloworld.txt.
I ended up working around it.
I sent the file name as a header and was thus able to create a replica of the file I wanted to download using the body info and the filenameheader.

How to download, modify and serve distant page

I try to create a http server that will download the HTML at https://google.com
And serve it (at localhost:3000). Kind of a proxy.
With this code:
const express = require('express')
const https = require('https')
const app = express()
app.get('/', function (req, mainRes) {
https.get('https://www.google.fr', (res) => {
res.on('data', (d) => {
mainRes.send(d)
})
})
})
app.listen(3000)
The html from google.com seems to be downloaded, but the server crashes with this error :
Error: Can't set headers after they are sent.
I Understand it's related to the 2 wrapped requests but I don't know how to fix it.
In your code the argument res is a stream (see the docs for https.get).
Currently you are attempting to send the complete response each time a chunk is received through that stream. Hence you are getting the error Can't set headers after they are sent., because the second time mainRes.send() is called you are trying to send the whole response again. (See the docs for express' res.send.)
You want to pipe res through the express response object, as this is also a stream:
const express = require('express')
const https = require('https')
const app = express()
app.get('/', function (req, mainRes) {
https.get('https://www.google.fr', (res) => {
mainRes.pipe(res)
})
})
app.listen(3000)
I am not 100% sure in your case but I would suggest you to add a debugger statement and step through it.
#sdgluck && #kwesi1337 helped me find a solution, I need to concat all chunks from the data event :
const express = require('express')
const https = require('https')
const app = express()
let data = ''
app.get('/', function (req, mainRes) {
https.get('https://www.google.fr', (res) => {
res.on('data', (chunk) => {
data += chunk;
})
res.on('end', () => {
mainRes.end(data);
});
})
})
app.listen(3000)

Why does my Node application keep sending data after every refresh?

I am using node.js and express.js to build a very simple application. I want to read the content of a directory and when I browse to localhost:3000/names, the application will print an array of the content to the web page except for the content that I choose not to. Here is my code:
const express = require('express');
const fs = require('fs');
const app = express();
const port = 3000;
let result = [];
app.get('/names', (req, res) => {
const printNames = (err, file) => {
file.forEach(e => {
if (e !== 'john') {
result.push(e);
}
});
res.send(result);
};
fs.readdir('./home', printNames);
});
app.listen(port, () => {
console.log('Listening on port 3000');
});
The application works the way that I wanted to, but there is a small bug. Every time I refresh the page, the application will add on the same content of the array to the existing array. My array keeps getting bigger with every refresh. I want the application to send the array to the page and will stay the same when I refresh. I do not have the slightest idea why my application is behaving this way. Can someone explain to me why it is behaving like this and what would be the right steps to fix this?
It is because you've declared your result array in the global scope.
Your result array is getting bigger and bigger every time.
Simply move the declaration to your route and you should be fine.
This should work fine:
const express = require('express');
const fs = require('fs');
const app = express();
const port = 3000;
// let result = []; Remove this line
app.get('/names', (req, res) => {
let result = []; // And add it here
const printNames = (err, file) => {
file.forEach(e => {
if (e !== 'john') {
result.push(e);
}
});
res.send(result);
};
fs.readdir('./home', printNames);
});
app.listen(port, () => {
console.log('Listening on port 3000');
});
Read more about scopes in JavaScript here:
https://www.w3schools.com/js/js_scope.asp
Every time you request to load the page /names, it is re-running the code in that handler. If you only want to run that script once, then move it outside of the handler, and only send the result.
let result = [];
const printNames = (err, file) => {
file.forEach(e => {
if (e !== 'john') {
result.push(e);
}
});
};
fs.readdir('./home', printNames);
app.get('/names', (req, res) => {
res.send(result)
});

Resources