4
votes

I am running an NginX server hosted on a Digital Ocean Droplet at 'pocket-caravan.com'.

The goal was to build the react bundle which would link to all css/js/images and use nginx to handle serving static content. The API server is an Express app which is running on some port, and I proxy all requests from the static bundle to this.

When I develop locally, I am able to send JSON data to my API from the front-end.

When I deploy my API, I can send JSON payloads through Postman

But the PROBLEM is that when I go to my app by accessing the static files served by NginX, and I send a POST request to the live API, the req.body is empty!

I can inspect the JS console and see that the fetch request is correctly sending a JSON data body.

The request is routed to the live API correctly, but the data is empty! The NginX proxy does not pass along the request body and I don't know why??

I have a problem - While I can access all the static files, my API requests are being proxied and routed correctly to the API, but when Express accesses req.body, it's empty!

/etc/nginx/sites-available/pocket-caravan.com

server {

    root /var/www/pocket-caravan.com/html;
    index index.html index.html index.nginx.debian.html;

    server_name pocket-caravan.com www.pocket-caravan.com;


    location /omni-commerce {
        index index.html;
    }

    location /static {
        try_files /omni-commerce/$uri /omni-commerce/$uri/;
    }

    location /api {
        proxy_pass http://localhost:3001;
        proxy_http_version 1.1;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Host $host;
        proxy_cache_bypass $http_upgrade;
        proxy_set_header Connection 'upgrade';
        access_log /var/www/pocket-caravan.com/logs/logs.txt;
    }


    listen [::]:443 ssl ipv6only=on; # managed by Certbot
    listen 443 ssl; # managed by Certbot
    ssl_certificate /etc/letsencrypt/live/pocket-caravan.com/fullchain.pem; # m$
    ssl_certificate_key /etc/letsencrypt/live/pocket-caravan.com/privkey.pem; #$
    include /etc/letsencrypt/options-ssl-nginx.conf; # managed by Certbot
    ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem; # managed by Certbot

}
server {
    if ($host = www.pocket-caravan.com) {
        return 301 https://$host$request_uri;
    } # managed by Certbot

    if ($host = pocket-caravan.com) {
        return 301 https://$host$request_uri;
    } # managed by Certbot
    listen 80;
    listen [::]:80;

    server_name pocket-caravan.com www.pocket-caravan.com;
    return 404; # managed by Certbot




}

Reproducing this problem:

I access my app: https://pocket-caravan.com/omni-commerce/

Send a POST request https://pocket-caravan.com/api/register?pathway=marketplace

Sample JSON data: { "firstName": "foo", "lastName": "bar", "email": "[email protected]", "password": "123123"
}

Express App running at port 3001:

import util from 'util';
import helmet from 'helmet';
import express from 'express';
import cookieParser from 'cookie-parser'
import morgan from 'morgan';
import { handleError } from '../lib/utils/logger';
import getEnv from '../lib/utils/get-env';

import { establishMongooseConnection } from '../lib/mongo/mongoose-db';
import { usersRouter } from './router/users';

getEnv()

const app = express();

app.use(morgan('common'));
app.use(express.json());
app.use(express.urlencoded({ extended: true }));
app.use(cookieParser());
app.use(helmet());

if (app.get('env') === 'development') {
  console.log('Configuring Access Control Allow Origin header for Local Development.');
  app.use('*', (req, res, next) => {
    res.header(
      'Access-Control-Allow-Origin',
      `${req.get('origin')}`,
    );
    res.header(
      'Access-Control-Allow-Headers',
      'Origin, X-Requested-With, Content-Type, Accept, Authorization, Set-Cookie',
    )
    res.header(
      'Access-Control-Allow-Methods',
      'POST, GET, OPTIONS, DELETE, PUT'
    )
    res.header('Access-Control-Allow-Credentials', true);
    next();
  });
}

if (app.get('env') === 'production') {
  // Trust DigitalOcean - NginX Proxy 
  // https://expressjs.com/en/guide/behind-proxies.html
  app.set('trust proxy', 'loopback', process.env.DIGITAL_OCEAN_DROPLET_IP)
}

establishMongooseConnection()
  .then(connection => {
    if (connection.success) {
      console.log(connection.message);
    };
  }).catch(err => {
    console.log("Catching mongoose error...")
    console.log(err);
  })


app.get('/', (req, res) => {
  res.send('GET /')
});

app.get('/api', (req, res) => {
  res.send('GET /api')
});

app.use('/api', usersRouter);

// Universal Error Handler
app.use('*', (err, req, res, next) => {
  handleError(err);
  res.status(err.status || 500).json({ error: err.stack, message: err.message });
})

app.listen(process.env.PORT, () => {
  console.log(`Server listening on port ${process.env.PORT}`);
});

This works fine from localhost - webpackdevserver when I send the request to https://pocket-caravan.com/api/register?pathway=marketplace

However, when the deployed built code is served from the same NginX server that proxies the requests, the request body is inaccessible by Express.

I don't know enough about NginX to proceed debugging this, I can't seem to log the request body, I would really appreciate any help!

Here is the info from the Network panel about the failed request: (Throws 500 since req.body.email is undefined and crashes middleware)

General
Request URL: https://pocket-caravan.com/api/register?pathway=omni
Request Method: POST
Status Code: 500 Internal Server Error
Remote Address: 67.205.154.96:443
Referrer Policy: no-referrer-when-downgrade

Response Headers
Access-Control-Allow-Credentials: true
Access-Control-Allow-Headers: Origin, X-Requested-With, Content-Type, Accept, Authorization, Set-Cookie
Access-Control-Allow-Methods: POST, GET, OPTIONS, DELETE, PUT
Access-Control-Allow-Origin: https://pocket-caravan.com
Connection: keep-alive
Content-Length: 1633
Content-Type: application/json; charset=utf-8
Date: Wed, 08 Jan 2020 08:52:16 GMT
ETag: W/"661-PIPnVrxYNILoG0ylml9L0Camw1w"
Server: nginx/1.14.0 (Ubuntu)
Strict-Transport-Security: max-age=15552000; includeSubDomains
X-Content-Type-Options: nosniff
X-DNS-Prefetch-Control: off
X-Download-Options: noopen
X-Frame-Options: SAMEORIGIN
X-XSS-Protection: 1; mode=block

Request-Headers
Accept: application/json
Accept-Encoding: gzip, deflate, br
Accept-Language: en-US,en;q=0.9
Connection: keep-alive
Content-Length: 94
Content-Type: text/plain;charset=UTF-8
Cookie: Stuff=random--!; Authorization-Omni=sec0ret%20encu0ingdgin
DNT: 1
Host: pocket-caravan.com
Origin: https://pocket-caravan.com
Referer: https://pocket-caravan.com/omni-commerce/
Sec-Fetch-Mode: no-cors
Sec-Fetch-Site: same-origin
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_2) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/79.0.3945.117 Safari/537.36
pathway: omni
{firstName: "asdasda", lastName: "asdas", email: "[email protected]", password: "asdasdsdasd"}
firstName: "asdasda"
lastName: "asdas"
email: "[email protected]"
password: "asdasdsdasd"
2
@RichardSmith That was my mistake when I was pasting my nginx config, sorry. It was actually there, I missed it with my cursor. - Mikhail

2 Answers

3
votes

I figured out the problem. It was simple. I failed to pass the correct 'Content-Type' header to the proxy. I looked at the request output and saw this while expecting to pass JSON:

Content-Type: text/plain;charset=UTF-8

Express body-parser/json-parser didn't parse the body, and did not pass it to req.body.

Adding this into my /api proxy handler fixed things:

proxy_set_header content-type "application/json";

    location /api {
        proxy_pass http://localhost:3001;
        proxy_http_version 1.1;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Host $host;
        proxy_set_header content-type "application/json";
        proxy_cache_bypass $http_upgrade;
        proxy_set_header Connection 'upgrade';
        access_log /var/www/pocket-caravan.com/logs/logs.txt;
    }
2
votes

Might help other, so I have been in same problem as this, the url call is received but without the data. After written the nginx config as suggested in the accepted answer the problem still exist. Then I find out that I need to call the server not with http but with https. So if you still stuck like me then check your url.

Didn't know why the call with http is routed without data but it did.