Piping Remote files in Node JS looking for alternative ways - node.js

I was using a request module and that is deprecated now. It was useful to pipe the remote files without storing in server. So looking for an alternative solution of the same function with Node-fetch, GOT, Axios etc..
import request from 'request';
(req, res) {
return request(`http://files.com/image.jpg`)
.on('error', (err) => {
log.error('error fetching img url: %O', err);
res.status(500).end('error serving this file');
})
.on('response', (urlRes) => {
if (urlRes.statusCode === 304) return;
urlRes.headers['content-disposition'] = `inline;filename="${slug}"`;
})
.pipe(res);
}

I just tried this way with node-fetch module, it works. But it doesn't pipe the response headers as like in request module. So we need to do set the required headers manually.
import fetch from 'node-fetch';
(req, res) {
fetch('http://files.com/image.jpg').then((response) => {
res.set({
'content-length': response.headers.get('content-length'),
'content-disposition': `inline;filename="${slug}"`,
'content-type': response.headers.get('content-type'),
})
response.body.pipe(res);
response.body.on('error', () => {}) // To handle failure
});
}

Related

Nodejs + Axios Post returns undefined value

I am using React + NodeJS & Axios but have been trying to send a post request but experiencing difficulties.
The request seems to be posting successfully, but all actions at the nodejs server is returning in the "undefined" data value, even if the data is passed successfully shown in the console.
REACT
const fireAction = (data1, data2) => {
const data = JSON.stringify({data1, data2})
const url = `http://localhost:5000/data/corr/fire`;
const config = {
headers: {
'Content-Type': 'application/x-www-form-urlencoded',
'Authorization': 'AUTHCODE',
}
}
axios.post(url, data, config)
.then(function (response) {
console.log(response);
})
.catch(function (error) {
console.log(error);
});
}
fireAction("Oklahoma", "Small apartment")
NODE
app.post('/data/corr/fire', async (req, res) => {
try {
const data = req.body.data1;
console.log(data)
} catch(e) {
res.send({success: "none", error: e.message})
}
});
Result of node: "undefined"
I have added the following body parser:
app.use(express.json());
app.use(bodyParser.json());
app.use(bodyParser.urlencoded({extended: true}));
I am not sure why this error is happening. I see there is similar questions to mine: however none of them are applicable as I'm using both express and body parser which is already suggested.
You're POSTing JSON with a content-type meant for forms. There's no need to manually set content-type if you're sending JSON, but if you want to manually override it, you can use 'Content-Type': 'application/json', and access the response in your route with req.body. If it does need to be formencoded, you'll need to build the form:
const params = new URLSearchParams();
params.append('data1', data1);
params.append('data2', data2);
axios.post(url, params, config);

Node, how to send result back to client from nested HTTP request?

I'm using ReactJS to run my front-end and using Express for my back-end. I want to make a get request to my back-end using the "/paas" path to get a listing of all of my pods that are running inside my namespace in Rancher(Kubernetes).
The back-end then needs to be able to make an https request to my Rancher API endpoint and return the result to the front-end. I can make the successful call to Rancher API and see the data print to the screen on my back-end but I get lost when trying to send this data to the front-end and console log it out inside the browser.
Due to "pre-flight" errors, I can't just make a direct call to the Rancher endpoint inside of my App.js file. More info on this here. So I need to go the custom back-end route. I any case, it seems like this should be pretty straightforward. Any guidance would be appreciated.
App.js:
import React, { useEffect } from "react"
import axios from "axios"
function App() {
useEffect(() => {
const fecthPods = async () => {
try {
const response = await axios.get(`http://localhost:3001/paas`)
console.log(response.data)
} catch (err) {
if (err.response) {
// Not in the 200 response range
console.log(err.response.data)
console.log(err.response.status)
console.log(err.response.headers)
} else {
console.log(`Error: ${err.message}`)
}
}
}
fecthPods()
},[])
return (
<div>
Hello World!
</div>
);
}
export default App;
Back-end server.js:
import express from "express"
import cors from "cors"
import https from "https"
import bodyParser from "body-parser";
const app = express()
app.use(cors())
app.use("/data", (req, res) => {
res.json({ name: "Minion", favFood: "pizza"})
})
app.get("/paas", bodyParser.json(), (req, res) => {
const options = {
hostname: "k8.fqdn.com",
port: 443,
path: "/k8s/clusters/c-wwfc/v1/pods/mynamespace",
method: "GET",
headers: {
Authorization: "Bearer token:12345"
}
}
const request = https.get(options, (res) => {
let responseBody = ""
res.setEncoding("UTF-8")
res.on("data", (chunk) => {
console.log("---chunk", chunk.length);
responseBody += chunk;
});
res.on("end", () => {
let json = JSON.parse(responseBody)
// console.log(responseBody)
console.log("Response finished");
res.json({data: responseBody})
});
});
request.end()
res.json({ status: "complete", data: request.data})
})
app.listen(3001)
console.log("backend up on 3001")
I see a couple of errors on your backend code.
First, you are naming the res variable for the express middleware and also for the response received by the https module. In this way, you lose the possibility to access to the express response object in the on.('end') callback.
Secondly, you are triyng to respond to the client multiple times (inside the on.('end') callback and also directly inside the express middleware with the instruction res.json({ status: "complete", data: request.data}). Also, consider that the code you wrote is repliyng to the client before the call to the k8s cluster is made. And the response will always be a JSON with this data: { "status": "complete", "data": undefined}.
To fix all, try with this code (I will try to comment all edits):
app.get("/paas", bodyParser.json(), (req, res) => {
const options = {
hostname: "k8.fqdn.com",
port: 443,
path: "/k8s/clusters/c-wwfc/v1/pods/mynamespace",
method: "GET",
headers: {
Authorization: "Bearer token:12345"
}
}
const k8sRequest = https.get(options, (k8sResponse ) => { // as you can see I renamed request and res to k8sRequest and k8sResponse, to avoid loosing the scope on req and res express middleware variables
let responseBody = ""
res.setEncoding("UTF-8")
k8sResponse.on("data", (chunk) => { // here use k8sResponse to collect chunks
console.log("---chunk", chunk.length);
responseBody += chunk;
});
k8sResponse.on("end", () => { // here use k8sResponse again
let json = JSON.parse(responseBody)
// console.log(responseBody)
console.log("Response finished");
res.json({ status: "complete", data: responseBody}) // here use the express res variable, to reply to the client.
});
});
k8sRequest.end() // here use the k8sRequest variable to make the https call to the k8s cluster
// here I deleted the res.json instruction
})
The above code should just works. Anyway, I suggest you using axios also with your backend service. You are already using it with React, so you know how to use it. The syntax is minimal and easier and you can use the async/await approach.
Axios solution:
import axios from "axios"
app.get("/paas", bodyParser.json(), async (req, res) => {
try {
const url = 'https://k8.fqdn.com/k8s/clusters/c-wwfc/v1/pods/mynamespace'
const k8sResponse = await axios.get(url, headers: {
Authorization: "Bearer token:12345"
})
res.json({ status: "complete", data: k8sResponse.data })
} catch (e) {
res.json({status: "error", data: e.response.data})
}
})
You should wrap your axios call inside a try/catch block to properly handle errors like you are doing with your React implementation. Error handling should be also implemented if you still want you the native node.js https module

Simple node.js http request proxy giving me a gzip error

I am using the below code, to start a server that accepts http requests, and then forwards them to an https api, then relay the response.
I have a problem in that the api gives a 'Content-Encoding: gzip' response, which seems to be causing me trouble.
If I don't relay the gzip response header, the C# code I'm testing gets a response that is just random symbols, the compressed data I assume, as does Postman. When I do include as per my example below, I get StatusCode 0: "The magic number in GZip header is not correct. Make sure you are passing in a GZip stream."
I've tried passing the response headers back as:
res.writeHead(cres.statusCode, cres.headers});
But that just seems to result in the jumbled output again. How can I fix this?
const https = require('https')
const port = 55555
const requestHandler = (req, res) => {
console.log(req.url)
var options = {
host: 'my.api.com',
path: '/7f308b16-d165-4062-b00f-76970783442e'+req.url,
path: req.url,
method: 'GET',
headers: req.headers
};
var json = '';
var creq = https.request(options, function(cres) {
// set encoding
cres.setEncoding('utf8');
// wait for data
cres.on('data', function(chunk){
console.log('data: '+chunk);
json += chunk;
});
cres.on('close', function(){
console.log('close: '+cres.statusCode);
res.writeHead(cres.statusCode);
res.end();
});
cres.on('end', function(){
console.log('end: '+json.toString());
console.log(res.headers)
res.writeHead(cres.statusCode, {'Content-Encoding': 'gzip', 'Content-Type':'text/json; charset=utf-8', 'Cache-Control':'private' });
res.end(json);
});
}).on('error', function(e) {
// we got an error, return 500 error to client and log error
console.log(e.message);
res.writeHead(500);
res.end();
});
creq.end();
}
const server = http.createServer(requestHandler)
server.listen(port, (err) => {
if (err) {
return console.log('something bad happened', err)
}
console.log(`server is listening on ${port}`)
})```

Unable to send request from request callback

I cant execute a second request inside the request callback.
request({
url: url,
headers: {
'auth-token': token
},
method: 'POST',
json: true,
body: data
}, (err, req, body) => {
if (err) {
console.log(err);
} else {
// Prosses data;
// This is the second request.
request({
url: url2,
headers; {
'auth-token': token
},
method: 'POST',
json: true,
body: data2
}, (err, req, body) => {
if (err) {
console.log(err);
return;
}
//Process data.
})
}
})
The problem is that the second request is not executing.
I am using nodemon to start the express server, but on the nodemon only the first request is receive on the express.
But the strange thing is that when I tried to call the method on the second time without closing the electron app, the second request is executed. And I can see it on the nodemon that the second request is executed first.
The output of the nodemon is like this.
POST /path/to/url 200 6.181 ms - 641 //-> this is the first execution. then there is nothing follows.
// Then I call the method again using the app. And the result is this.
POST /path/to/second/url 200 9.645 ms - 21
POST /path/to/url 200 21.628 - 641
It look like the /path/to/second/url is staying on somewhere nowhere and just send to the server if the method is called for the second time.
Please help, thanks.
Update: Added more info.
I have a folder could routes all the .js file is save there.
then I am loading it using this on the my app.js
let rs = fs.readdirSync(path.join(process.cwd(), '/routes/'));
rs.forEach((file) => {
if (file.indexOf('.js') !== -1) {
let fn = '/' + file.replace(/\.[^/.]+$/, '');
let pt = path.join(__dirname, './routes', fn);
let req = require(pt);
app.use(fn, req);
}
});
Then on the routes files I have something similar like this.
router.post('url', (req, res) => {
// here calling the controller.
// mostly just single line passing the request and result variable to the controller.
});
Actually that requests is called inside the ipc callback. I have a menuitems and on the click() event I just used the browserWindow.getFocusedWindow().webContents.send('ipc-name') then this will be triggered.
ipc.on('ipc-name', () => {
// The request is called here.
}
This does not solve the OP problem as the problem exists in Linux env but acts as an workaround.Instead of request module we have to make use of ClientRequest API in electron which is Event based and only makes the task much more difficult.But doesn't suffer from the issue faced in callback inside callback.
Example Code:
function triggerCall() {
const request = net.request(`${root}/routes/get1`);
request.on('response', (response) => {
response.on('data', (chunk) => {
console.log(`BODY: ${chunk}`)
})
response.on('end', () => {
console.log('req 1 end');
const request1 = net.request(`${root}/routes/get2`);
request1.on('response', (response) => {
response.on('data', (chunk) => {
console.log(`BODY: ${chunk}`)
})
response.on('end', () => {
console.log('req 2 end');
})
})
request1.end();
})
})
request.end();
};

Using Node/Express to stream file to user for download

I want to use a Node/Express server to stream a file to the client as an attachment. I would like to make an async request from the client to a /download endpoint and then serve an object received via API proxy to the client as a downloadable file (similar to the way res.attachment(filename); res.send(body); behaves).
For example:
fetch(new Request('/download'))
.then(() => console.log('download complete'))
app.get('/download', (req, res, next) => {
// Request to external API
request(config, (error, response, body) => {
const jsonToSend = JSON.parse(body);
res.download(jsonToSend, 'filename.json');
})
});
This will not work because res.download() only accepts a path to a file. I want to send the response from an object in memory. How is this possible with existing Node/Express APIs?
Setting the appropriate headers does not trigger a download, either:
res.setHeader('Content-disposition', 'attachment; filename=filename.json');
res.setHeader('Content-type', 'application/json');
res.send({some: 'json'});
This worked for me.
I use the content type octet-stream to force the download.
Tested on chrome the json was downloaded as 'data.json'
You can't make the download with ajax according to: Handle file download from ajax post
You can use a href / window.location / location.assign. This browser is going to detect the mime type application/octet-stream and won't change the actual page only trigger the download so you can wrap it a ajax success call.
//client
const endpoint = '/download';
fetch(endpoint, {
method: 'POST',
credentials: 'include',
headers: {
'Accept': 'application/json',
'Content-Type': 'application/json'
}
})
.then(res => res.json())
.then(res => {
//look like the json is good to download
location.assign(endpoint);
})
.catch(e => {
//json is invalid and other e
});
//server
const http = require('http');
http.createServer(function (req, res) {
const json = JSON.stringify({
test: 'test'
});
const buf = Buffer.from(json);
res.writeHead(200, {
'Content-Type': 'application/octet-stream',
'Content-disposition': 'attachment; filename=data.json'
});
res.write(buf);
res.end();
}).listen(8888);
You can set the header to force the download, then use res.send
see those links
Force file download with php using header()
http://expressjs.com/en/api.html#res.set

Resources