Cross-origin Resource Sharing Chinese name “cross-domain Resource Sharing” referred to as “CORS”, it broke through a request in the browser can only be in the same origin to the server to obtain data restrictions.

This article will start with an example, examine whether the browser or server constraints are present, explain when precheck requests are generated, and, along the way, how to implement solutions to the problem. It will conclude with a summary of how to use the CORS module in Node.js and the Nginx reverse proxy to solve the cross-domain problem.

This article uses Node.js to do some Demo demonstrations, after each section will also give the code Demo address.

Browser or server restrictions

Is CORS a browser-side or server-side limitation? To better illustrate the problem, let’s start with an example.

Start with an example

index.html

Create index. HTML using the fetch calls to http://127.0.0.1:3011/api/data

<body>
  <! -- < script SRC = "https://cdn.bootcdn.net/ajax/libs/fetch/3.0.0/fetch.min.js" > < / script > -- >
  <script>
    fetch('http://127.0.0.1:3011/api/data');
  </script>
</body>
Copy the code

client.js

Create client.js to load the index.html above. Set the port number to 3010.

const http = require('http');
const fs = require('fs');
const PORT = 3010;
http.createServer((req, res) = > {
  fs.createReadStream('index.html').pipe(res);
}).listen(PORT);
Copy the code

server.js

Create server.js to start a service that returns different responses based on different requests. Set the port number to 3011.

const http = require('http');
const PORT = 3011;

http.createServer((req, res) = > {
  const url = req.url;
  console.log('request url: ', url);
  if (url === '/api/data') {
    return res.end('ok! ');
  }
  if (url === '/script') {
    return res.end('console.log("hello world!" ); ');
  }
}).listen(PORT);

console.log('Server listening on port ', PORT);
Copy the code

Test analysis cause

Run the client. Js and server.js browsers above and enter http://127.0.0.1:3010 to open the Network TAB in Chrome to view the request information, as shown below:

On the left is the 127.0.0.1:3011/ API /data interface that uses the FETCH request. You can see the Origin field in the request header, showing our current request information. There are also three fields starting with sec-fetch -*, which is a new draft Fetch Metadata Request Headers.

Sec-fetch -Mode represents the Mode of request. It can be seen from the comparison of the left and right results that the left side is cross-domain. Sec-fetch -Site indicates whether the request is same-origin or cross-domain. Since the two requests are sent by port 3010 to request port 3011, they do not comply with the same-origin policy.

Look at the browser Console log information, according to the prompt that reason is from “http://127.0.0.1:3010” access “http://127.0.0.1:3011/api/data” blocked by the CORS strategy There is no “access-Control-Allow-Origin” header.

Service 3011 service 3011 service 3011 service 3011

Server listening on port  3011
request url:  /script
request url:  /api/data
Copy the code

If a request message is received on the server, the server is working properly.

We can also test the curl command on the terminal, and the terminal can request normally when it is out of the browser environment.

$curl http://127.0.0.1:3011/api/data ok!Copy the code

Code examples for this section:

github.com/qufei1993/http-protocol/tree/master/example/cors/01
Copy the code

Summarize and answer the initial question

Browsers restrict cross-source HTTP requests from within scripts, such as XMLHttpRequest and the Fetch API we use in this example follow the same origin policy.

When a request to send out in the browser, the server will be received and will be processing and response, but the browser to parse response after the request, found that do not belong to the browser’s same-origin policy (address inside of protocol, domain name and port number are the same) did not contain the correct CORS response headers, and return the result to intercept the browser.

Preview the request

Before sending the actual request, the client sends a request in the OPTIONS method to confirm the request to the server. If the request passes, the browser will initiate the real request. In this way, the cross-domain request can avoid affecting the user data on the server.

Looking at this you may be wondering why the above example does not have a precheck request? CORS divides requests into two categories: simple and non-simple. Our case above is a simple request, so there is no precheck request.

Let’s continue to look at how simple and non-simple requests are defined.

Precheck request definitions

According to the MDN document definition, the request methods are: GET, POST, and HEAD. The request header content-Type is: Text /plain, multipart/form-data, Application/X-www-form-urlencoded are “simple requests” that do not trigger CORS precheck requests.

For example, a CORS precheck request is triggered if the content-type of the request header is Application/JSON, which is also referred to as a “non-simple request.”

“MDN Documentation developer.mozilla.org/en-US/docs/Web/HTTP/CORS Simple Requests” has more field definitions for simple requests.

Example precheck request

Learn about the precheck request with an example.

Setting the Client

Add some Settings to the fetch method in index. HTML, set the request method to PUT, and add a custom field test-cors to the request header.

<script>
  fetch('http://127.0.0.1:3011/api/data', {
    method: 'PUT'.headers: {
      'Content-Type': 'text/plain'.'Test-Cors': 'abc'}});</script>
Copy the code

This code will detect that it is not a simple Request, and will execute a pre-check Request. The Request Headers will have the following information:

OPTIONS/API /data HTTP/1.1 Host: 127.0.0.1:3011 Access-Control-request-method: PUT access-Control-request-headers: Content-type,test-cors Origin: http://127.0.0.1:3010 sec-fetch -Mode: CORSCopy the code

This method is defined in HTTP/1.1. An important field Origin indicates the source of the request. The server can determine whether the request is a valid source based on this field. For example, because the same origin policy is not restricted in Websocket, the server can use this field to determine the value.

Access-control-request-method tells the server that the actual Request will use the PUT Method.

Access-control-request-headers tells the server that the actual Request will use two header fields, Content-Type and test-cors. If content-Type is specified as a simple Request, access-Control-request-headers will tell the server only test-cors.

Setting the Server

The client setup was explained above, but also the server support is needed to enable the request to respond properly.

Modify our server.js to set the Response Headers code as follows:

res.writeHead(200, {
  'Access-Control-Allow-Origin': 'http://127.0.0.1:3010'.'Access-Control-Allow-Headers': 'Test-CORS, Content-Type'.'Access-Control-Allow-Methods': 'PUT,DELETE'.'Access-Control-Max-Age': 86400
});
Copy the code

Why the above configuration? When prechecking the request, the browser sends several important information to the server: Origin, PUT Method, and Headers Content-type. The test-cORS server also needs to set some parameters and respond to the request after receiving it.

Access-control-allow-origin indicates that http://127.0.0.1:3010 is accessible. This field can also be set to * to Allow any cross-source requests.

Access-control-allow-methods indicates that the server allows clients to use PUT and DELETE Methods to initiate requests. You can set more than one method at a time to indicate all cross-domain Methods supported by the server, not just the one currently requested. In this way, the server can avoid multiple pre-checking requests.

Access-control-allow-headers Indicates that the server allows the request to carry test-CORS, Content-Type or multiple fields.

Access-control-max-age Specifies the validity period of the response, in seconds. The browser does not have to make another precheck request for the same request during the valid time. It is also important to note that this value must be less than the maximum duration of maintenance by the browser itself, otherwise it is invalid.

OPTIONS request is sent first, and the method and Headers information of this request is set in the request header. The server also responds to the Response. After OPTIONS is successful, the browser immediately initiates the real request we need. As shown on the right, Resquest Method is PUT.

Code examples for this section:

github.com/qufei1993/http-protocol/tree/master/example/cors/02
Copy the code

CORS and certification

For cross-domain XMLHttpRequest or Fetch requests, the browser does not send authentication information. For example, if we want to send Cookie information in a cross-domain request, we need to do some Settings:

To see the results, I first defined a cookie information id=NodejsRoadmap.

The emphasis is on setting the authentication fields. The fetch example in this article sets the credentials: “include” or withCredentials:”include” if it is XMLHttpRequest

<body>
  <script>
    document.cookie = `id=NodejsRoadmap`;
    fetch('http://127.0.0.1:3011/api/data', {
      method: 'PUT'.headers: {
        'Content-Type': 'application/json'.'Test-Cors': 'abc',},credentials: "include"
    });
  </script>
</body>
Copy the code

After the above Settings, the browser sends Cookies to the server when sending actual requests, and the server also needs to set the access-Control-allow-credentials response header in the response

res.writeHead(200, {
  'Access-Control-Allow-Origin': 'http://127.0.0.1:3010'.'Access-Control-Allow-Credentials': true
});
Copy the code

If the server does not set the browser, it will not respond properly and will report a cross-domain error, as shown below:

Access to fetch the at 'http://127.0.0.1:3011/api/data' from origin 'http://127.0.0.1:3010' has had been blocked by CORS policy:  Response to preflight request doesn't pass access control check: The value of the 'Access-Control-Allow-Credentials' header in the response is '' which must be 'true' when the request's  credentials mode is 'include'.Copy the code

It is also important to note that if we set the credentials: “include” in the request, the server cannot set access-Control-allow-origin: “*” can only be set to an explicit address.

Code examples for this section:

github.com/qufei1993/http-protocol/tree/master/example/cors/03
Copy the code

Several approaches to solving cross-domain problems

After understanding the causes of cross-domain through the above analysis, it is not difficult to solve them. In fact, the above explanation also provides solutions. For example, in Node.js, we can set access-Control-allow-origin, access-Control-expose-headers, access-Control-allow-methods, etc. But in the actual development of this setting is cumbersome, the following several common solutions.

Use the CORS module

In Node.js it is recommended that you use the cORS module github.com/expressjs/c… .

In our examples in this section, we have been using the Node.js native module to write our examples, which can be rewritten after the introduction of the CORS module as follows:

const http = require('http');
const PORT = 3011;
const corsMiddleware = require('cors') ({origin: 'http://127.0.0.1:3010'.methods: 'PUT,DELETE'.allowedHeaders: 'Test-CORS, Content-Type'.maxAge: 1728000.credentials: true}); http.createServer((req, res) = > {
  const { url, method } = req;
  console.log('request url:', url, ', request method:', method);
  const nextFn = () = > {
    if (method === 'PUT' && url === '/api/data') {
      return res.end('ok! ');
    }
    return res.end();
  }
  corsMiddleware(req, res, nextFn);
}).listen(PORT);
Copy the code

Cors does not execute nextFn until after the precheck request or after the preflightContinue attribute is set in the precheck request and option. It does not execute nextFn if the precheck fails.

If you’re using the Express.js framework, it’s easy to use, as follows:

const express = require('express')
const cors = require('cors')
const app = express()

app.use(cors());
Copy the code

JSONP

The browser allows the link, img, script tag to load some content on the path to request, is allowed to cross the domain, so the implementation principle of JSONP is to load a link in the script tag, to access a server request, return the content.

<body>
  <script>
    / / the fetch (' http://127.0.0.1:3011/api/data ', {
    // method: 'PUT',
    // headers: {
    // 'Content-Type': 'application/json',
    // 'Test-Cors': 'abc',
    / /},
    // credentials: "include"
    // });
    <srcipt src="http://127.0.0.1:3011/api/data"></srcipt>
  </script>
</body>
Copy the code

JSONP supports only GET requests, which is obviously not as powerful as the CORS module.

Nginx proxy servers are configured across domains

After using the Nginx proxy server, the request will not directly arrive at our Node.js server. The request will pass through Nginx after setting some cross-domain information and then be forwarded to our Node.js server by Nginx. So at this time our Nginx server to listen to port 3011, we change the node.js service port 30011, simple configuration as follows:

server { listen 3011; server_name localhost; Location / {if ($request_method = 'OPTIONS') {add_header 'access-control-allow-origin' 'http://127.0.0.1:3010'; add_header 'Access-Control-Allow-Methods' 'PUT,DELETE'; add_header 'Access-Control-Allow-Headers' 'Test-CORS, Content-Type'; add_header 'Access-Control-Max-Age' 1728000; add_header 'Access-Control-Allow-Credentials' 'true'; add_header 'Content-Length' 0; return 204; } add_header 'Access - Control - Allow - Origin' 'http://127.0.0.1:3010'. add_header 'Access-Control-Allow-Credentials' 'true'; Proxy_pass http://127.0.0.1:30011; proxy_set_header Host $host; }}Copy the code

Code examples for this section:

github.com/qufei1993/http-protocol/tree/master/example/cors/04
Copy the code

conclusion

If you are a front-end developer, working hard to avoid can cross domain problems, though it belongs to the browser’s same-origin policy limit, but in order to solve the problem still need to the browser and server to support, hope to read to the readers of this article is to understand the causes of cross-domain, finally gives several solutions, also hope to be able to solve the problem of you for cross domain this confusion.

About the author: May Jun, software designer, author of the public account “Nodejs Technology Stack”.