preface

Front-end cross-domain of various articles in fact has been a lot, but most of the introduction is not quite in line with my appetite cross-domain. It seems that if you want to impress yourself, you have to knock yourself out and summarize a blog post to record it.

Cross-domain constraints

Cross-domain is to prevent the user from reading content under another domain name. Ajax can get the response, and the browser thinks it’s not secure, so it blocks the response.

Cross-domain solutions

Unless otherwise specified, the HTML files marked below run under the http://127.0.0.1:5500 service by default

CORS

CORS refers to cross-domain resource sharing. It allows browsers to make Ajax requests to non-cognate servers, overcoming the limitation that Ajax can only be used cognate. Cross-domain in this way is mainly set up at the back end.

The key to this method is to set the back end, that is, to enable access-Control-allow-Origin or the corresponding Origin on the back end, cross-domain can be realized.

Browsers classify CORS requests into two categories: simple and non-simple.

As long as the following two conditions are met, it is a simple request.

  1. The request method is one of three:
  • HEAD
  • GET
  • POST
  1. HTTP headers do not exceed the following fields:
  • Accept
  • Accept-Language
  • Content-Language
  • Last-Event-ID
  • Content-type: Application/X-www-form-urlencoded, multipart/form-data, text/plain

Any request that does not meet both conditions is a non-simple request.

A simple request

cors.html

let xhr = new XMLHttpRequest() xhr.open('GET'.'http://localhost:8002/request')
xhr.send(null)
Copy the code

server.js

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

app.use((req, res, next) = > {
  res.header('Access-Control-Allow-Origin'.'http://127.0.0.1:5500') // Sets which domain access is allowed
  next()
})

app.get('/request'.(req, res) = > {
  res.end('server ok')
})

app.listen(8002)
Copy the code

Non-simple request

The above is a simple request. If we use a non-simple request, such as a PUT request method, we can also set it to cross domains.

For CORS requests that are not simple requests, an HTTP query request is added before formal communication, called a “precheck” request.

After receiving the pre-check Request, the server checks the Origin, access-Control-request-method, and access-Control-request-headers fields to confirm that cross-source requests are allowed. The browser issues a formal XMLHttpRequest request, otherwise an error is reported.

let xhr = new XMLHttpRequest()
xhr.open('PUT'.'http://localhost:8002/request')
xhr.send(null)
Copy the code

server.js

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

let whileList = ['http://127.0.0.1:5500'] // Set the whitelist
app.use((req, res, next) = > {
  let origin = req.headers.origin
  console.log(whitList.includes(origin))
  if (whitList.includes(origin)) {
    res.setHeader('Access-Control-Allow-Origin', origin) // Sets which domain access is allowed
    res.setHeader('Access-Control-Allow-Methods'.'PUT') // Sets which request method access is allowed
  }
  next()
})

app.put('/request'.(req, res) = > {
  res.end('server ok')
})

app.listen(8002)
Copy the code

The entire process sent two requests and succeeded across domains.

Of course, you can also set other parameters:

  • Access-Control-Request-Headers

This field is a comma-delimited string that specifies the additional header fields that the browser will send for CORS requests

  • Access-Control-Allow-Credentials

Indicates whether cookies are allowed to be sent. By default, cookies are not included in CORS requests.

  • Access-Control-Expose-Headers

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.

  • Access-Control-Max-Age

Specifies the validity period of this precheck request, in seconds. The validity period is 20 days (1728,000 seconds), which allows the response to be cached for 1728,000 seconds (20 days), during which time another precheck request is not issued.

Node middleware Proxy

How it works: The same origin policy is the standard that browsers need to follow, and there is no cross-domain when a server requests a server.

For proxy servers, you need to do the following steps:

  1. Accept client requests.
  2. Forwards the request to the server.
  3. Get the server response data.
  4. The response is forwarded to the client.

This time we used express middleware HTTP-proxy-Middleware to broker requests and responses across domains

In this case, all three files are in the same level directory:

index.html

let xhr = new XMLHttpRequest()
xhr.open('GET'.'/api/request')
xhr.onreadystatechange = () = > {
  if (xhr.readyState === 4 && xhr.status === 200) {
    console.log('Request successful, result:', xhr.responseText) // request success
  }
}
xhr.send(null)
Copy the code

nodeMdServer.js

const express = require('express')
const { createProxyMiddleware } = require('http-proxy-middleware')

const app = express()

// Set the static resource
app.use(express.static(__dirname))

// Use the proxy
app.use(
  '/api',
  createProxyMiddleware({
    target: 'http://localhost:8002'.pathRewrite: {
      '^/api': ' '.// Rewrite the path
    },
    changeOrigin: true,
  })
)

app.listen(8001)
Copy the code

nodeServer.js

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

app.get('/request'.(req, res) = > {
  res.end('request success')
})

app.listen(8002)
Copy the code

Run http://localhost:8001/index.html, cross-domain success

The VUE/React project uses Node proxy to configure webpack-dev-server.

Nginx reverse proxy

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

In this way, you only need to modify the configuration of Nginx to solve the cross-domain problem. In addition to changing the front end interface to the corresponding form, there is no need to modify the front and back end to make other changes.

Nginx to configure a proxy server (different ports in the same domain) as a jumper, reverse proxy to cross-domain domain name, so that you can modify the cookie domain information, convenient cookie writing in the current domain, cross-domain login.

Nginx. conf in the nginx directory:

// proxy server {listen 80; 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

Start the Nginx

index.html

var xhr = new XMLHttpRequest()
// Front-end switch: whether the browser reads and writes cookies
xhr.withCredentials = true
// Access the proxy server in nginx
xhr.open('get'.'http://www.domain1.com:81/?user=admin'.true)
xhr.send()
Copy the code

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 cookies to the front desk
  res.writeHead(200, {
    'Set-Cookie': 'l=123456; Path=/; Domain=www.domain2.com; HttpOnly'.// HttpOnly: the script cannot read
  })
  res.write(JSON.stringify(params))
  res.end()
})
server.listen(8080)
Copy the code

jsonp

Principle: Using the cross-domain feature of the Script tag, a callback function (global function) is defined on the client side, the server is requested to return the call of the callback function, and the server data is passed in the form of the callback function parameters, and then the function is executed. This method requires the cooperation of the server.

Implementation steps:

  1. Declare a global callback function as data returned by the server.
  2. Create a script tag that concatenates the address of the entire request API. Callback =getInfo), assigned to the SRC property of script
  3. The server receives the request and processes the data, then concatenates the function name and the data to be returned into a string. The assembly is in the form of the execution of the function. Server (getInfo (‘ data ‘))
  4. The browser receives the result from the server and invokes the declared callback function.

jsonp.html

function getInfo(data) {
  console.log(data) // Just to let you know, jSONP cross-domain success
}

let script = document.createElement('script')
script.src = 'http://localhost:3000? callback=getInfo' //
document.body.appendChild(script)
Copy the code

server.js

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

app.get('/'.(req, res) = > {
  let { callback } = req.query
  res.end(`${callback}(' Just so you know, JSONP cross-domain success ') ')
})

app.listen(3000)
Copy the code

JQuery’s $.ajax() method incorporates the JSONP implementation, which I won’t write here.

In development, you may encounter multiple JSONP requests with the same callback function name, and it is also cumbersome to use this way, so we wrap a JSONP function

function jsonp({ url, params, callback }) {
  return new Promise((resolve, reject) = > {
    let script = document.createElement('script')
    // Define the global callback function
    window[callback] = function (data) {
      resolve(data)
      document.body.removeChild(script) // Delete immediately after call} params = { callback, ... params }// {callback: "getInfo", name: "jacky"}
    let paramsArr = []
    for (const key in params) {
      paramsArr.push(`${key}=${params[key]}`)
    }
    script.src = `${url}?${paramsArr.join('&')}` // http://localhost:3000/? callback=getInfo&name=jacky
    document.body.appendChild(script)
  })
}

jsonp({
  url: 'http://localhost:3000'.params: {
    name: 'jacky',},callback: 'getInfo',
}).then(res= > {
  console.log(res) // Just to let you know, jSONP cross-domain success
})
Copy the code

The server can retrieve the parameters when it is deconstructed

app.get('/'.(req, res) = > {
  let { callback, name } = req.query
  res.end(`${callback}(' Just so you know, JSONP cross-domain success ') ')})Copy the code

Advantages: Good compatibility

Disadvantages: Due to script limitations, this cross-domain approach only supports GET requests and is not secure enough to be exposed to XSS attacks

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:

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

In summary, it 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]);

  • OtherWindow: A reference to another window, such as the contentWindow property of iframe, the window object returned by executing window.open, or the window.frames named or numeric index.
  • 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.

This time, we attach two HTML files to two servers, import them in the way of FS reading, and run two JS files

postMessage1.html

<body>
  <iframe src="http://localhost:8002" frameborder="0" id="frame" onLoad="load()"></iframe>
  <script>
    function load() {
      let frame = document.getElementById('frame')
      frame.contentWindow.postMessage('Hello, this is postMessage1'.'http://localhost:8002') // Send data
      window.onmessage = function (e) {
        // Accept the returned data
        console.log(e.data) // Hello, THIS is postMessage2}}</script>
</body>
Copy the code

postMsgServer1.js

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

app.get('/'.(req, res) = > {
  const html = fs.readFileSync('./postMessage1.html'.'utf8')
  res.end(html)
})

app.listen(8001.(req, res) = > {
  console.log('server listening on 8001')})Copy the code

postMessage2.html

<body>
  <script>
    window.onmessage = function (e) {
      console.log(e.data) // Hello, THIS is postMessage1
      e.source.postMessage('Hello, this is postMessage2', e.origin)
    }
  </script>
</body>
Copy the code

postMsgServer2.js

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

app.get('/'.(req, res) = > {
  const html = fs.readFileSync('./postMessage2.html'.'utf8')
  res.end(html)
})

app.listen(8002.(req, res) = > {
  console.log('server listening on 8002')})Copy the code

websocket

WebSocket is a network communication protocol. It implements full duplex communication between browser and server, and allows cross-domain communication. Long connection mode is not affected by cross-domain. Since the native WebSocket API is not easy to use, we tend to use third-party libraries such as WS.

Both Web browsers and servers must implement the WebSockets protocol to establish and maintain connections. Because WebSockets connections are long-standing, they have significant impact on the server, unlike typical HTTP connections.

Socket. HTML (http://127.0.0.1:5500/socket.html)

let socket = new WebSocket('ws://localhost:8001')
socket.onopen = function () {
  socket.send('Send data to server')
}
socket.onmessage = function (e) {
  console.log(e.data) // Data sent to you by the server
}
Copy the code

Run nodeServer. Js

const express = require('express')
const WebSocket = require('ws')
const app = express()

let wsServer = new WebSocket.Server({ port: 8001 })
wsServer.on('connection'.function (ws) {
  ws.on('message'.function (data) {
    console.log(data) // Send data to the server
    ws.send('Server data sent to you')})})Copy the code

document.domain + iframe

This method can be used only when the secondary domain names are the same.

For example, a.test.com and B.test.com are secondary domain names. They are subdomains of test.com

Just add document.domain =’test.com’ to the page to indicate that the secondary domain is the same.

Such as: page a.test.com: 3000 / test1. HTML access page b.test.com: 3000 / test2. HTML in a value

test1.html

<body>
  <iframe
    src="http://b.test.com:3000/test2.html"
    frameborder="0"
    onload="load()"
    id="iframe"
  ></iframe>
  <script>
    document.domain = 'test.com'
    function load() {
      console.log(iframe.contentWindow.a)
    }
  </script>
</body>
Copy the code

test2.html

document.domain = 'test.com'
var a = 10
Copy the code

window.name + iframe

Browsers have the feature that pages loaded by the same TAB or iframe frame share the same window.name attribute value. In the same TAB, the name value is still present in different pages loaded with the same value for the window.name attribute. Using these features, you can use this property as a medium for passing data between different pages.

For security reasons, browsers always keep window.name as a string.

Open http://localhost:8001/a.html

  • http://localhost:8001/a.html
<body>
  <iframe
    src="http://localhost:8002/c.html"
    frameborder="0"
    onload="load()"
    id="iframe"
  ></iframe>
  <script>
    let first = true
    function load() {
      if (first) {
        // After the first onload succeeds, switch to the same-domain proxy page
        let iframe = document.getElementById('iframe')
        iframe.src = 'http://localhost:8001/b.html'
        first = false
      } else {
        // After the second onload(HTML page) succeeds, read the data in the domain window.name
        console.log(iframe.contentWindow.name) // I am data in C.HTML}}</script>
</body>
Copy the code
  • http://localhost:8001/b.html (don’t need to add HTML content, the default HTML structure template)

  • http://localhost:8002/c.html

<body>
  <script>
    window.name = 'I'm data in C.HTML'
  </script>
</body>
Copy the code

The c page sets a value for window.name. Even if the C page is destroyed, the name value will not be destroyed. Page A still gets window.name.

location.hash + iframe

Implementation principle: A.HTML wants to communicate with C.HTML across domains, through the middle page B.HTML to achieve. Three pages, different fields use iframe location.hash to transfer values, the same fields directly js access to communicate.

A.html first passes a hash value to C.HTML, then C.HTML receives the hash value and passes the hash value to B.HTML, and finally B.HTML puts the result into the HASH value of A.HTML.

Similarly, A.html and B.html are co-domain, both http://localhost:8001, which means that b’s hash value can be copied directly to A’s hash. C. HTML is under http://localhost:8002

a.html

<body>
  <iframe src="http://localhost:8002/c.html#jackylin" style="display: none;"></iframe>
  <script>
    window.onhashchange = function () {
      // Check for hash changes
      console.log(456, location.hash) // #monkey
    }
  </script>
</body>
Copy the code

b.html

window.parent.parent.location.hash = location.hash
// B.HTML puts the result in the hash value of A.HTML. B.html can access a.HTML pages through parent. Parent
Copy the code

c.html

console.log(location.hash) // #jackylin
let iframe = document.createElement('iframe')
iframe.src = 'http://localhost:8001/b.html#monkey'
document.body.appendChild(iframe)
Copy the code

conclusion

  • CORS supports all HTTP requests and is the most popular solution across domains
  • In daily work, the cross-domain solutions used more are CORS and Node middleware and Nginx reverse proxy
  • Both the Node middleware proxy and the Nginx reverse proxy have no restrictions on the server through the same origin policy.

reference

  • Cross-domain resource sharing (CORS
  • Nine Cross-domain Implementation Principles (Full version)
  • Probably the best cross-domain solution


  • Ps: Personal technical blog Github warehouse, if you feel good welcome star, give me a little encouragement to continue writing ~