Cannot make POST request with JSON from react/axios to restify server

1.4k Views Asked by At

I have a restify set up like this:

var restify = require('restify');

const server = restify.createServer();

//server.use(restify.plugins.acceptParser(server.acceptable)); // [1]
server.use(restify.plugins.queryParser());
server.use(restify.plugins.bodyParser());

server.use(function(req, res, next) {
  console.log(req); // <-- Never see the POST from React here
  res.setHeader('Access-Control-Allow-Origin', '*');
  res.setHeader('Access-Control-Allow-Headers', '*');
  res.setHeader('Access-Control-Allow-Methods', '*');
  next();
});

I define a bunch GET and POST routes and so far it worked perfectly fine. I called the server from an Android application, Python scripts and for testing simply using curl. No issues at all. Neat!

However, now I've implemented a Web application with React and want to make a request to the restify API using the axios package. GET requests are fine, so I exclude any typos in the URL or such things.

But a POST request like the following won't work:

var data = {"test": "hello"};
axios.post("http://.../api/v1/message/question/public/add", data)
  .then(function (response) {
    console.log("Test question sent!");
  })
  .catch(function (error) {
    console.log(error);
  });

When I check with the browser developer tools, I can see that the browser is trying to make an OPTIONS request (not a POST) to that URL. I assume, from what I've read, that is because the browser is making a "preflighted request". The problem is that I get an 405 Method Not Allowed error:

Request URL: http://.../api/v1/message/question/public/add
Request method: OPTIONS
Remote address: ...
Status code: 405 Method Not Allowed

Response headers (178 B)    
  Server: restify
  Allow: POST
  Content-Type: application/json
  Content-Length: 62
  Date: Sat, 09 Sep 2017 08:16:32 GMT
  Connection: keep-alive
Request headers (485 B) 
  Host: ...
  User-Agent: Mozilla/5.0 (X11; Ubuntu; Linu…) Gecko/20100101 Firefox/55.0
  Accept: text/html,application/xhtml+xm…plication/xml;q=0.9,*/*;q=0.8
  Accept-Language: en-ZA,en-GB;q=0.8,en-US;q=0.5,en;q=0.3
  Accept-Encoding: gzip, deflate
  Access-Control-Request-Method: POST
  Access-Control-Request-Headers: content-type
  Origin: http://...
  DNT: 1
  Connection: keep-alive

But why? I allow all Access-Control-Allow-Methods in restify. Everything works, except from POST requests and only when they come from the browser (with the React Web app). I think it's because of the OPTIONS request, but I have no idea how to handle it.

By the way with JSON.stringify(data), the POST requests gets through, but the API expects Json and not a string. And since with all other means it works perfectly fine, I don't want to change the restify code just to accommodate this issue.

[1] If I use this line, I get the following error: AssertionError [ERR_ASSERTION]: acceptable ([string]) is required at Object.acceptParser (/home/bob/node_modules/restify/lib/plugins/accept.js:30:12)

2

There are 2 best solutions below

2
On

If your server isn’t allowing OPTIONS by default, you can add an explicit handler:

server.opts(/\.*/, function (req, res, next) {
    res.send(200);
    next();
});

Another possible problem is that you won’t have the effect intended the following headers:

res.setHeader('Access-Control-Allow-Headers', '*');
res.setHeader('Access-Control-Allow-Methods', '*');

The * wildcard values there are allowed by the spec, but browsers don’t yet support them. So what you must do instead is, explicitly specify in those values the methods and headers to allow:

res.setHeader('Access-Control-Allow-Headers', 'content-type');
res.setHeader('Access-Control-Allow-Methods', 'POST');

That’s because the request headers shown for the CORS preflight OPTIONS request have this:

Access-Control-Request-Method: POST
Access-Control-Request-Headers: content-type
Origin: http://...

And what those request headers indicate is, the browser’s asking the server, Some code running at this origin wants to make a POST request to your server that adds a specific Content-Type to the request. Are you OK with receiving POST requests that add their own Content-Type?

So in response to that preflight OPTIONS request, the browser expects to receive an Access-Control-Allow-Methods response header which explicitly allows POST, along with an Access-Control-Allow-Headers response header which explicitly allows Content-Type.

0
On

After a couple of more hours, I finally found a solution. I've changed my restify server code as follows:

var restify = require("restify");
var corsMiddleware = require('restify-cors-middleware')

var cors = corsMiddleware({
  preflightMaxAge: 5, //Optional
  origins: ['*'],
  allowHeaders: ['API-Token'],
  exposeHeaders: ['API-Token-Expiry']
});


// WITHOUT HTTPS
const server = restify.createServer();

server.pre(cors.preflight);
server.use(cors.actual);
...
(rest is the same as above)

To be honest, I've no real idea what it actual does. But it's working and it the cost me too much energy for today already. I hope it will help others at some point. Everything seems to be rather recent changes from restify.