The original address

Cross domains. Everything you need to know is here

Before reading this article, I hope you have some basic JS/Node knowledge. This article does not introduce how to use Ajax to make asynchronous requests. If you do not know, you can read it first:

Ajax knowledge system overview

Recently, I was often asked how to solve the problem of cross-domain in the interview. After reading some articles on the Internet, many articles were not clearly introduced, which often confused the readers (me). So today, I sorted out the commonly used cross-domain skills and wrote this article about cross-domain.

  1. This section describes common cross-domain solutions and their advantages and disadvantages
  2. Simulating a real cross-domain scenario, with a simple example behind each scenario, allows readers to type code with me and intuitively understand these cross-domain techniques

All the code in this article can be downloaded from the Github repository. You can view it as follows:

git clone https://github.com/happylindz/blog.git
cd blog/code/crossOrigin/
yarn Copy the code

I suggest you clone it so that you can read the code and test it with me.

The same-origin policy

Those of you who have used Ajax know its convenience. It can realize local refresh without submitting the complete page to the server. It is widely used in SPA applications today. This makes it difficult to inject iframe or Ajax applications.

Simply put, a domain name is a domain name only when the protocol, domain name, and port number are the same. Otherwise, cross-domain processing is required.

Cross domain method

Today we introduce seven commonly used cross-domain techniques, which can be roughly divided into IFrame cross-domain and API cross-domain request.

Here are three ways that apis can cross domains:

1. The json:

JSONP, or JSON with Padding, can be used to solve cross-domain data access problems in older browsers.

Since calling js files on web pages is not affected by the browser’s same-origin policy, cross-domain requests can be made with script tags:

  1. First, the front end needs to set up the callback function and take it as an argument to the URL.
  2. When the server receives the request, it retrieves the callback function name with this parameter and returns the data in the parameter
  3. After receiving the result, the browser will run it as a script because it is a script tag, so as to achieve the purpose of cross-domain data retrieval

The key reason why JSONP can be cross-domain is that the page is not affected by the same origin policy, which is equivalent to sending an HTTP request to the back end, agreeing the function name with the back end, and then dynamically calculating the return result and returning it to the front end to execute the JS script, which is equivalent to a “dynamic JS script”.

Let’s try this with an example:

Back-end logic:

// jsonp/server.js const url = require('url'); require('http').createServer((req, res) => { const data = { x: 10 }; Const callback = url.parse(req.url, true).query.callback; console.log(callback); res.writeHead(200); res.end(`${callback}(${JSON.stringify(data)})`); }), listen (3000, '127.0.0.1); Console. log(' Start service, listen on 127.0.0.1:3000');Copy the code

Front-end logic:

// jsonp/index.html <script> function jsonpCallback(data) {alert(' get X data :' + data.x); } < / script > < script SRC = "http://127.0.0.1:3000? callback=jsonpCallback"></script>Copy the code

Then start the service on the terminal:

I can use script commands because I set up script commands in package.json:

{// enter yarn jsonp equals "node./jsonp/server.js & http-server./jsonp" "scripts": {"jsonp": "node ./jsonp/server.js & http-server ./jsonp", "cors": "node ./cors/server.js & http-server ./cors", "proxy": "node ./serverProxy/server.js", "hash": "http-server ./hash/client/ -p 8080 & http-server ./hash/server/ -p 8081", "name": "http-server ./name/client/ -p 8080 & http-server ./name/server/ -p 8081", "postMessage": "http-server ./postMessage/client/ -p 8080 & http-server ./postMessage/server/ -p 8081", "domain": "http-server ./domain/client/ -p 8080 & http-server ./domain/server/ -p 8081" }, // ... }Copy the code
Yarn jSONp // Port 3000 and port 8080 belong to different domain names. // Check the effect at localhost:3000 to receive background data. 10Copy the code

Open a browser and access localhost:8080 to see the obtained data.

So far, fetching data across domains through JSONP has been successful, but there are some advantages and disadvantages to this approach:

Advantages:

  1. It is not constrained by the same origin policy in the way that the XMLHttpRequest object implements Ajax requests
  2. Compatibility is good and runs well in older browsers
  3. No XMLHttpRequest or ActiveX support is required; After the request is complete, the result can be returned by calling callback.

Disadvantages:

  1. It supports GET requests rather than HTTP requests from other class lines such as POST.
  2. It only supports cross-domain HTTP requests and does not solve the problem of data communication between two pages or iframes of different domains
  3. Connection exceptions on Jsonp requests cannot be caught and can only be handled through timeouts

CORS:

CORS, or Cross-Origin Resource Sharing, is a W3C standard that allows browsers to issue XMLHttpRequest requests to cross-source servers, overcoming the limitation that Ajax can only be used in the same source.

CORS requires both browser and server support to work, and for developers, CORS communication is no different from same-origin Ajax communication, with the same code. As soon as the browser discovers that an Ajax request crosses the source, it automatically adds some additional headers, and sometimes an additional request, but the user doesn’t feel it.

Therefore, the key to CORS communication is the server. As long as the server implements the CORS interface, it can communicate across domains.

The front-end logic is simple, as long as you make a normal Ajax request:

// cors/index.html <script> const xhr = new XMLHttpRequest(); XHR. Open (' GET 'and' http://127.0.0.1:3000 ', true); xhr.onreadystatechange = function() { if(xhr.readyState === 4 && xhr.status === 200) { alert(xhr.responseText); } } xhr.send(null); </script>Copy the code

This might seem like a normal asynchronous Ajax request, but the key is what happens when the server receives the request:

// cors/server.js require('http').createServer((req, res) => { res.writeHead(200, { 'Access-Control-Allow-Origin': 'http://localhost:8080', 'Content-Type': 'text/html; charset=utf-8', }); Res.end (' Here's the data you want: 1111'); }), listen (3000, '127.0.0.1); Console. log(' Start service, listen on 127.0.0.1:3000');Copy the code

The key is to set the access-Control-allow-origin value in the corresponding header. The value must be the same as the Origin value in the request header to take effect. Otherwise, cross-domain failure will occur.

Yarn cors: localhost:3000

The key to success is whether access-Control-Allow-Origin contains the domain name of the requested page. If it does not, the browser will consider it a failed asynchronous request and will call the function in xhr.onerror.

Advantages and disadvantages of CORS:

  1. Easy to use, more safe
  2. POST requests are supported
  3. CORS is a new solution to cross-domain problems. It has compatibility problems and only supports IE 10 or above

This is just a brief introduction to CORS. If you want to understand how it works in more detail, you can check out the following article:

Cross-domain resource Sharing CORS details – Ruan Yifeng’s network log

3. Server proxy:

The server proxy, as its name implies, sends requests to the backend when you need a cross-domain request, asks the backend to do the request for you, and finally sends the results back to you.

Suppose there is such a scenario, your page needs for CNode: Node. Js professional Chinese community BBS on some data, such as through https://cnodejs.org/api/v1/topics, at that time, because of the different domains, so you can request the back-end, let them to this request to forward.

The code is as follows:

// serverProxy/server.js const url = require('url'); const http = require('http'); const https = require('https'); const server = http.createServer((req, res) => { const path = url.parse(req.url).path.slice(1); if(path === 'topics') { https.get('https://cnodejs.org/api/v1/topics', (resp) => { let data = ""; resp.on('data', chunk => { data += chunk; }); resp.on('end', () => { res.writeHead(200, { 'Content-Type': 'application/json; charset=utf-8' }); res.end(data); }); })}}). Listen (3000, '127.0.0.1); Console. log(' Start service, listen on 127.0.0.1:3000');Copy the code

Through the code you can see, when you visit http://127.0.0.1:3000/topics server receives the request, will you send the request https://cnodejs.org/api/v1/topics finally gets the data sent to the browser.

Start the service yarn proxy and access http://localhost:3000/topics to see the effect:

The cross-domain request succeeded. We’ve covered the pure way to get cross-domain requests for back-end data, as well as the four other ways to communicate with other pages across domains through iframe.

The location. The hash:

In the URL, http://www.baidu.com#helloworld’s “# helloWorld “is location.hash. Changing the hash value will not refresh the page, so you can use the hash value to pass data. Of course, the amount of data is limited.

If there is a file named index. HTML under localhost:8080 and a message is passed to data. HTML under localhost:8081, the index. HTML first creates a hidden iframe, Iframe SRC points to localhost:8081/data.html, and the hash value is passed as a parameter.

/ / hash/client/index. The HTML corresponding localhost: 8080 / index. The HTML < script > let ifr = document. The createElement method (" iframe "); ifr.style.display = 'none'; ifr.src = "http://localhost:8081/data.html#data"; document.body.appendChild(ifr); function checkHash() { try { let data = location.hash ? location.hash.substring(1) : ''; Console. log(' Obtained data is: ', data); }catch(e) {}} window.adDeventListener ('hashchange', function(e) {console.log(' get data: ', location.hash.substring(1)); }); </script>Copy the code

Data. HTML changes the hash value of index. HTML using the parent. Location. Hash value after receiving the message to achieve data transfer.

// hash/server/data.html corresponds to localhost:8081/data.html <script> switch(location.hash) {case "#data": callback(); break; } function callback() {const data = "data.html data "try {parent.location.hash = data; }catch(e) { // ie, Iframe var ifrProxy = document.createElement('iframe'); ifrproxy.style.display = 'none'; ifrproxy.src = 'http://localhost:8080/proxy.html#' + data; / / this file on the client domain name under the domain of the document. The body. The appendChild (ifrproxy); } } </script>Copy the code

Since the two pages are not in the same domain, IE and Chrome do not allow you to change the value of parent-location. hash, so use a proxy. HTML page that is a proxy iframe under localhost:8080

/ / hash/client/proxy. HTML corresponding localhost: 8080 / proxy. The HTML < script > parent. The parent. The location. The hash = self.location.hash.substring(1); </script>Copy the code

After enabling yarn Hash, you can view the following information in localhost:8080:

Of course, this approach has many disadvantages:

  1. The data is directly exposed in the URL
  2. Data capacity and type are limited and so on

window.name:

Window.name (usually found in js code) is not a normal global variable, but the name of the current window. Note that each iframe has its enclosing window, which is a child of top Window. The magic of the window.name attribute is that the name value persists from page to page (and even from domain to domain), and it can support very long name values (2MB).

Here’s a simple example:

You type on the console of a page:

window.name = "Hello World"
window.location = "http://www.baidu.com"Copy the code

The page jumps to baidu home page, but window.name is saved, still Hello World, the cross-domain solution seems to be ready:

Front-end logic:

/ / name/client/index. The HTML corresponding localhost: 8080 / index. The HTML < script > let data = ' '; const ifr = document.createElement('iframe'); ifr.src = "http://localhost:8081/data.html"; ifr.style.display = 'none'; document.body.appendChild(ifr); ifr.onload = function() { ifr.onload = function() { data = ifr.contentWindow.name; Console. log(' Received data :', data); } ifr.src = "http://localhost:8080/proxy.html"; } </script>Copy the code

Data page:

// name/server/data.html localhost:8081/data.html <script> window.name = "data.html data!" ; </script>Copy the code

Localhost :8081/data.html we can create a new iframe on this page. The SRC of the iframe points to the data side address (using the cross-domain capability of the IFrame tag), and the data side file is set to the value window.name.

If the SRC source of the index.html page is different from the SRC source of the page’s iframe, then you can’t manipulate anything in the iframe, so you can’t fetch the name of the iframe. So we need to change SRC to point to a homologous HTML file after data.html is loaded, or set it to ‘about:blank; ‘in this case, I just create a proxy.html empty page in the same directory as index. HTML. [SRC] window.name [SRC] window.name

Then run yarn name to see the effect:

6.postMessage

PostMessage is a new feature of HTML5, Cross Document Messaging, currently: Chrome 2.0+, Internet Explorer 8.0+, Firefox 3.0+, Opera 9.6+, and Safari 4.0+ all support this feature, and it’s incredibly easy to use.

Front-end logic:

/ / postMessage/client/index. The HTML corresponding localhost: 8080 / index. The HTML < iframe SRC = "http://localhost:8081/data.html" style='display: none; '></iframe> <script> window.onload = function() { let targetOrigin = 'http://localhost:8081'; Window. The frames [0]. PostMessage (' index.html data! ', targetOrigin); } window.addeventListener ('message', function(e) {console.log('index.html received message :', e.ata); }); </script>Copy the code

Create an iframe, one way to use the iframe postMessage sends a message to http://localhost:8081/data.html, then listen to the message, can obtain the documentation from the news.

Data side logic:

/ / postMessage/server/data. HTML corresponding localhost: 8081 / data. The HTML < script > window. The addEventListener (' message ', function(e) { if(e.source ! = window.parent) { return; } let data = e.data; Console. log('data.html received message :', data); The parent. PostMessage (' data. The HTML data! ', e.origin); }); </script>Copy the code

Start the service: yarn postMessage and open a browser to access:

For more details on postMessage, see the tutorial:

PostMessage_ Baidu Encyclopedia window.postMessage ()

7.document.domain

In the case of the same primary domain and different subdomains, you can set document.domain to solve the problem. Particular way is to add the document at http://www.example.com/index.html and http://sub.example.com/data.html respectively two files. The domain = “example.com” Then create an iframe through the index. HTML file to control the window of the iframe, so as to interact. Of course, this method can only solve the case that the primary domain is the same but the secondary domain is different. How do you test if you want to set the domain of script.example.com to QQ.com?

If you do not have nginx installed on your computer, please install nginx first

Front-end logic:

/ / domain/client/index. The corresponding sub1.example.com/index.html HTML < script > document. The domain = 'example.com'. let ifr = document.createElement('iframe'); ifr.src = 'http://sub2.example.com/data.html'; ifr.style.display = 'none'; document.body.append(ifr); ifr.onload = function() { let win = ifr.contentWindow; alert(win.data); } </script>Copy the code

Data side logic:

/ / domain/server/data corresponding sub2.example.com/data.html < script > document. The domain = 'example.com'. Window. data = 'data.html data! '; </script>Copy the code

Open the hosts file in the operating system: MAC is in the /etc/hosts file and add:

127.0.0.1 sub1.example.com
127.0.0.1 sub2.example.comCopy the code

After opening the nginx configuration file: / usr/local/etc/nginx/nginx. Conf, and add in the HTTP module, remember to input nginx start nginx service:

/usr/local/etc/nginx/nginx.conf http { // ... server { listen 80; server_name sub1.example.com; Location / {proxy_pass http://127.0.0.1:8080/; } } server { listen 80; server_name sub2.example.com; Location / {proxy_pass http://127.0.0.1:8081/; }} / /... }Copy the code

Sub1.example.com and sub2.example.com refer to local 127.0.0.1:80 and then use nginx as a reverse proxy to map to ports 8080 and 8081, respectively.

Accessing sub1(2).example.com thus equals accessing 127.0.0.1:8080(1)

Start the yarn Domain service and access the browser.

Conclusion:

The previous seven cross-domain way I already all finished, actually reason, commonly used is the former three ways, behind four more often a few little skill, though not necessarily used in work, but if you’re in the interview process can be mentioned these cross-domain skills, there is no doubt in the interviewer’s mind is a plus.

The above method may be a bit confusing, I hope you can follow me to type the code as you read, when you open the browser to see the result, you will be able to master this method.