In front-end development, cross-domain is one of the most common questions we encounter, and it is also one of the most common questions we are asked in interviews, so here is a summary. It’s too small a problem to worry about

You-should-know-js

What is cross-domain

Cross-domain means that the browser cannot execute scripts from other sites. It is caused by the same origin policy of the browser and is a security restriction imposed by the browser on JavaScript.

The same origin policy restricts the following behaviors:

  • Cookie, LocalStorage, and IndexDB cannot be read
  • DOM and JS objects cannot be retrieved
  • The Ajax request could not be sent

Common cross-domain scenarios

The same domain name, protocol, and port are the same.

http://www.nealyang.cn/server.php http://www.nealyang.cn/index.html call the cross-domain calls at http://www.nealyang.cn/index.html http://www.neal.cn/server.php cross-domain, primary domain http://abc.nealyang.cn/index.html call http://def.neal.cn/server.php across different domains, subdomains Different https://www.nealyang.cn/index.html http://www.nealyang.cn/server.php http://www.nealyang.cn:8080/index.html call cross-domain, port Call http://www.nealyang.cn/server.php cross-domain, agreement 127.0.0.1 localhost calls across different domainsCopy the code

Cross-domain solutions

The json cross-domain

Jsonp cross-domain is actually a proxy pattern in the JavaScript design pattern. Loading static resource files from different domain names in HTML pages with corresponding tags is allowed by browsers, so we can use this “criminal vulnerability” to cross domains. Generally, we can dynamically create a script tag and then request a reference url to achieve cross-domain communication

// Let script = document.createElement('script'); script.src = 'http://www.nealyang.cn/login?username=Nealyang&callback=callback'; document.body.appendChild(script); function callback(res) { console.log(res); }Copy the code

Of course, jquery also supports jSONP implementations

$.ajax({
    url:'http://www.nealyang.cn/login'.type:'GET'.dataType:'jsonp'.// The request is jSONP
    jsonpCallback:'callback'.data: {"username":"Nealyang"}})Copy the code

While this approach is great, one of the biggest drawbacks is that you can only implement GET requests

Document.domain + iframe cross domain

The most important thing about this cross-domain approach is that the main domain name is the same. What is the main domain name same? www.nealyang.cn aaa.nealyang.cn ba.ad.nealyang.cn these three primary domain names are all nealyang.cn. However, this method cannot be used if the primary domain names are different.

Assume that at present, a.nealyang.cn and b.nealyang.cn correspond to servers with different IP addresses.

There is a test. HTML file under a.nealyang.cn


      
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>html</title>
    <script type="text/javascript" src = "Jquery - 1.12.1. Js"></script>
</head>
<body>
    <div>A page</div>
    <iframe 
    style = "display : none" 
    name = "iframe1" 
    id = "iframe" 
    src="http://b.nealyang.cn/1.html" frameborder="0"></iframe>
    <script type="text/javascript">
        $(function(){
            try{
                document.domain = "nealyang.cn"
            }catch(e){}
            $("#iframe").load(function(){
                var jq = document.getElementById('iframe').contentWindow.$
                jq.get("http://nealyang.cn/test.json".function(data){
                    console.log(data); }); })})</script>
</body>
</html>
Copy the code

Use iframe to load files in other domains (nealyang.cn/1.html), and set document.domain to nealyang.cn. After iframe is loaded, you can obtain global objects in nealyang.cn. Try to request test.json from nealyang.cn and you will find that the data request failed ~~ surprise, surprise, !!!!!!!

Data request failed, the purpose is not achieved, of course, there is still one step less:


      
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>html</title>
    <script type="text/javascript" src = "Jquery - 1.12.1. Js"></script>
    <script type="text/javascript">
        $(function(){
            try{
                document.domain = "nealyang.com"
            }catch(e){}
        })
    </script>
</head>
<body>
    <div id = "div1">B page</div>
</body>
</html>
Copy the code

At this point in the browser refresh, you will find that the data is really successful ~~~~~

Window. name + iframe cross domain

The window.name property sets or returns a string that holds the window name. His magic is that the name value remains unchanged when loaded on different pages or domains, and can store very long names (2MB).

If the index page requests data from a remote server, we create an iframe tag under the page. The SRC of the iframe tag points to the address of the server file (the SRC of the iframe tag can be cross-domain), and set the value window.name in the server file. Then read the value of window. Name in iframe in index. HTML. Perfect ~

<body>
  <script type="text/javascript"> 
    iframe = document.createElement('iframe'),
    iframe.src = 'http://localhost:8080/data.php';
    document.body.appendChild(iframe);
    iframe.onload = function() {
      console.log(iframe.contentWindow.name)
    };
  </script>
</body>
Copy the code

Of course, that’s not enough.

If the SRC source of the index.html page is different from the SRC source of the iframe frame in the page, then the name of the iframe frame cannot be retrieved. If the SRC source of the iframe frame in the page is different, the name of the iframe frame cannot be retrieved. SRC = ‘window.name’; SRC = ‘window.name’; SRC = ‘window.name’; SRC = ‘window.name’; SRC = ‘window.name’; SRC = ‘window.name’;

<body>
  <script type="text/javascript"> 
    iframe = document.createElement('iframe'),
    iframe.src = 'http://localhost:8080/data.php';
    document.body.appendChild(iframe);
    iframe.onload = function() {
      iframe.src = 'http://localhost:81/cross-domain/proxy.html';
      console.log(iframe.contentWindow.name)
    };
  </script>
</body>
Copy the code

The ideal would be to quickly reset ifame.src to the same source as index.html during iframe loading, so that the index page could fetch its name value! In reality, iframe is constantly refreshed, which is easy to understand. Resetting SRC is equivalent to reloading the page and triggering the onload event, so it keeps refreshed (but the required data can still be output). The modified code is as follows:

<body>
  <script type="text/javascript"> 
    iframe = document.createElement('iframe');
    iframe.style.display = 'none';
    var state = 0;
    
    iframe.onload = function() {
      if(state === 1) {
          var data = JSON.parse(iframe.contentWindow.name);
          console.log(data);
          iframe.contentWindow.document.write(' ');
          iframe.contentWindow.close();
        document.body.removeChild(iframe);
      } else if(state === 0) {
          state = 1;
          iframe.contentWindow.location = 'http://localhost:81/cross-domain/proxy.html'; }}; iframe.src ='http://localhost:8080/data.php';
    document.body.appendChild(iframe);
  </script>
</body>
Copy the code

So above, we have the data returned by the server, but there are several conditions that are essential:

  • Cross-domain capability of iframe tags
  • The ability for the window.names attribute value to persist after the document is refreshed

Location. hash + iframe cross-domain

This cross-domain approach is similar to the one described above, in that it dynamically inserts an iframe and sets its SRC to the server address, while the server also outputs one side of the JS code, and completes the data transmission by communicating with the child window.

I believe you already know about the anchor point, in fact, set the anchor point, so that the document specified the corresponding location. The anchor point is set with the A tag, and then the href points to the ID that you want to jump to, if you have a scroll bar, it’s not very easy to scroll.

And location.hash is essentially the url anchor. For example, if the url http://www.nealyang.cn#Nealyang is opened, typing location.hash in the console will return the #Nealyang field.

After the basic knowledge is added, let’s talk about how to achieve cross-domain

If the index page wants to fetch data from a remote server, insert an iframe dynamically, and execute the SRC of the iframe to the address of the server. In this case, the top window and the child window surrounding the iframe cannot communicate. Because of the same origin policy, change the path of the child window. Load the data on the path as the hash value of the changed path, and then you can communicate. Add the data to the hash of the address of the index page, which listens for changes to the hash, h5’s hashchange method

<body>
  <script type="text/javascript">
    function getData(url, fn) {
      var iframe = document.createElement('iframe');
      iframe.style.display = 'none';
      iframe.src = url;

      iframe.onload = function() {
        fn(iframe.contentWindow.location.hash.substring(1));
        window.location.hash = ' ';
        document.body.removeChild(iframe);
      };

      document.body.appendChild(iframe);
    }

    // get data from server
    var url = 'http://localhost:8080/data.php';
    getData(url, function(data) {
      var jsondata = JSON.parse(data);
      console.log(jsondata.name + ' ' + jsondata.age);
    });
  </script>
</body>
Copy the code

In addition, location.hash and window.name are the same methods that use global object properties, and they are the same as Jsonp in that they only implement get requests

PostMessage cross-domain

This is a cool API from H5. IE8+, Chrome, and FF all support this functionality. This functionality is also very simple, including the Message time to receive the Message, and the postMessage method to send the Message.

The postMessage method for sending a message is to send a message to an outside window

otherWindow.postMessage(message,targetOrigin);
Copy the code

OtherWindow is the target window, that is, the window to send a message to, either a member of the window.frames property or the window created by the window.open method. Message is the Message to be sent, the type is String, Object(IE8 and IE9 do not support Obj), targetOrigin is to limit the scope of the Message, the asterisk * if not limited

The message event that receives the message

var onmessage = function(event) {
  var data = event.data;
  var origin = event.origin;
}

if(typeof window.addEventListener ! ='undefined') {window.addEventListener('message',onmessage,false);
}else if(typeof window.attachEvent ! ='undefined') {window.attachEvent('onmessage', onmessage);
}
Copy the code

Take a chestnut

a.html(www.nealyang.cn/a.html)

<iframe id="iframe" src="http://www.neal.cn/b.html" style="display:none;"></iframe>
<script>       
    var iframe = document.getElementById('iframe');
    iframe.onload = function() {
        var data = {
            name: 'aym'
        };
        // Send cross-domain data to Neal
        iframe.contentWindow.postMessage(JSON.stringify(data), 'http://www.neal.cn');
    };

    // Accept data from domain2
    window.addEventListener('message'.function(e) {
        alert('data from neal ---> ' + e.data);
    }, false);
</script>
Copy the code

b.html(www.neal.cn/b.html)

<script>
    // Receive data from domain1
    window.addEventListener('message'.function(e) {
        alert('data from nealyang ---> ' + e.data);

        var data = JSON.parse(e.data);
        if (data) {
            data.number = 16;

            // Then send it back to NealYang
            window.parent.postMessage(JSON.stringify(data), 'http://www.nealyang.cn'); }},false);
</script>
Copy the code

Cross-domain resource sharing CORS

Because it is the mainstream cross-domain solution. So here’s a little bit more.

Introduction to the

CORS is a W3C standard, which stands for “Cross-origin Resource Sharing”. It allows browsers to issue XMLHttpRequest requests across source servers, overcoming the limitation that AJAX can only be used in the same source.

CORS requires both browser and server support. Currently, all browsers support this function, and Internet Explorer cannot be lower than Internet Explorer 10. IE8+ : IE8/9 needs to use the XDomainRequest object to support CORS.

The entire CORS communication process is completed automatically by the browser without user participation. For developers, CORS communication is no different from same-origin AJAX communication, and the code is exactly the same. 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, cross-source communication is possible.

Two kinds of requests

It’s funny to say that there are two types of requests, simple requests and non-simple requests. Simple request as long as the following conditions are met

  • The request mode can be HEAD, POST, or GET
  • The HTTP header does not exceed the following fields: Accept, Accept-language, Content-language, last-event-ID, and Content-Type(limited to three values: Application/X-www-form-urlencoded, multipart/form-data, text/plain)

The reason why simple and non-simple requests are classified is because browsers treat these two types of requests differently.

A simple request

The basic flow

For simple requests, the browser issues CORS requests directly. Specifically, add an Origin field to the header information. As an example, the browser automatically adds an Origin field to the header when it realizes that the cross-source AJAX request is a simple one.

GET /cors HTTP/1.1 Origin: http://api.bob.com Host: api.alice.com Accept-language: en-us Connection: Keep alive - the user-agent: Mozilla / 5.0...Copy the code

The Origin field indicates the source (protocol + domain name + port) from which the request is sent. Based on this value, the server decides whether to approve the request or not.

If Origin does not specify a licensed source, the server will return a normal HTTP response. The browser realizes that the response header does not contain the Access-Control-Allow-Origin field (more on that below), and it throws an error that is caught by XMLHttpRequest’s onError callback.

Note that this error cannot be identified by the status code, because the status code for the HTTP response might be 200.

If Origin specifies a domain name within the license, the server returns a response with several additional header fields.

   Access-Control-Allow-Origin: http://api.bob.com
   Access-Control-Allow-Credentials: true
   Access-Control-Expose-Headers: FooBar
   Content-Type: text/html; charset=utf-8
Copy the code

In the header above, there are three fields related to CORS requests, all beginning with Access-Control-

  • Access-control-allow-origin: This field is required. Its value is either the value of the Origin field at the time of the request, or an *, indicating acceptance of requests for any domain name
  • Access-control-allow-credentials: This field is optional. Its value is a Boolean value indicating whether cookies are allowed to be sent. By default, cookies are not included in CORS requests. If set to true, the server explicitly approves that cookies can be included in the request and sent to the server. This value can only be set to true if the server does not want the browser to send cookies.
  • Access-control-expose-headers: This field is optional. In CORS requests, the getResponseHeader() method of the XMLHttpRequest object takes only six basic fields: Cache-control, Content-language, Content-Type, Expires, Last-Modified, Pragma. If you want to get other fields, you must specify access-Control-expose-headers.
WithCredentials attribute

As mentioned above, CORS requests do not send cookies and HTTP authentication information by default. To send cookies to the server, specify the access-Control-allow-credentials field with the server’s permission.

On the other hand, the developer must turn on the withCredentials attribute in the AJAX request.

var xhr = new XMLHttpRequest(); // Ie8/9 must be window.xdomainRequest compatible

// Set whether cookies are included in the front end
xhr.withCredentials = true;

xhr.open('post'.'http://www.domain2.com:8080/login'.true);
xhr.setRequestHeader('Content-Type'.'application/x-www-form-urlencoded');
xhr.send('user=admin');

xhr.onreadystatechange = function() {
    if (xhr.readyState == 4 && xhr.status == 200) { alert(xhr.responseText); }};// jquery
$.ajax({
    ...
   xhrFields: {
       withCredentials: true    // Set whether cookies are included in the front end
   },
   crossDomain: true.// The request header contains additional cross-domain information, but does not contain cookies. });Copy the code

Otherwise, the browser won’t send a Cookie, even if the server agrees to do so. Or, if the server asks for a Cookie, the browser won’t handle it. However, if the withCredentials setting is omitted, some browsers still send cookies together. In this case, you can explicitly disable the withCredentials.

Note that access-Control-allow-Origin cannot be set to an asterisk if cookies are to be sent, and must specify an explicit domain name consistent with the requested web page. At the same time, cookies still follow the same origin policy, only the Cookie set with the server domain name will be uploaded, cookies of other domain names will not be uploaded, and (cross-source) document. Cookie in the original web page code can not read cookies under the server domain name.

Non-simple request

Non-simple requests are requests that have special requirements on the server, such as the request method being PUT or DELETE, or the content-Type field being of Type Application/JSON.

CORS requests that are not simple requests are preceded by an HTTP query request, called a “preflight” request.

The browser asks the server if the domain name of the current web page is on the server’s license list, and what HTTP verb and header fields can be used. The browser issues a formal XMLHttpRequest request only if it receives a positive response; otherwise, an error is reported.

var url = 'http://api.alice.com/cors';
var xhr = new XMLHttpRequest();
xhr.open('PUT', url, true);
xhr.setRequestHeader('X-Custom-Header'.'value');
xhr.send();
Copy the code

The browser realizes that this is not a simple request and automatically issues a “pre-check” request, asking the server to confirm that it is ok to do so. Here is the HTTP header for this “precheck” request.

    OPTIONS /cors HTTP/1.1
   Origin: http://api.bob.com
   Access-Control-Request-Method: PUT
   Access-Control-Request-Headers: X-Custom-Header
   Host: api.alice.com
   Accept-Language: en-US
   Connection: keep-alive
   User-Agent: Mozilla/5.0...
Copy the code

The request method for the “precheck” request is OPTIONS, indicating that the request is being queried. In the header information, the key field is Origin, indicating which source the request came from.

In addition to the Origin field, the precheck request header contains two special fields.

  • Access-control-request-method: This field is required to list HTTP methods used by the browser for CORS requests. In this example, PUT.
  • Access-control-request-headers: This field is a comma-separated string that specifies the additional Header field to be sent by a browser CORS Request. X-custom-header is used in this example
Response to precheck request

After receiving the precheck Request, the server checks the Origin, access-Control-request-method, and access-Control-request-headers fields and confirms that cross-source requests are allowed, it can respond

HTTP/1.1 200 OK
Date: Mon, 01 Dec 2008 01:15:39 GMT
Server: Apache/2.0.61 (Unix)
Access-Control-Allow-Origin: http://api.bob.com
Access-Control-Allow-Methods: GET, POST, PUT
Access-Control-Allow-Headers: X-Custom-Header
Content-Type: text/html; charset=utf-8
Content-Encoding: gzip
Content-Length: 0
Keep-Alive: timeout=2, max=100
Connection: Keep-Alive
Content-Type: text/plain
Copy the code

In the HTTP response above, the key is the Access-Control-Allow-Origin field, which indicates that http://api.bob.com can request data. This field can also be set to an asterisk to indicate approval of any cross-source request.

If the browser denies the “precheck” request, it will return a normal HTTP response, but without any CORS related header fields. At this point, the browser decides that the server did not approve the precheck request, and therefore fires an error that is caught by the ONError callback function of the XMLHttpRequest object. The console will print the following error message.

Other CORS related fields that the server responds to are as follows:

Access-Control-Allow-Methods: GET, POST, PUT
Access-Control-Allow-Headers: X-Custom-Header
Access-Control-Allow-Credentials: true
Access-Control-Max-Age: 1728000
Copy the code
  • Access-control-allow-methods: This field is required, and its value is a comma-separated string indicating all Methods supported by the server for cross-domain requests. Notice that all supported methods are returned, not just the one requested by the browser. This is to avoid multiple “pre-check” requests.
  • Access-control-allow-headers: The access-Control-allow-headers field is required if the browser Request includes the access-Control-request-headers field. It is also a comma-separated string indicating all header information fields supported by the server, not limited to those requested by the browser in precheck.
  • Access-control-allow-credentials: This field has the same meaning as that in simple requests.
  • Access-control-max-age: Specifies the validity period of the precheck request, in seconds. This field is optional. In the result above, the validity period is 20 days (1728000 seconds), which allows the response to be cached for 1728000 seconds (20 days), during which time another precheck request is not issued.
The browser responded properly

Once the server passes the “precheck” request, every subsequent normal BROWSER CORS request will have the same Origin header field as a simple request. The server also responds with an Access-Control-Allow-Origin header field.

PUT /cors HTTP/1.1
Origin: http://api.bob.com
Host: api.alice.com
X-Custom-Header: value
Accept-Language: en-US
Connection: keep-alive
User-Agent: Mozilla/5.0...
Copy the code

Normal CORS requests for the browser. The Origin field in the header above is automatically added by the browser. The following is a normal response from the server.

Access-Control-Allow-Origin: http://api.bob.com
Content-Type: text/html; charset=utf-8
Copy the code

The access-Control-Allow-Origin field is mandatory for each response

conclusion

CORS serves the same purpose as JSONP, but is more powerful than JSONP. JSONP supports only GET requests, and CORS supports all types of HTTP requests. JSONP has the advantage of supporting older browsers and being able to request data from sites that do not support CORS.

The WebSocket protocol is cross-domain

WebSocket Protocol is a new protocol for HTML5. It implements full duplex communication between browser and server, and allows cross-domain communication. It is a good implementation of server push technology.

The native WebSocket API is not very convenient to use. We use socket. IO, which encapsulates the WebSocket interface well, provides a simpler, flexible interface, and provides backward compatibility for browsers that do not support WebSocket.

Front-end code:

<div>User input:<input type="text"></div>
<script src="./socket.io.js"></script>
<script>
var socket = io('http://www.domain2.com:8080');

// The connection was successfully processed
socket.on('connect'.function() {
    // Listen for server messages
    socket.on('message'.function(msg) {
        console.log('data from server: ---> ' + msg); 
    });

    // The listener server is closed
    socket.on('disconnect'.function() { 
        console.log('Server socket has closed.'); 
    });
});

document.getElementsByTagName('input') [0].onblur = function() {
    socket.send(this.value);
};
</script>
Copy the code

node Server

var http = require('http'); var socket = require('socket.io'); Var server = http.createserver (function(req, res) {res.writehead (200, {' content-type ': 'text/ HTML '}); var server = http.createserver (function(req, res) {res.writehead (200, {' content-type ': 'text/ HTML '}); res.end(); }); server.listen('8080'); console.log('Server is running at port 8080... '); Socket.listen (server). On ('connection', function(client) { Function (MSG) {client.send('hello: '+ MSG); console.log('data from client: ---> ' + msg); }); On ('disconnect', function() {console.log(' client socket has closed.'); }); });Copy the code

Node agents cross domains

Node middleware realizes cross-domain proxy by starting a proxy server to realize data forwarding. It can also modify the domain name in the cookie in the response header by setting the cookieDomainRewrite parameter to realize cookie writing in the current domain and facilitate interface login authentication.

Build a proxy server with Node + Express + HTTP-proxy-middleware

The front-end code

var xhr = new XMLHttpRequest();

// Front-end switch: whether the browser reads and writes cookies
xhr.withCredentials = true;

// Access the HTTP-proxy-Middleware proxy server
xhr.open('get'.'http://www.domain1.com:3000/login?user=admin'.true);
xhr.send();
Copy the code

The back-end code

var express = require('express');
var proxy = require('http-proxy-middleware');
var app = express();

app.use('/', proxy({
    // Proxy cross-domain target interface
    target: 'http://www.domain2.com:8080'.changeOrigin: true.// Modify the response header information to cross-domain and allow cookies
    onProxyRes: function(proxyRes, req, res) {
        res.header('Access-Control-Allow-Origin'.'http://www.domain1.com');
        res.header('Access-Control-Allow-Credentials'.'true');
    },

    // Change the cookie domain name in the response information
    cookieDomainRewrite: 'www.domain1.com'  // The value can be false, indicating no change
}));

app.listen(3000);
console.log('Proxy server is listen at port 3000... ');
Copy the code

Nginx agents cross domains

NGINX in fact, personal did not how to play, so for the time being also can not mistake people’s children, forgive the author only sparse is still shallow ~ have the opportunity to study and come back to supplement ~~

communication

Welcome to the React Technology Stack, front-end technology chat QQ group

React Technology Stack :398240621

Pay attention to the public number: [full stack front selection] daily access to good articles recommended. You can also join groups to learn and communicate with them

Reference documentation

www.ruanyifeng.com/blog/2016/0… Segmentfault.com/a/119000001…