Server Push with Nodejs pushStream method is not working - node.js

I am studying http2 on nodejs, but find out a issue pushStream method not working
(client side do not show "Pushed/[fileName]" on developer tool)
I wonder if the reason is nodejs version (I installed the latest version v9.8.0)
My codes is the following :
server.js
'use strict'
const fs = require('fs');
const path = require('path');
const http2 = require('http2');
const utils = require('./utils');
const { HTTP2_HEADER_PATH } = http2.constants;
const PORT = process.env.PORT || 3000;
// The files are pushed to stream here
function push(stream, path) {
const file = utils.getFile(path);
if (!file) {
return;
}
stream.pushStream({ [HTTP2_HEADER_PATH]: path}, (err, pushStream, headers) => {
if (err) throw err;
pushStream.respondWithFD(file.content, file.headers)
});
}
// Request handler
function onRequest(req, res) {
const reqPath = req.headers[':path'] === '/' ? '/index.html' : req.headers[':path']
const file = utils.getFile(reqPath);
// 404 - File not found
if (!file) {
res.statusCode = 404;
res.end();
return;
}
// Push with index.html
if (reqPath === '/index.html') {
push(res.stream, '/assets/main.js');
push(res.stream, '/assets/style.css');
} else {
console.log("requiring non index.html")
}
// Serve file
res.stream.respondWithFD(file.content, file.headers);
}
// creating an http2 server
const server = http2.createSecureServer({
cert: fs.readFileSync(path.join(__dirname, '/certificate.crt')),
key: fs.readFileSync(path.join(__dirname, '/privateKey.key'))
}, onRequest);
// start listening
server.listen(PORT, (err) => {
if (err) {
console.error(err);
return -1;
}
console.log(`Server listening to port ${PORT}`);
});
utils.js
'use strict';
const fs = require('fs');
const mime = require('mime');
module.exports = {
getFile: function (path) {
const filePath = `${__dirname}/public${path}`;
try {
const content = fs.openSync(filePath, 'r');
const contentType = mime.getType(filePath);
return {
content,
headers: {
'content-type': contentType
}
};
} catch (e) {
return null;
}
}
}
Updated 2020 01 28
Resolved: The reason is the latest version of chrome v65. has bug, cause client do not trust PUSH_PROMISE frame. I backup chrome v64 then it working now.

I haven’t tried to run your code but have noticed that Chrome does not allow HTTP/2 push with an untrusted HTTPS certificate (e.g. a self-signed one not yet added to the trust store). Raised a bug with the Chrome team.
If you have the red unsecure padlock item then you could be hitting this issue too. Add the certificate into your trust store, restart Chrome and reload the site where you should get a green padlock.
Note Chrome needs a certificate with a Subject Alternative Name (SAN) field matching the domain so if you’ve just got the older Subject field then it won’t go green even after adding it to your trust store.
Another option is to look at the Chrome HTTP2 frames by typing this into your URL:
chrome://net- internals/#http2
If you see the push promise frames (with a promised_stream_id), followed by the headers and data on that promised_stream_id then you know the sever side is working.

Related

Downloading the file from server but still getting error 'Cannot fetch' and not going inside fetch.then function

This is the error I am getting while download a file from node server.
and the file is downloading but the name is not correct what I am providing because the fetch is not accessing it's response section.
This is the download file function that fetches the .pdf file from node server.
downloadFileHandler = async (name, path) => {
this.setState({ cvDownloadLoading: true });
try {
const response = await fetch(
`${process.env.REACT_APP_SERVER_URL}/${path}`
);
if (!response.ok) throw response;
const buffer = await response.arrayBuffer();
const url = window.URL.createObjectURL(new Blob([buffer]));
const element = document.createElement('a');
element.style.display = 'none';
element.href = url;
element.setAttribute('download', name);
document.body.appendChild(element);
element.click();
this.setState({ cvDownloadLoading: false });
window.URL.revokeObjectURL(element.href);
document.body.removeChild(element);
} catch (err) {
this.setState({ cvDownloadLoading: false });
if (err.name === 'AbortError') {
} else {
try {
// const body = await err.json();
console.log(err);
} catch (e) {
console.log('catch', e);
}
}
}
};
The backend node has 'cors' installed and app is using this.
const cors = require('cors');
app.use(cors());
And serving files statically like this.
app.use('/data', express.static(path.join(__dirname, 'data')));
EDITED:
Now I tried to download image file and it downloaded without any issue.
It is a problem with PDF downloads or any file not image (maybe).
Can anyone explain?
I think you must to config your cors settings and add your client domain in the origin field. Try like this:
const corsOptions = {
origin: 'http://localhost:8080'
};
// and then
app.use(cors(corsOptions));
You can see this exapmle in Configuring CORS section here
https://expressjs.com/en/resources/middleware/cors.html

Load custom configuration runtime

I have a nuxt application in which I will need to append data from a generated configuration file when the application is first started. The reason I cannot do this in the actual build is because the configuration file does not exists at this point; it is generated just before calling npm start by a bootstrap script.
Why don't I generated the configuration file before starting the application you may ask and this is because the application is run in a docker container and the built image cannot include environment specific configuration files since it should be used on different environments such as testing, staging and production.
Currently I am trying to use a hook to solve this, but I am not really sure on how to actually set the configuration data in the application so it can be used everywhere:
# part of nuxt.config.js
hooks: {
listen(server, listener) {
# load the custom configuration file.
fs.readFile('./config.json', (err, data) => {
let configData = JSON.parse(data));
});
}
},
The above hook is fired when the application first starts to listen for connecting clients. Not sure this is the best or even a possible way to go.
I also made an attempt of using a plugin to solve this:
import axios from ‘axios’;
export default function (ctx, inject) {
// server-side logic
if (ctx.isServer) {
// here I would like to simply use fs.readFile to load the configuration, but this is not working?
} else {
// client-side logic
axios.get(‘/config.json’)
.then((res) => {
inject(‘storeViews’, res.data);
});
}
};
In the above code I have problems both with using the fs module and axios.
I was also thinking about using a middleware to do this, but not sure on how to proceed.
If someone else has this kind of problem here is the solution I came up with in the end:
// plugins/config.js
class Settings
{
constructor (app, req) {
if (process.server) {
// Server side we load the file simply by using fs
const fs = require('fs');
this.json = fs.readFileSync('config.json');
} else {
// Client side we make a request to the server
fetch('/config')
.then((response) => {
if (response.ok) {
return response.json();
}
})
.then((json) => {
this.json = json;
});
}
}
}
export default function ({ req, app }, inject) {
inject('config', new Settings(app, req));
};
For this to work we need to use a server middleware:
// api/config.js
const fs = require('fs');
const express = require('express');
const app = express();
// Here we pick up requests to /config and reads and return the
// contents of the configuration file
app.get('/', (req, res) => {
fs.readFile('config.json', (err, contents) => {
if (err) {
throw err;
}
res.set('Content-Type', 'application/json');
res.end(contents);
});
});
module.exports = {
path: '/config',
handler: app
};

Change issue closing pattern for gitlab user account

I want to close an issue matching the file name pushed with Issue title (My source files are named with unique integers, e.g. 34521.cpp and there are corresponding issues on Gitlab e.g. Problem #34521).
How can I do so?
The default pattern is not suitable as I have 2000+ issues and I do not want to refer issues with the issue ID's each time. I want it to be automated. So I was checking the page :
Change the issue closing pattern.
It says I need to have access to the server where gitlab is installed. Does that mean I cannot change the issue closing pattern for Gitlab cloud's user account hosted at http://gitlab.com ?
You can't define a custom closing pattern on gitlab.com, only on your own hosted gitlab instance. But what you can do is to use webhooks to listen on push events on a remote server. You can then parse the commit messages yourself and take decision on closing issues. You can use Gitlab API to close issue on your server instance (with a hard coded access token)
This can be tested locally using an http tunnel like ngrok
The following nodejs script starts a server serving a /webhook endpoint. This webhook endpoint is called when any push occurs on your repo.
const express = require('express');
const bodyParser = require('body-parser');
const axios = require('axios');
const to = require('await-to-js').to;
const port = 3000;
const projectId = "4316159";
const accessToken = "YOUR_ACCESS_TOKEN";
const app = express();
app.use(bodyParser.json())
app.post('/webhook', async function(req, res) {
console.log("received push event");
let result, err, closeRes;
for (var i = 0; i < req.body.commits.length; i++) {
for (var j = 0; j < req.body.commits[i].added.length; j++) {
filenameWithoutExt = req.body.commits[i].added[j].split('.').slice(0, -1).join('.');
[err, result] = await to(axios({
url: `https://gitlab.com/api/v4/projects/${projectId}/issues?search=#${filenameWithoutExt}`,
method: 'GET',
headers: {
"PRIVATE-TOKEN": accessToken
}
}));
if (err) {
console.log(err);
} else {
if (result.data.length !== 0) {
//close those issues
for (item in result.data) {
console.log(`closing issue #${result.data[item].iid} with title ${result.data[item].title}`);
[err, closeRes] = await to(axios({
url: `https://gitlab.com/api/v4/projects/${projectId}/issues/${result.data[item].iid}?state_event=close`,
method: 'PUT',
headers: {
"PRIVATE-TOKEN": accessToken
}
}));
if (err) {
console.log(err);
} else {
console.log(`closing status : ${closeRes.status}`);
}
}
} else {
console.log("no issue were found");
}
}
}
}
res.sendStatus(200);
});
app.listen(port, () => console.log(`listening on port ${port}!`))
In the above you need to change the access token value & projectId. Also note that above code will check only added file, you can modify it to include updated or deleted file matching your requirements.
Launch ngrok on port 3000 ngrok http 3000 & copy the given url in integrations sections of your repo :
Now when you add any file it will check for the filename without extension and search all issue with within title #filename_without_extension and close it right away

I'm using .pfx certification in my node.js https server and I can't use 'passphrase' in option

I'm currently making an web application with node.js and https.
So I try to use my .pfx(I got the file from here http://www.cert-depot.com/) for certification required for https, like following code:
var https = require('https');
var fs = require('fs');
var options = {
pfx: fs.readFileSync('./8ab20f7b-51b9-4c09-a2e0-1918bb9fb37f.pfx')
passphrase: 'password'
};
var server = https.createServer(options, function (request, response) {
fs.readFile('index.html', function (error, data) {
response.writeHead(200, {'Content-Type': 'text/html'});
response.end(data);
});
}).listen(12345, function(){
console.log('server running');
});
But when I start this code with node.js, I'm getting an error message in my windows console:
passphrase: 'password'
Unexpected identifier
My code is very similar to the official guide page of Node.js (http://nodejs.org/api/https.html#https_https_createserver_options_requestlistener), but I can't start my https server.
What's wrong with my passphrase?
(I'm running node.js in Windows 8 64bit.)
I guess the missing comma between your pfx and passphrase properties is what cause the error. Here I added the comma:
var options = {
pfx: fs.readFileSync('./8ab20f7b-51b9-4c09-a2e0-1918bb9fb37f.pfx'),
passphrase: 'password'
};
I stick a promise wrapper on my implementation of it and keep it async (ES2015).
lib/pfx.js
import { readFile } from 'fs'
import { resolve as resolvePath } from 'path'
export const CERTIFICATE_ROOT = resolvePath(__dirname, '..', 'etc', 'certificates')
export const getCertificatePath = filename => resolvePath(CERTIFICATE_ROOT, filename)
export function readCertificate(filename) {
let certificatePath = getCertificatePath(filename)
return new Promise((resolve, reject) => {
readFile(certificatePath, (err, certificate) => {
if (err)
return reject(err)
resolve(certificate)
})
})
}
export function readPfx(filename, passphrase) {
assert.typeOf(passphrase, 'string', 'passphrase must be a string')
assert.isAbove(passphrase.length, 0, 'passphrase must not be empty')
return readCertificate(filename).then(pfx => ({ pfx, passphrase }))
}
and usage
lib/app.js
import { readPfx } from './pfx'
readPfx('8ab20f7b-51b9-4c09-a2e0-1918bb9fb37f.pfx', process.env.PASSPHRASE)
.then(opts => /* start server here */)
.catch(err => /* handle errors */)

Node itself can serve static files without express or any other module..?

I am beginner in the field of node js.No idea how to send simple request from url
Like :- http://localhost:9999/xyz/inde.html
my file hierarchy is
server.js
xyz(folder)-
|->index.html
And get the html page from my server.Which is running at post 9999
var http = require("http");
function onRequest(request, response) {
console.log("Request received.");
response.writeHead(200, {"Content-Type": "text/plain"});
response.end();
}
http.createServer(onRequest).listen(9999);
console.log("Server has started.");
I know i can send string(having html template) from node js server and send it in response but how to send file without express and any other external module.
Thanks
It's ridiculous to attempt to create a node application without npm dependencies, because the base of nodejs is just that -- a base. Unless you feel like implementing entire protocols, you're better off using a minimal, well maintained npm module that does that for you. That said, here is the very basic thing you asked for (without MiME, eTags, caching, etc, etc):
var basePath = __dirname;
var http = require('http');
var fs = require('fs');
var path = require('path');
http.createServer(function(req, res) {
var stream = fs.createReadStream(path.join(basePath, req.url));
stream.on('error', function() {
res.writeHead(404);
res.end();
});
stream.pipe(res);
}).listen(9999);
const http = require('http');
const fs = require("fs");
const path = require("path");
function send404(response){
response.writeHead(404, {'Content-Type': 'text/plain'});
response.write('Error 404: Resource not found.');
response.end();
}
const mimeLookup = {
'.js': 'application/javascript',
'.html': 'text/html'
};
const server = http.createServer((req, res) => {
if(req.method == 'GET'){
let fileurl;
if(req.url == '/'){
fileurl = 'index.html';
}else{
fileurl = req.url;
}
let filepath = path.resolve('./' + fileurl);
let fileExt = path.extname(filepath);
let mimeType = mimeLookup[fileExt];
if(!mimeType) {
send404(res);
return;
}
fs.exists(filepath, (exists) => {
if(!exists){
send404(res);
return;
}
res.writeHead(200, {'Content-Type': mimeType});
fs.createReadStream(filepath).pipe(res);
});
}
}).listen(3000);
console.log("Server running at port 3000");
its very simple, node already provide fs module from which u can read that html
file and append
on response obj like this:
response.writeHead(200, {"Content-Type": "text/plain"});
//here is the code required
fs.readFile("./xyz/index.html", (err,fileContent) =>
{
response.end(fileContent);
});
but the problem here is you will only get the HTML document
not the resources inside that HTML files which are stored in
different folders like if you have this code in your index.html
<link rel="stylesheet" href="../css/index.css" />
this index.css will not be allowed by the node server.
But i guess your question is solved.
I don't agree with the assertion in the accepted answer:
"It's ridiculous to attempt to create a node application without npm dependencies"
as having zero dependencies allows you to deploy an app to a system that runs node by just copying the javascript file(s), and without running npm install.
An example of when I found this useful in real life was writing a public API to compute the amount of income tax a business needs to withold from an employee's pay. You can read all about this fascinating topic here but in essence I had an api that was passed a gross income and returned how that gross income should be split between net income and tax.
I did this with one solitary index.js file, no package.json, and need to npm install:
index.js:
http = require('http');
url = require('url');
const port = 80; // make this a number over 1024 if you want to run `node` not run `sudo node`
const debug = true;
const httpServer = http.createServer((request, response) => {
response.setHeader('Content-Type', 'application/json');
const parsedUrl = url.parse(request.url, true);
let pathName = parsedUrl.pathname;
if (pathName==='/favicon.ico') {
// chrome calls this to get an icon to display in the tab. I want to ignore these request. They only happen when testing in a browser,
// not when this api is called in production by a non-browser.
if (debug) console.log('Browser requested favicon.ico')
response.end();
} else {
if (debug) console.log('Request on path ' + pathName);
const elements = pathName.split('/');
if (elements.length == 3 && elements[0] === '' && elements[1]==='witholdcalc') {
const grossString = elements[2];
const gross = Number.parseInt(grossString);
if (isNaN(gross)) {
response.writeHead(400).end(JSON.stringify({error:'Gross salary must be an integer. found: ' + grossString}));
} else {
/*
* The computation of the amount to withold is more complicated that this, but it could still be hard coded here:
* For simplicity, I will compute in one line:
*/
const withold = Math.floor((gross<1000)?0:((gross-1000)*.2));
response.writeHead(200).end(JSON.stringify({net: (gross-withold), withold: withold, elements:(debug?elements:undefined)}));
}
} else {
if (debug) console.log('Invalid path was: ' + pathName,elements);
response.writeHead(404).end();
}
}
});
httpServer.listen(port), () => {
console.log(`PAYG listening at http://localhost:${port}`)
}
I then could execute sudo node install.js on my linux computer, and in a browser, hit http://localhost/witholdcalc/6000, and it would return {"net":5000,"withold":1000} when debug is set to false.
Heads up for anyone attempting to serve static files without Express or any other framework:
There is no benefit to skipping Express, in terms of performance or productivity. The only exception is to gain an understanding of how the server and client communicate with each other. Frameworks like Express abstract away all of those complexities, and beginners may not fully understand how they work.
Here is my approach to serving static files using Node.js only. Actually, the reason I did this is because a coding test I was given stipulated no framework whatsoever.
First, decide what the URL path for those static files should look like. I want mine to be accessible under /assets/ path, such as https://example.com/assets/main.css, https://example.com/assets/cat.jpg.
Decide the REGEX for matching those URL.
`const assetPattern = /^/assets/[a-zA-Z]+.[a-zA-Z]+/;
The formula above will match urls which contains /assets/[filename].[file extension.
Create a server with Node.js, taking a callback function as a parameter.
// ...
const http = require('http')
const server = http.createServer(requestListener).
Extract url path, get the corresponding file path and configure your server to send the correct MIME types to the browser.
const path = require('path');
// ...
const requestListener = (req, response) => {
// get the relative url for the request. For example, relative url for a request
// to https://example.com is /.
const { url } = req;
if (url.match(assetPattern)) {
// Inside the project directory, the static files are located under the
// /public/assets directory.
const filePath = `./public/${url}`;
// Get the extension name aka the string after the dot. For example, a url like
// https://example.com/assets/main.css will result in extension name of css.
const extname = String(path.extname(filePath)).toLowerCase();
const mimeTypes = {
'.html': 'text/html',
'.js': 'text/javascript',
'.css': 'text/css',
'.json': 'application/json',
'.png': 'image/png',
'.jpg': 'image/jpg',
'.gif': 'image/gif',
'.svg': 'image/svg+xml',
'.wav': 'audio/wav',
'.mp4': 'video/mp4',
'.woff': 'application/font-woff',
'.ttf': 'application/font-ttf',
'.eot': 'application/vnd.ms-fontobject',
'.otf': 'application/font-otf',
'.wasm': 'application/wasm',
};
const contentType = mimeTypes[extname] || 'application/octet-stream';
staticFileHandler(req, response, filePath, contentType);
}
}
Serve the static files with fs module
const fs = require('fs')
// ...
const staticFileHandler = (req, res, filePath, contentType) => {
fs.readFile(filePath, (err, content) => {
if (err) {
res.writeHead(500);
res.end(`Sorry, check with the site admin for error: ${err.code}`)
} else {
res.writeHead(200, { 'Content-Type': contentType }); // indicate the request was successful
res.end(content, 'utf-8');
}
}
}

Resources