Why cross domains?

For security reasons, browsers restrict cross-source HTTP requests originating within scripts. For example, the XMLHttpRequest and Fetch apis follow the same origin policy. This means that Web applications using these apis can only request HTTP resources from the same domain that loaded the application, unless the response message contains the correct CORS response header.

Same-origin policy: Two urls are same-origin if their protocol, port(if specified), and host are the same.

Cross-domain solutions

1. JSONP

Prior to CORS, developers also had the need to request resources across domains, and they proposed a variety of solutions, among which JSONP was a common one. As an ancient solution, JSONP is not recommended to be used in the project, but its advantage is that it supports older browsers and can request data from websites that do not support CORS. It is also necessary for us to understand its implementation principle and broaden our development thinking.

How it works: All HTML tags with SRC attributes are cross-domain. <script>, <img>, <iframe>, and <link> can load cross-domain (non-same-origin) resources in the same way as a normal GET request. The only difference is that for security purposes, browsers do not allow this method of reading and writing the loaded resources. You can only use the capabilities that the tag should have (such as script execution, style application, and so on).

Disadvantages of JSONP:

  • Only one request can be implemented, get.
  • The front end of JSONP cannot retrieve various HTTP status codes in the event of a failed call.
  • Security issues. In case the service providing JSONP has a page injection vulnerability, that is, the javascript content it returns is controlled by someone. So what’s the result? Any site that calls this JSONP will have vulnerabilities. So can not control the risk under a domain name… Therefore, when using JSONP, we must ensure that the JSONP service used must be secure and trusted.

Using the principle that script tag has inherent cross-domain capability and script execution capability, the front and back ends agree on a function name callbackName, use script tag to send requests, and the server side responds to requests by obtaining data RES and returning scripts in callback(RES) format. The function callback is executed on the client side with res as an argument. At this point, the client gets the interface return value res.

Start by implementing a simple JSONP.

Jsonp_v1 version

Example server code (node) XXX.x.com

router.get('/test', async ctx => { const { keyword, callback } = ctx.query; let res = await testController.getData({ keyword }); CTX. Body = ` ${callback} ({code: 1, data: ${JSON. Stringify (res)}, MSG: 'request is successful'}) `; });Copy the code

Front-end sample code

Const getEncodeParams = (data = {}) => {let res = [] for (let key in data) { res.push(`${encodeURIComponent(key)}=${encodeURIComponent(data[key])}`); } return res.join('&'); }Copy the code
function jsonp_v1 (url, data, callbackFunc) { let elem = document.createElement('script'); elem.type = 'text/javascript'; elem.src = `${url}? ${getEncodeParams(data)}&callback=callbackFunc`; document.body.appendChild(elem); window.callbackFunc = callbackFunc; } / / send the request jsonp_v1 (' http://localhost:3000/tool/test '{keyword: 'hello-world'}, function (res) {alert(res)});Copy the code

Disadvantages: Window. callbackFunc can be overridden when sending multiple requests, such as running the following code where we expect alert once, console once, and console twice.

jsonp_v1('http://localhost:3000/tool/test', { keyword: 'hello-world'}, function (res) {alert(res)}); jsonp_v1('http://localhost:3000/tool/test', { keyword: 'hello'}, function (res) {// The server will return the script that called this function console.log(res)});Copy the code

Jsonp_v2 version

Optimization for v1: Multiple JSONP requests need to be sent at the same time, and callbackFunc needs to have a unique property name hanging on the window.

function jsonp_v2(url, data, callbackFunc) { let elem = document.createElement('script'); let callbackName = `jsonp_${new Date().getTime()}`; elem.type = 'text/javascript'; elem.src = `${url}? ${getEncodeParams(data)}&callback=${callbackName}`; document.body.appendChild(elem); window[callbackName] = callbackFunc; }Copy the code

Jsonp_v3 version

Optimization: Encapsulate promise returns and add error handling

function jsonp_v3 (url, data) { return new Promise((resolve, reject) => { let elem = document.createElement('script'); let callbackName = `jsonp_${new Date().getTime()}`; elem.type = 'text/javascript'; elem.src = `${url}? ${getEncodeParams(data)}&callback=${callbackName}`; document.body.appendChild(elem); window[callbackName] = resolve; Elem. Onerror = function () {reject(' reject ')}}); }Copy the code

Jsonp_v4 version

Optimization: When sending a large number of requests, we find that the page is overloaded with global variables and script tags, which should be deleted after completing the request.

function jsonp_v4 (url, data) { return new Promise((resolve, reject) => { let elem = document.createElement('script'); let callbackName = `jsonp_${new Date().getTime()}`; elem.type = 'text/javascript'; elem.src = `${url}? ${getEncodeParams(data)}&callback=${callbackName}`; document.body.appendChild(elem); Window [callbackName] = function (res) {resolve(res); delete window[callbackName]; document.body.removeChild(elem); } elem.onError = function() {reject(' reject '); delete window[callbackName]; document.body.removeChild(elem); }}); }Copy the code

The sample code

2. Image ping

Image Ping works in much the same way as JSONP, which is a simple, one-way, cross-domain communication with the server. The requested data is sent in the form of a query string, and the response can be anything, but usually a pixel image or 204 response. With image pings, the browser doesn’t get any concrete data, but by listening for load and error events, it knows when the response was received.

let img = new Image(); Img. Onload = img. Onerror = function () {the console. The log (' already inform server Nick to browse this page ')} img. SRC = ` http://localhost:3000/tool/test? name=nick`Copy the code

The sample code

3. CORS cross-domain resource sharing

CORS is a W3C standard called Cross-Origin Resource Sharing. It is an HTTP header based mechanism that allows the server to identify any origin(domain, protocol and port) other than its own so that the browser can access and load those resources. Cross-domain resource sharing also checks whether the server will allow the actual request to be sent through a mechanism that sends an “option request” through the browser to a server-hosted cross-domain resource. In precheck, the browser sends headers that are identified with HTTP methods and those that will be used in real requests.

The key to CORS communication is the server. As long as the server implements the CORS interface, cross-source communication is possible.

Here is the sample code (NodeJS middleware)

module.exports = function (WHITE_WEBSITES) { return async function (ctx, next) { const allowHost = WHITE_WEBSITES; / / white list the if (allowHost. Includes (CTX) request. The header. Origin)) {CTX. Set (' Access - Control - Allow - origin, ctx.request.header.origin); ctx.set('Access-Control-Allow-Headers', 'Content-Type, Content-Length, Authorization, Accept, X-Requested-With'); ctx.set('Access-Control-Allow-Methods', 'POST, GET, OPTIONS'); ctx.set('Access-Control-Allow-Credentials', true); } if (ctx.method == 'OPTIONS') { ctx.response.status = 204; } else { await next(); }}}Copy the code

When the front-end sends requests, set withCredentials to true to enable cookies to be sent in cross-domain requests.

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.

4. Reverse proxy

Note that this is typically done during development, since interface proxying comes at a cost.

In the process of front-end local development, it is often necessary to use proxies to solve cross-domain problems.

// vue.config.js devServer: { ... Proxy: {'/test - proxy: {target: 'http://192.168.22.173:3000', pathRewrite: {' ^ / test - proxy ':'}}}}Copy the code

A brief introduction to forward and reverse proxies

To use a less apt but understandable analogy, a forward agent is the agent who helps us find a house, and a directional agent is the principal landlord.

5. Nodejs middleware proxy across domains

Use middleware such as KOA2-proxy-middleware

6. iframe

Method 1 iframe + postMessage

Iframe allows a page to nest HTML files from non-homologous sites. They can communicate via the postMessage API. With these two features, we can embed an IFrame in the page (assuming that a.HTML is pointing to B.HTML and b.HTML is communicating with the server). Set its display to None, and the interface will be requested by non-cross-domain B.HTML, and the interface return value will be returned to A.HTML via postMessage

// a.html <script> /** * communicate with iframe * @author astar * @date 2021-07-03 12:47 */ function iframeCommunicate(url, cb) { let iframe = document.createElement('iframe'); let key = `callbackName_${new Date().getTime()}`; iframe.style.display = 'none'; iframe.name = key; iframe.src = url + `&key=${key}`; / / add unique document request. Body. The appendChild (iframe); window[key] = cb; } function handleIframe (e) { let { data, key } = e.data; if (window[key]) { window[key](e.data); let iframes = document.getElementsByName(key); document.body.removeChild(iframes[0]); delete window[key]; }} // All interface returns will call handleIframe, need to specify a unique identifier (key) to identify the returned information which call iframeCommunicate window.addeventListener ('message', handleIframe); iframeCommunicate(`http://localhost:3000? type=GET&queryUrl=${encodeURIComponent('http://localhost:3000/tool/test? Keyword =888&callback=test')} ', function(params) {console.log(' callback ', params)}); iframeCommunicate(`http://localhost:3000? type=POST&queryUrl=${encodeURIComponent('http://localhost:3000/tool/test')}&keyword=999&callback=test`, Function (params) {console.log(' interface return ', params)}); </script>Copy the code
* @author astar * @date 2021-07-03 17:37 */ function parseSearch() {function parseSearch() search = location.search.slice(1); let keyValuePairArr = search.split('&'); let obj = {}; keyValuePairArr.forEach(pair => { let [key, value] = pair.split('='); obj[decodeURIComponent(key)] = decodeURIComponent(value); }); return obj; } @author astar * @date 2021-07-03 17:38 */ function getParams(obj) {delete obj.type; delete obj.queryUrl; return obj; } let obj = parseSearch(); // let XHR = new XMLHttpRequest(); xhr.onreadystatechange = function () { if (xhr.readyState == 4) { console.log(xhr.status, xhr.responseText) if ((xhr.status >= 200 && xhr.status < 300) || xhr.status === 304) { window.parent.postMessage({ data:  xhr.responseText, key: obj.key }, '*'); } else { console.log("Request was unsuccessful:" + xhr.status); }}}; xhr.open(obj.type, obj.queryUrl); xhr.send(JSON.stringify(getParams(obj))); </script>Copy the code

The above implementation has room for optimization, such as request interface failure handling, but this is not the focus of this article, readers interested in their own improvement.

Sample code -A.HTML

Sample code – B.html

Method 2 iframe + window.name

The window.name variable is shared between iframes. We can assign window.name to the interface return value after d.html retrieves it. Use window.href to open e.html(e and C are under the same domain name), and use window.parent to access the functions on the E page.

<script> /** * communicate with iframe * @author astar * @date 2021-07-03 12:47 */ function iframeCommunicate(url, cb) { let iframe = document.createElement('iframe'); let key = `callbackName_${new Date().getTime()}`; iframe.style.display = 'none'; iframe.name = key; iframe.src = url + `&key=${key}`; / / add unique document request. Body. The appendChild (iframe); window[key] = function (data) { cb(data) delete window[key]; document.body.removeChild(iframe); }; } iframeCommunicate(`http://localhost:3000? type=GET&queryUrl=${encodeURIComponent('http://localhost:3000/tool/test? Keyword =888&callback=test')} ', function (params) {console.log(' callback ', params)}); iframeCommunicate(`http://localhost:3000? type=POST&queryUrl=${encodeURIComponent('http://localhost:3000/tool/test')}&keyword=999&callback=test`, Function (params) {console.log(' interface 2 returns ', params)}); </script>Copy the code
* @author astar * @date 2021-07-03 17:37 */ function * @author astar * @date 2021-07-03 17:37 */ parseSearch() { let search = location.search.slice(1); let keyValuePairArr = search.split('&'); let obj = {}; keyValuePairArr.forEach(pair => { let [key, value] = pair.split('='); obj[decodeURIComponent(key)] = decodeURIComponent(value); }); return obj; } @author astar * @date 2021-07-03 17:38 */ function getParams(obj) {delete obj.type; delete obj.queryUrl; return obj; } let obj = parseSearch(); let xhr = new XMLHttpRequest(); xhr.onreadystatechange = function () { if (xhr.readyState == 4) { if ((xhr.status >= 200 && xhr.status < 300) || xhr.status === 304) { window.name = JSON.stringify({ data: xhr.responseText, key: obj.key }); location.href = 'http://localhost:8080/e.html'; } else { console.log("Request was unsuccessful:" + xhr.status); }}}; xhr.open(obj.type, obj.queryUrl); xhr.send(JSON.stringify(getParams(obj))); </script>Copy the code
// e.html
<script>
  let { key, data } = JSON.parse(window.name);
  window.parent[key](data);
</script>
Copy the code

Sample code – C.HTML

Sample code – d.HTML

Sample code – E.html

Other iframe solutions to cross-domain include document.domain, location.hash, etc., which will not be described in detail due to space limitations.

“Reference”

JSONP and image ping cross – domain process

Cross-domain resource Sharing CORS – Ruan Yifeng

Common Cross-domain solutions at the front end (full)

Summary of javascript cross-domain methods

Ajax is cross-domain, and this should be the most complete solution

Finally someone put forward agent and reverse agent explain clearly!

Forward proxy and Reverse Proxy

Iframe resolves cross-domain Ajax requests