Why does the browser send an OPTIONS request even though my frontend code is just making a POST request?
Asked Answered
O

2

17

My front-end code:

<form action="" onSubmit={this.search}>
  <input type="search" ref={(input) => { this.searchInput = input; }}/>
  <button type="submit">搜索</button>
</form>

// search method:
const baseUrl = 'http://localhost:8000/'; // where the Express server runs
search(e) {
  e.preventDefault();
  let keyword = this.searchInput.value;
  if (keyword !== this.state.lastKeyword) {
    this.setState({
      lastKeyword: keyword
    });
    fetch(`${baseUrl}search`, {
      method: 'POST',
      // mode: 'no-cors',
      headers: new Headers({
      'Content-Type': 'application/json'
      }),
      // credentials: 'include',
      body: JSON.stringify({keyword})
    })
  }
}

And My Express.js server code:

app.all('*', (req, res, next) => {
  res.header("Access-Control-Allow-Origin", "*");
  res.header('Access-Control-Allow-Methods', 'POST, GET, OPTIONS');
  res.header('Access-Control-Allow-Headers', 'Content-Type');
  // res.header('Access-Control-Allow-Credentials', true);
  res.header('Content-Type', 'application/json; charset=utf-8')
  next();
});

When I submit the form, I get two requests. One of them is an OPTIONS request, and another one is an POST request and the response to it is right:

two requests to /search with response codes 200 OPTIONS request to localhost:8000/search POST request to localhost:8000/search

As you can see, the Express server runs on port 8000, and the React development server runs on port 3000. localhost:3000 is requesting localhost:8000/search, and localhost:8000 is requesting another origin via using a POST method. However, only the second request works well. I don't know how this happen. Of course, If I make a GET request with querystring, things are normal. But I also want to know how to make a POST fetch with the request body.

Overabundance answered 24/10, 2017 at 7:16 Comment(0)
H
32

That OPTIONS request is sent automatically by your browser on its own, before it tries the POST request from your code. It’s called a CORS preflight.

https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS#Preflighted_requests has details.

The gist of it in this specific case is that the Content-Type: application/json request header that the code is adding is what triggers the browser to do that preflight OPTIONS request.

So the purpose of that particular preflight request is for the browser to ask the server, “Do you allow cross-origin POST requests that have a Content-Type header whose value isn’t one of application/x-www-form-urlencoded, multipart/form-data, or text/plain?”

And for the browser to consider the preflight successful, the server must send back a response with an Access-Control-Allow-Headers header that includes Content-Type in its value.

So I see that you’ve got res.header('Access-Control-Allow-Headers', 'Content-Type') in your current server code on http://localhost:8000/, and that’s the right value to set if you’re going to code it manually that way. But I think the reason that’s not working is because you don’t also have code that explicitly handles OPTIONS requests.

To fix that, you might try instead installing the npm cors package:

npm install cors

…and then doing something like this:

var express = require('express')
  , cors = require('cors')
  , app = express();
const corsOptions = {
  origin: true,
  credentials: true
}
app.options('*', cors(corsOptions)); // preflight OPTIONS; put before other routes
app.listen(80, function(){
  console.log('CORS-enabled web server listening on port 80');
});

That’ll handle the OPTIONS request, while also sending back the right headers and values.

Hedwig answered 24/10, 2017 at 7:19 Comment(3)
Thanks! I know that my fetch() function and the code that set response headers are not good, and I've tried many times(maybe you can see that many code is being commented). Can you tell me how to use fetch and set response headers correctly?Overabundance
Your fetch call is fine. But you need to enable CORS support in the http://localhost:8000/ server that the request is being sent to. See up my updated answer. That’ll cause the server to handle the preflight OPTIONS request correctly, and to send the necessary CORS headers back. Without that, your browser will block your frontend JavaScript code from being able to send that POST request as long as you’re adding that Content-Type: application/json request header to the request.Hedwig
In my case the Access-Control-Allow-Origin, Access-Control-Allow-Headers and Access-Control-Allow-Method were not enough, I had to add Access-Control-Max-Age: 86400 to make it work. I guess the browser needs the information for how long the "access rights" are validBilbrey
L
-2

I faced similar problem recently and use FormData to send my request payload, and you don't need to send custom headers. FormData object takes care of the authentication credential for you, but it uses SSL and can only work with https which is not provided by localhost because local host uses http protocol. Check form-data npm documentation for detail information on how to install and use it in your express app but FormData is readily available for use in major browsers. here a simple code example npm install --save form-data

`

var FormData = require('form-data');
var http = require('http'); 
var form = new FormData();
 
http.request('http://nodejs.org/images/logo.png', function(response) {
  form.append('my_field', 'my value');
  form.append('my_buffer', new Buffer(10));
  form.append('my_logo', response);
});

`

Legionary answered 6/10, 2020 at 9:20 Comment(4)
"you don't need to send custom headers" — Yes, you do.Circumambient
"FormData object takes care of the authentication credential for you" — No, it doesn't.Circumambient
"can only work with https which is not provided by localhost because local host uses http protocol" — Nothing is stopping you from installing a server that supports HTTPS and listens on localhost … and FormData works just fine with HTTP anyway.Circumambient
"Check form-data npm documentation for detail information on how to install and use it in your express app" — They are making the request from the browser so that doesn't help.Circumambient

© 2022 - 2024 — McMap. All rights reserved.