Configure Nginx for file upload using connect-busboy - node.js

I have an API in NodeJS where I can upload a file, and I receive it on the server side using the connect-busboy package.
So, for example, here is a part of the code that handles the request:
var app = require('express')();
var busboy = require('connect-busboy');
app.use(busboy({
highWaterMark: 2 * 1024 * 1024,
limits: {
fileSize: 1024 * 1024 * 1024 // 1 GB
},
immediate: true
}));
var busboyHandler = function (req, res) {
if (req.busboy) {
req.busboy.on('file', function(fieldname, file, filename, encoding, mimetype) {
console.log('received file ', req.path, fieldname, file, filename, encoding, mimetype);
});
req.busboy.on('field', function(key, value, keyTruncated, valueTruncated) {
console.log('field..', key, value, keyTruncated, valueTruncated);
});
req.busboy.on('finish', function() {
console.log('busboy finished');
});
}
};
app.post('api/documents/files', busboyHandler);
This works well when I start the API with npm start and upload the file to this API directly, however, when I configure an Nginx Docker, it works for really small files, but for most files, they don't get uploaded successfully.
Here is an excerpt from my nginx.conf file:
user nobody nogroup;
worker_processes auto; # auto-detect number of logical CPU cores
events {
worker_connections 512; # set the max number of simultaneous connections (per worker process)
}
http {
include mime.types;
client_max_body_size 100M;
client_body_buffer_size 256k;
upstream api_doc {
server 192.168.2.16:4956;
}
server {
listen *:4000; # Listen for incoming connections from any interface on port 80
server_name localhost; # Don't worry if "Host" HTTP Header is empty or not set
root /usr/share/nginx/html; # serve static files from here
client_max_body_size 100M;
client_body_buffer_size 256k;
location /api/documents {
proxy_pass http://api_doc;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection 'upgrade';
proxy_set_header Host $host;
proxy_cache_bypass $http_upgrade;
}
}
}
I see the received file log, but when it's under Nginx, there is never a busboy finished log, unlike when calling the API directly without Nginx.
I tried changing these Nginx configs but it did not work: client_max_body_size, client_body_buffer_size. It looks to me like the API receives only a chunk of the file for larger files, instead of the whole file, or all chunks, as it should.
Any help would be appreciated.
Thanks,
Simon

It turned out that the problem was elsewhere, I was starting to read the incoming stream before the file was fully uploaded, so it led to breaking the incoming stream for some reasons.

Related

What's the correct way to get rid of /public from a domain using node.js and nginx?

I've been structuring my website as follows:
someSite
app.js
public
index.html
css
assets
Project1
Project2
...
I'd like for the path to /public/Project1/index.html to be accessible by going to someSite.com/Project1, and similarly for subfolders. I've found this can be accomplished like this:
var express = require('express');
var app = express();
var http = require('http').createServer(app);
var io = require('socket.io')(http);
app.use('/public',express.static(path.join(__dirname, 'public')));
app.get('/', (req, res) => {
res.sendFile(__dirname + '/public/index.html');
});
app.get('/Project1', (req, res) => {
res.sendFile(__dirname + '/public/Project1/index.html');
});
But I don't know if this is standard, or inefficient. It's also breaking all of my relative calls, such as <img src="../assets/someImage.svg">, which no longer point to the correct place. It can be fixed by using absolute urls everywhere, but that feels wrong.
I'm using nginx as a reverse proxy server, which I think is the right place to make this change. My configuration is as follows:
server {
listen 80;
server_name someSite.com www.someSite.com;
rewrite ^(.*) https://$host$1 permanent;
}
server {
listen 443 ssl;
root /var/www/someSite/public;
server_name someSite.com;
ssl_certificate /path/to/credentials.crt;
ssl_certificate_key /path/to/credentials.key;
ssl_protocols TLSv1 TLSv1.1 TLSv1.2;
ssl_ciphers HIGH:!aNULL:!MD5;
location / {
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
proxy_http_version 1.1;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header Host $host;
proxy_pass http://localhost:3000/;
}
}
I still haven't figured out exactly what's wrong with my nginx configuration, but the hosting from node issue was just a matter of changing
app.use('/public',express.static(path.join(__dirname, 'public')));
to
app.use('/',express.static(path.join(__dirname, 'public')));
and removing subsequent app.get calls.

Node: PORT 443 requires elevated privileges error

I'm using PM2 to start the application and I pass PORT=443 as a parameter while starting the app. However, it returns with an error saying "PORT 443 requires elevated privileges". Though I have generated the certificate and key using openssl and referenced in the code. Appreciate your support
#!/usr/bin/env node
var app = require('../app');
var debug = require('debug')('ls-templates-server:server');
var https = require('https');
var fs = require('fs');
/**
* Get port from environment and store in Express.
*/
var port = normalizePort(process.env.PORT || '3000');
app.set('port', port);
var options = {
key: fs.readFileSync('/home/admin/cert/server.key'),
cert: fs.readFileSync('/home/admin/cert/server.cert')
};
var httpsServer = https.createServer(options, app);
/* for https (production stage) */
httpsServer.listen(port, "0.0.0.0");
httpsServer.on('error', onError);
httpsServer.on('listening', onListening);
/**
* Normalize a port into a number, string, or false.
*/
function normalizePort(val) {
var port = parseInt(val, 10);
if (isNaN(port)) {
// named pipe
return val;
}
if (port >= 0) {
// port number
return port;
}
return false;
}
/**
* Event listener for HTTP server "listening" event.
*/
function onListening() {
var addr = httpsServer.address();
var bind = typeof addr === 'string'
? 'pipe ' + addr
: 'port ' + addr.port;
debug('Listening on ' + bind);
}
Option 1 .
Run PM2 as sudo
If it doesn't work, achieve it using authbind
sudo apt-get install authbind
sudo touch /etc/authbind/byport/443
sudo chown %user% /etc/authbind/byport/443
sudo chmod 755 /etc/authbind/byport/443
Edit ~/.bashrc file and add
+alias pm2='authbind --deep pm2'
at the end of the file, and run
source ~/.bashrc
Finally ensure that pm2 is updated with authbind:
authbind --deep pm2 update
Option 2
Use a different PORT and use Nginx to reverse proxy your application
Eg : change your PORT to 3000
In Nginx , create a server block which forwards the request to your application.
Server Block Eg :
server {
#listen [::]:80;
server_name your-domain.com
#root /var/www/example.com;
#index index.html;
client_max_body_size 20M;
location / {
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header Host $http_host;
proxy_set_header X-NginX-Proxy true;
proxy_pass http://localhost:3000;
proxy_redirect off;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "Upgrade";
proxy_connect_timeout 500000;
proxy_send_timeout 500000;
proxy_read_timeout 500000;
send_timeout 500000;
}
listen 443 ssl;
ssl_certificate /home/admin/cert/server.cert;
ssl_certificate_key /home/admin/cert/server.key;
if ($scheme = http) {
return 301 https://$server_name$request_uri;
}
}
It's best to use Nginx / Authbind method, It's good to run as little as possible as a privileged user, as you want to restrict the potential damage in case someone exploits your program. You don't want to run your Node code as root unless you absolutely have to.
refer
https://pm2.keymetrics.io/docs/usage/specifics/#listening-on-port-80-w-o-root
https://www.digitalocean.com/community/tutorials/how-to-use-pm2-to-setup-a-node-js-production-environment-on-an-ubuntu-vps

Can we use NGINX as webapp for template engine

I have a requirement for basic html template webapp such as:
http://localhost:3000/myapp?param1=hello&param2=John is called it should return text/html response which looks like this:
<html>
<body>
<p>Nice to see you John. Platform greets you "hello".</p>
</body>
</html>
the name and greeting is templated from param. so template is something like this:
<html>
<body>
<p>Nice to see you {{param1}}. Platform greets you "{{param2}}".</p>
</body>
</html>
I have currently done this in node server using express.js and then the server is exposed publicly via nginx.conf:
server {
listen 80;
# server_name example.com;
location / {
proxy_pass http://private_ip_address:3000;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection 'upgrade';
proxy_set_header Host $host;
proxy_cache_bypass $http_upgrade;
}
}
I was wondering if this could be possible with some plugins or other configuration with bare nginx without hosting the node server on 3000 port.
I was able to solve this using only Nginx to program it using OpenResty's lua module.
The https://github.com/openresty/lua-nginx-module gives ability to program in nginx.conf, where one could use the existing lua libraries such as https://github.com/bungle/lua-resty-template for templating!
myapp.lua:
local template = require("resty.template")
local template_string = ngx.location.capture("/templates/greet.html")
template.render(template_string.body, {
param1 = ngx.req.get_uri_args()["param1"],
param2 = ngx.req.get_uri_args()["param2"]
})
greet.html:
<html>
<body>
<p>Nice to see you {{param1}}. Platform greets you "{{param2}}".</p>
</body>
</html>
nginx.conf:
worker_processes 1;
error_log logs/error.log;
events {
worker_connections 1024;
}
http {
root ./;
server {
listen 8090;
location /myapp {
default_type text/html;
content_by_lua_file ./lua/myapp.lua;
}
}
content_by_lua_file is where the power of openresty comes.
I described the complete process here: https://yogin16.github.io/2018/03/04/nginx-template-engine/
Hopefully, someone would find this helpful.
You can't render file with nginx.
Just send the file with nginx and the rewrite directive then inside the file just include some javascript to replace text content with query parameters
Nginx conf :
location / {
rewrite ^ /static/index.html break;
}
index.html:
<div>My content <span id="name"></span></div>
<script>
function getParameterByName(name, url) {
if (!url) url = window.location.href;
name = name.replace(/[\[\]]/g, "\\$&");
var regex = new RegExp("[?&]" + name + "(=([^&#]*)|&|#|$)"),
results = regex.exec(url);
if (!results) return null;
if (!results[2]) return '';
return decodeURIComponent(results[2].replace(/\+/g, " "));
}
document.getElementById("name").textContent = getParameterByName("foo"):
</script>

Multiple node.js projects with one domain name, each using path '/' as base url. Nginx & Express.js

I am successfully able to reverse proxy multiple node.js projects to different ports as in the Nginx set up below. What I am trying to accomplish however, is viewing the url as the root when it gets to the Node.js server.
For example, When someone goes to mydomain.com/projects/music_player, is it possible to have the Express application view the request url as just '/' instead of '/projects/music_player'.
My current Nginx setup, and Express configuration example are as follows.
Nginx:
server {
listen 80;
server_name mydomain_name.com;
location / {
proxy_pass http://localhost:8080;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Host $host;
proxy_cache_bypass $http_upgrade;
}
location /projects/music_player/ {
proxy_pass http://localhost:8000;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Host $host;
proxy_cache_bypass $http_upgrade;
}
}
I want to do this:
app.get("/", function (req, res) {
res.sendFile("index.html");
});
app.listen(8000);
Instead of this:
app.get("/projects/music_player", function (req, res) {
res.sendFile("index.html");
});
app.listen(8000);
I am not sure if that is even possible. The reason is that I would like each of my node node.js/express applications to be deployable as standalone applications, without restructuring the code. I am trying to avoid having a bunch of domain names for each project, plus I think it would be pretty cool.
Yes! Of course you can.
I think the best way to do this is to rewrite your entire program into one express app, but you say you want to avoid this.
Then I think the short way with your current setup is to make each program listen to a different port and then map routes to them in your nginx.
For example:
dog.js
app.get("/", function (req, res) {
res.sendFile("dog.html");
});
app.listen(8001);
cat.js
app.get("/", function (req, res) {
res.sendFile("cat.html");
});
app.listen(8002);
And finally in your nginx config file:
server {
listen 80;
server_name mydomain_name.com;
location / {
proxy_pass http://localhost:8001;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Host $host;
proxy_cache_bypass $http_upgrade;
}
location /cat/ {
rewrite /cat/ / break;
proxy_pass http://localhost:8002;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Host $host;
proxy_cache_bypass $http_upgrade;
}
}
Notice the rewrite directive, this prevents for the /cat/ location from being sent to your cat.js app that only expects the / route.
I ran into this same problem, where I didn't want to fully restructure my backend services while wanting to change the endpoint to match others.
I ended up using node-http-proxy with http-proxy-rules. Grabbing an example from the http-proxy-rules github README, you're solution would resemble the following.
var http = require('http'),
httpProxy = require('http-proxy'),
HttpProxyRules = require('http-proxy-rules');
// Set up proxy rules instance
var proxyRules = new HttpProxyRules({
rules: {
'.*/test': 'http://localhost:8080/cool', // Rule (1)
'.*/test2/': 'http://localhost:8080/cool2/' // Rule (2)
},
default: 'http://localhost:8080' // default target
});
// Create reverse proxy instance
var proxy = httpProxy.createProxy();
// Create http server that leverages reverse proxy instance
// and proxy rules to proxy requests to different targets
http.createServer(function(req, res) {
// a match method is exposed on the proxy rules instance
// to test a request to see if it matches against one of the specified rules
var target = proxyRules.match(req);
if (target) {
return proxy.web(req, res, {
target: target
});
}
res.writeHead(500, { 'Content-Type': 'text/plain' });
res.end('The request url and path did not match any of the listed rules!');
}).listen(6010, cb);
Happy hacking!

node.js: route request to different port on same host

I have a host computer which serves a number of webapplications (not node.js based). It does this using different ports. This means that for example the following applications are live:
app1: http://hostname:3000
app2: http://hostname:3001
app3: http://hostname:3003
Next to that I have a node.js based webapp (running on port 80) which I want to use as a sort of router. When someone navigates to http://localhost/app/app1. I want it to navigate to http://hostname:3000. This is relatively straightforward using a simple redirect. However, I would want to preserve the url http://localhost/app/app1. Can someone point me to a way to make this work using node.js/express?
My routing logic looks somewhat like this (pseudo-code).
app.route('/app/:appName')
.get(appEngine.gotoApp);
appEngine.gotoApp = function(req, res) {
redirectToApp logic
}
You probably better use Nginx setting up a reverse proxy with different locations per application.
It's not what you ask for because it does not use node.js, but if it's the only purpose, Nginx really suits your needs.
For example a Nginx configuration file like should work the way you want :
server {
listen 80;
server_name myapp.com;
location /app1 {
proxy_pass http://APP_PRIVATE_IP_ADDRESS:3000;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection 'upgrade';
proxy_set_header Host $host;
proxy_cache_bypass $http_upgrade;
}
location /app2 {
proxy_pass http://APP_PRIVATE_IP_ADDRESS:3001;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection 'upgrade';
proxy_set_header Host $host;
proxy_cache_bypass $http_upgrade;
}
location /app3 {
proxy_pass http://APP_PRIVATE_IP_ADDRESS:3003;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection 'upgrade';
proxy_set_header Host $host;
proxy_cache_bypass $http_upgrade;
}
}
If you use express, you can try to create the app with the cli express application generator.
It creates an express app and returns it with module exports.
In the server.js file it pass to listen function of the server instance the express app object.
You can create more server object and listen different app with different port.
var server = http.createServer(app);
server.listen(port);
var server2 = http.createServer(app2);
server2.listen(port2);
If you want to point different app based on the url, you can instance an express router instead of express object.
var app1 = express.Router();
Then you can set all your routes into this object with classic get or post or other methods.
Now you are able to pass the router as a middleware of your main express app.
app.use( "app1/", app1 );
You can also pass an express app to middleware, instead of router object, in order to gain the possibility of exec the app with a different url and port server listening.
There is a nice http-proxy lib designed exactly for that!
const httpProxy = require('http-proxy');
const url = require('url');
const proxy = httpProxy.createProxy();
const options = {
'/app/app1': 'http://localhost:3000',
'/app/app2': 'http://localhost:3001',
'/app/app3': 'http://localhost:3003',
}
require('http').createServer((req, res) => {
const pathname = url.parse(req.url).pathname;
for (const [pattern, target] of Object.entries(options)) {
if (pathname === pattern ||
pathname.startsWith(pattern + '/')
) {
proxy.web(req, res, {target});
}
}
}).listen(80);

Resources