preface

In the front-end cross domain series (1) – The same origin policy introduction we described the browser’s same origin policy, so if we need to carry out cross domain in the development, how to do, so this chapter on the commonly used cross domain methods are divided into client and server cross domain communication and cross page communication

1. Cross-domain communication between client and server

1.1 the json

1.1.1 the json principle

Jsonp uses

1.1.2 JSONP vs. AJAX

JSONP, like AJAX, is a way for a client to send a request to the server and get data from the server. But AJAX is a same-origin policy, JSONP is a non-same-origin policy (cross-domain request)

1.1.3 Advantages and disadvantages of JSONP

JSONP has the advantage of simple compatibility and can be used to solve the problem of cross-domain data access in mainstream browsers. The disadvantage is that only support for get methods is limited and insecure and may be subject to XSS attacks.

1.1.4 JSONP implementation process

  • Declare a callback function whose name (such as show) is passed as a parameter value to the server requesting data across domains, and whose parameter is to fetch the target data (the data returned by the server).
  • To create a<script>Tag, assign the cross-domain API data interface address to script SRC, and pass the function name to the server at that address. The callback = show).
  • Once the server receives the request, it needs to do something special: concatenate the name of the function passed in with the data it needs to give you into a string, for example: the name of the function passed in is show, and the data it prepares is show(‘hello’).
  • Finally, the server returns the data to the client through HTTP protocol, and the client parses the data to operate on the returned data.

During development, you may encounter multiple JSONP requests with the same callback function name, and you may need to wrap your own JSONP function.

// index.html function jsonp({ url, params, callback }) { return new Promise((resolve, reject) => { let script = document.createElement('script') window[callback] = function(data) { resolve(data) document.body.removeChild(script) } params = { ... params, callback } // wd=b&callback=show let arrs = [] for (let key in params) { arrs.push(`${key}=${params[key]}`) } script.src  = `${url}? ${arrs.join('&')}` document.body.appendChild(script) }) } jsonp({ url: 'http://localhost:3000/say', params: { wd: '123' }, callback: 'show' }).then(data => { console.log(data) })Copy the code

The above code to http://localhost:3000/say? Callback =show (‘hello’) wd=123&callback=show

// server.js let express = require('express') let app = express() app.get('/say', function(req, res) { let { wd, callback } = req.query console.log(wd) // 123 console.log(callback) // show res.end(`${callback}('hello')`) // }) app.listen(3000)Copy the code

1.1.5 jSONP form of jQuery

JSONP is all GET and asynchronous requests, there is no other way to request or synchronous requests, and jQuery clears the cache for JSONP requests by default.

$.ajax({ url:"http://crossdomain.com/jsonServerResponse", dataType:"jsonp", Type :"get",// omit jsonpCallback:"show",// omit jsonp:"callback",// omit jsonp:"callback" Function (data){console.log(data); }});Copy the code

1.1.6 Vue AxiOS implementation

this.$http = axios;
this.$http.jsonp('http://crossdomain.com/jsonServerResponse', {
    params: {},
    jsonp: 'callback'
}).then((res) => {
    console.log(res); 
})
Copy the code

1.2 CORS

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.

Browsers classify CORS cross-domain requests as simple and non-simple. It is a simple request if both conditions are met: (1) Use one of the following methods:

  • head
  • get
  • post

(2)HTTP headers do not exceed the following fields:

  • Accept
  • Accept-Language
  • Content-Language
  • Last-Event-ID
  • Content-type: Only three values: Application/X-www-form-urlencoded, multipart/form-data, text/plain

If the two conditions are not met at the same time, it is a non-simple request. Browsers treat these two differently

1.2.1 Simple Request

For simple requests, the browser issues CORS requests directly. Specifically, add an Origin field to the header information.

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

In the header above, the Origin field specifies the source (protocol + domain + port) from which the request came. Based on this value, the server decides whether to approve the request or not.

The response header fields set by CORS requests all start with access-Control – :

  1. Access – Control – Allow – Origin: will choose

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: 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: The getResponseHeader() method of the XMLHttpRequest object returns only six basic fields for CORS requests: Cache-control, Content-language, Content-Type, Expires, Last-Modified, Pragma. If you want to get other fields, you must specify access-Control-expose-headers. The above example specifies that getResponseHeader(‘FooBar’) can return the value of the FooBar field.

1.2.2 Non-simple Requests

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.

Preview the request

The request method for the “precheck” request is OPTIONS, indicating that the request is for questioning. In the request header information, the key field is Origin, indicating which source the request comes from. In addition to the Origin field, the precheck request header contains two special fields.

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
  1. Access – Control – Request – Method: choice

Used to list the HTTP methods used by the browser for CORS requests, in this case PUT. Access-control-request-headers: Optional This field is a comma-separated string that specifies the additional Header field sent by the browser for CORS requests. X-custom-header is used in the preceding 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. Access-control-allow-origin: Access-Control-Allow-Origin: Access-Control-Allow-Origin: Access-Control-Allow-Origin

  1. Access – Control – Allow – the Methods: will choose

Its value is a comma-separated string that indicates all the 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. 3. Access-control-allow-credentials: Optional This field has the same meaning as that in simple requests. 4. Access-control-max-age: Specifies the validity period of the precheck request, in seconds.

CORS cross-domain example

// let XHR = new XMLHttpRequest() document.cookie = 'name=xiamen' // Cookies cannot cross domain xhr.withcredentials = true // The front set whether or not to bring a cookie XHR. Open (' PUT ', 'http://localhost:4000/getData', true) XHR. SetRequestHeader (' name ', 'xiamen') xhr.onreadystatechange = function() { if (xhr.readyState === 4) { if ((xhr.status >= 200 && xhr.status < 300) | | XHR. Status = = = 304) {the console. The log (XHR. Response) / / get the response headers, Access-control-expose-headers console.log(xhr.getresponseheader ('name'))}}} xhr.send() //server1.js let express = require('express'); let app = express(); app.use(express.static(__dirname)); app.listen(3000); Server2.js let express = require('express') let app = express() let whitList = ['http://localhost:3000'] // Set whitelist app.use(function(req, res, Next) {let origin = req.headers. Origin if (whitlist.includes (origin)) {// Set which source can access me Res. setHeader(' access-Control-allow-headers ', Origin) Res.setheader (' access-control-allow-methods ', 'PUT') // Cookie res.setHeader(' access-Control-allow-credentials ', Res.setheader (' access-Control-max-age ', 6) res.setheader (' access-Control-expose-headers ', 6) res.setheader (' access-Control-expose ', 6) 'name') if (req.method === 'OPTIONS') {res.end()}} next()}) app.put('/getData', function(req, Res) {console.log(req.headers) res.setheader ('name', 'jw') Res.end ('hello put')}) app.get('/getData', function(req, res) { console.log(req.headers) res.end('hello get') }) app.use(express.static(__dirname)) app.listen(4000)Copy the code

The above code from http://localhost:3000/index.html to http://localhost:4000/ cross-domain request, as we mentioned above, the backend is the key to the realization of CORS communication.

1.3 Node Middleware Proxy (cross domain twice)

Implementation Principle: The same origin policy is a standard that the browser must comply with. However, the same origin policy is not required if the server requests the same origin policy to the server. For proxy servers, you need to do the following steps:

  • Accept client requests.
  • Forwards the request to the server.
  • Get the server response data.
  • The response is forwarded to the client.

Let’s start with an example: a local file named index.html, via a proxy serverhttp://localhost:3000To target serverhttp://localhost:4000Request data.

/ / index. HTML (http://127.0.0.1:5500) < script SRC = "https://cdn.bootcss.com/jquery/3.3.1/jquery.min.js" > < / script > < script >  $.ajax({ url: 'http://localhost:3000', type: 'post', data: { name: 'xiamen', password: '123456' }, contentType: 'application/json; charset=utf-8', success: function(result) { console.log(result) // {"title":"fontend","password":"123456"} }, error: Function (MSG) {console.log(MSG)}}) </script> // server1.js (http://localhost:3000) const HTTP = require(' HTTP ') // Step 1: Const server = http.createserver ((request, response) => {// Proxy server, WriteHead (200, {' access-control-allow-origin ': '*', 'access-control-allow-methods ': '*', 'access-Control-allow-headers ':' content-type '}) const proxyRequest = http. request({host: '127.0.0.1', port: 4000, URL: '/', method: request.method, headers: Request. Headers}, serverResponse => {// Var body = 'serverResponse.on('data', chunk => {body += chunk}) serverResponse.on('end', () => {console.log('The data is' + body) Forward the response to the browser response.end(body)})}).end()}) server.listen(3000, () => { console.log('The proxyServer is running at http://localhost:3000') }) // server2.js(http://localhost:4000) const  http = require('http') const data = { title: 'fontend', password: '123456' } const server = http.createServer((request, response) => { if (request.url === '/') { response.end(JSON.stringify(data)) } }) server.listen(4000, () => { console.log('The server is running at http://localhost:4000') })Copy the code

{“title”:”fontend”,”password”:”123456″}}

1.4 Nginx Reverse proxy

The implementation principle is similar to Node middleware proxy, requiring you to build a nginx server for forwarding requests.

Nginx reverse proxy is the simplest way to cross domains. Nginx only needs to change the configuration to solve the cross-domain problem, support all browsers, support sessions, no code changes, and do not affect server performance.

Nginx configure a proxy server (domain name and domain1 the same, different port) as a jumper, reverse proxy access to domain2 interface, and can incidentally modify the cookie in the domain information, convenient for the current domain cookie writing, cross-domain login.

Download nginx first, and then to nginx directory nginx. Conf modified as follows:

// Proxy server {listen 81; server_name www.domain1.com; location / { proxy_pass http://www.domain2.com:8080; # reverse proxy proxy_cookie_domain www.domain2.com www.domain1.com; # change cookie domain name index index.html index.htm; # When accessing Nignx with middleware proxy interface such as Webpack-dev-server, there is no browser participation, so there is no source restriction. Add_header access-Control-allow-origin http://www.domain1.com; * add_header access-control-allow-credentials true; * add_header access-control-allow-credentials true; }}Copy the code

Finally, start nginx on nginx -s reload

// index.html var xhr = new XMLHttpRequest(); // Whether the browser reads and writes cookies xhr.withCredentials = true; / / access proxy server in nginx XHR. Open (' get 'and' http://www.domain1.com:81/?user=admin ', true); xhr.send(); // server.js var http = require('http'); var server = http.createServer(); var qs = require('querystring'); server.on('request', function(req, res) { var params = qs.parse(req.url.substring(2)); // Write cookie res.writeHead(200, {' set-cookie ': 'l=a123456; Path=/; Domain=www.domain2.com; HttpOnly' // HttpOnly: script cannot read}); res.write(JSON.stringify(params)); res.end(); }); server.listen('8080'); console.log('Server is running at port 8080... ');Copy the code

Two, cross-page communication

2.1 postMessage

PostMessage is an API in HTML5 XMLHttpRequest Level 2, and is one of the few window properties that can operate across domains. It can be used to solve the following problems:

  • Data transfer between the page and the new window it opens
  • Messaging between multiple Windows
  • Page with nested IFrame message delivery
  • Cross-domain data transfer for the three scenarios above

The postMessage() method allows scripts from different sources to communicate asynchronously in a limited manner, enabling cross-text file, multi-window, cross-domain messaging.

	otherWindow.postMessage(message, targetOrigin, [transfer]);     
Copy the code
  • Message: Data to be sent to another window.
  • TargetOrigin: Specifies which Windows can receive message events via the origin property of the window. The value can be a string “*” (for unrestricted) or a URI. If any of the protocol, host address, or port of the target window does not match the value provided by targetOrigin, the message will not be sent. A message will only be sent if all three match.
  • Transfer (Optional) : A string of Transferable objects that are transferred at the same time as Message. Ownership of these objects is transferred to the receiver of the message, and ownership is no longer retained by the sender.

Let’s look at an example: http://localhost:3000/a.html page to http://localhost:4000/b.html “hello”, then the latter back to the “world”.

/ / a.h HTML < iframe SRC = "http://localhost:4000/b.html" frameborder = "0" id = "frame" onload = "load ()" > < iframe > / / finished loading it triggers an event / / embedded in http://localhost:3000/a.html < script > function load () {let frame = document. The getElementById () 'frame' frame.contentWindow.postMessage('hello', 'http://localhost:4000') // send data window.onmessage = function(e) {// accept return data console.log(e.ata) //world}} </script> // b.html window.onmessage = function(e) { console.log(e.data) //hello e.source.postMessage('world', e.origin) }Copy the code

2.2 the websocket

Websocket is a persistent protocol of HTML5, which realizes the full duplex communication between browser and server, and is also a cross-domain solution. WebSocket and HTTP are both application layer protocols based on TCP. However, WebSocket is a two-way communication protocol. After the connection is established, both the WebSocket server and client can actively send or receive data to each other. At the same time, the WebSocket needs to use HTTP protocol to establish a connection. After the connection is established, the two-way communication between the client and server is independent of HTTP.

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.

Let’s start with an example: the local file socket. HTML sends and receives data to localhost:3000

// socket.html <script> let socket = new WebSocket('ws://localhost:3000'); socket.onopen = function () { socket.send('hello'); } socket.onMessage = function (e) {console.log(e.data); } </script> // server.js let express = require('express'); let app = express(); let WebSocket = require('ws'); WSS = new websocket. Server({port:3000}); wss.on('connection',function(ws) { ws.on('message', function (data) { console.log(data); // hello ws.send('world') }); })Copy the code

2.3 the window. The name + iframe

The window.name attribute is unique in that the name value persists across different pages (and even different domain names) and supports very long name values (2MB). Where A.HTML and B.HTML are sympatric, both are http://localhost:3000; And c. HTML is http://localhost:4000

// a.html(http://localhost:3000/b.html) <iframe src="http://localhost:4000/c.html" frameborder="0" onload="load()" Id ="iframe"></iframe> <script> let first = true // onload Name function load() {if(first){// after the first onload(cross-domain page) succeeds, Let iframe = document.getelementById ('iframe'); let iframe = document.getelementById ('iframe'); iframe.src = 'http://localhost:3000/b.html'; first = false; } else {/ / second onload (sympatric b.h HTML pages) after the success, read the same domain window. The name of the data to the console. The log (iframe. ContentWindow. Name); } } </script>Copy the code

B.html is an intermediate proxy page, in the same domain as A.HTML, with empty content.

 // c.html(http://localhost:4000/c.html)
  <script>
    window.name = 'hello'  
  </script>
Copy the code

Summary: The SRC attribute of iframe is used to pass the data from the outfield to the local region. The cross-domain data is passed from the outfield to the local region by the window.name of iframe. This is a neat way to circumvent the browser’s cross-domain access restrictions, but it’s also a secure operation.

2.4 the location. The hash + iframe

Implementation principle: A wants to communicate with B across domains, which is achieved through the middle page C. Three pages, different fields use iframe location.hash to transfer values, the same fields directly js access to communicate. A domain: A.html -> B domain: B.html -> A domain: C.HTML, A and B different domain can only hash value one-way communication, B and C are also different domain can only one-way communication, but C and A are the same domain, so C can access all objects on A page through parent. Parent.

// a.html(www.domain1.com/a.html) <iframe id="iframe" src="http://www.domain2.com/b.html" style="display:none;" ></iframe> <script> var iframe = document.getElementById('iframe'); SetTimeout (function() {iframe.src = iframe.src + '#user=admin'; }, 1000); Function onCallback(res) {alert('data from c.html --> '+ res); } </script> // b.html(www.domain2.com/b.html) <iframe id="iframe" src="http://www.domain1.com/c.html" style="display:none;" ></iframe> <script> var iframe = document.getElementById('iframe'); Onhashchange = function () {ifame. SRC = ifame. SRC + location.hash; }; </script> // c.html(www.domain1.com/c.html) <script> // listen for hash values window.onhashchange = function () {// And by operating with domain a.h HTML js callback, the results back to the window. The parent. The parent. OnCallback (' hello, '+ location. Hash. Replace (' # user =', ')); }; </script>Copy the code

2.5 the document domain + iframe

This mode applies only to cross-domain scenarios where the primary domains are the same and the subdomains are different.

Implementation principle: two pages through JS forced document.domain as the base of the primary domain, to achieve the same domain. For example, a.test.com and b.test.com are applicable. Just add document.domain =’test.com’ to the page to indicate that the secondary domain is the same.

Let’s look at an example: page a.zf1.cn:3000/a.html gets the value of a in page b.zf1.cn:3000/b.html

// a.html
<body>
 helloa
  <iframe src="http://b.zf1.cn:3000/b.html" frameborder="0" onload="load()" id="frame"></iframe>
  <script>
    document.domain = 'zf1.cn'
    function load() {
      console.log(frame.contentWindow.a);
    }
  </script>
</body>


// b.html
<body>
   hellob
   <script>
     document.domain = 'zf1.cn'
     var a = 100;
   </script>
</body>
Copy the code

Third, summary

These are nine common cross-domain solutions:

  • Jsonp (only support GET request, support the old IE browser) is suitable for loading different domain names JS, CSS, IMG and other static resources, JSONP’s advantage is to support the old browser, and can not support CORS website request data;
  • CORS (supports all types of HTTP requests, but not below Internet Explorer 10) is suitable for various cross-domain ajax requests, is the fundamental solution of cross-domain HTTP requests;
  • The principle of Nginx proxy cross-domain and NodeJS middleware cross-domain is similar. They both build a server and request HTTP interface directly on the server side, which is suitable for the front-end project to adjust the back-end interface with the separation of the front and back ends.
  • Document.domain +iframe is suitable for cross-domain requests with the same primary domain name and different subdomain names.
  • PostMessage and WebSocket are new HTML5 features, not very compatible, only applicable to mainstream browsers and IE10+.
  • In daily work, the cross-domain solutions used more are CORS and NGINX reverse proxies

Four, reference

Nine Common front-end cross-domain solutions (in detail)