[cross domain]

Time:2022-3-11

preface

This article was written by the author in 2008“Those coquettish cross domain operations in those days”Remake power enhanced version of.
Gu Yun reviewed the old and learned the new. Looking back at the articles of that year, he still felt that there were many deficiencies and omissions, and there was still a lack of depth of thinking, so he remade them.
My personal ability is limited. I welcome criticism and correction.

Name correction and apology

Let’s start with a bow.
To make complaints about the translation, it is all cross-origin and Wiki to translate all kinds of English names in the form of “Wiki” and “RFC”. But somehow, it became “cross domain” in the Chinese area, which also caused a lot of misunderstanding. Here is the relationship between domain and origin. This bad translation made many Mengxin confuse these two concepts (including me).
Therefore, this article will only use the accurate translation of “cross source” and apologize for the mistakes in my previous articles.

Demonstration case

The most important point of this remake is that the author has realized a complete set of demonstration cases. There are both front-end and back-end codes. The front-end has no third-party dependence. The server is based on express. The source code details are at a glance, and the theory and practice are perfectly combined. All of the following cross source schemes can be demonstrated locally, which can be played with nodejs environment without complex configuration, compilation and container.Portal
Screenshot of home page:
[cross domain]

Same origin policy

In 1995, the same origin strategy was introduced into the browser by Netscape. At present, all browsers implement this security policy.
The “cross source” in this article is to bypass the restrictions of this strategy on the premise of ensuring security.

Core concept

The purpose of homology strategy is to ensure that the files (resources) provided by different homology are independent of each other, which is similar to the concept of sandbox. In other words, there is no limit only when different file scripts are provided by the same source. Restrictions can be broken down into two aspects:

  • Object access restrictions
    It is mainly reflected in iframe. If the parent and child pages belong to different sources, there will be the following restrictions:

    • The DOM (document object model) cannot be accessed to each other, that is, the document node cannot be obtained. The mounting methods and properties under the document, including all its child nodes, cannot be accessed. This is why cookies follow the same origin policy becausedocument.cookieAccess is not possible.
    • There are only a few permissions for BOM, that is, window objects can be obtained from each other, but all methods and most attributes cannot be used (for examplewindow.localStoragewindow.nameAnd so on), only a few attributes can be accessed with limited access, such as the following two types:

      • Readable,window.length
      • Writable,window.location.href
  • Network access restrictions
    It is mainly reflected in Ajax requests. If the target source of the initiated request is different from that of the current page, the browser will have the following restrictions:

    • Intercept response: forSimple request, the browser will initiate a request and the server will respond normally, but as long as the response header returned by the server does not meet the requirements, it will ignore all the returned data and report an error directly.
    • Restrict requests: for non simple requests, modern browsers will initiate them firstPre inspection requestHowever, as long as the response message header returned by the server does not meet the requirements, it will directly report an error and will not launch a formal request again. In other words, in this case, the server will not get any data about this request.

What is the same origin

Origin is strictly defined in the web domain and consists of three parts: protocol, domain and port.

origin = scheme + domain + port

In other words, the three are exactly the same, so they can be called homology.
For example, suppose there is now a sourcehttp://example.comSend a request to the following sources, and the results are as follows:

origin(URL) result reason
http://example.com success The protocol, domain and port number are the same (the browser defaults to port 80)
http://example.com:8080 fail Different ports
https://example.com fail Different agreement
http://sub.example.com fail Different domain names

Cross origin

The era when the homology strategy was proposed was still the era when the traditional MVC Architecture (JSP, ASP) was popular. At that time, most of the pages were filled by server rendering, and developers would not maintain independent API services. Therefore, in fact, there were few cross source requirements.
With the separation of the front and rear end of the new era and the rise of the third-party jssdk, we began to find that although this strategy greatly improves the security of the browser, it is sometimes very inconvenient and its reasonable use is also affected. For example:

  • Independent API services use independent domain names for easy management;
  • The front-end developer needs to use remote API for local debugging;
  • The jssdk developed by a third party needs to be embedded into other people’s pages for use;
  • Open API of public platform.

Therefore, cross source solutions to solve these problems have been put forward one after another, which can be described as a hundred schools of thought, including amazing operations. Although there are standard CORS solutions, it is still worth learning for in-depth understanding of the interaction between browser and server.

Json-p (self filling JSON)

Json-p is one of the most popular cross source solutions. Now it will be used in some environments that are compatible with old browsers. The famous jQuery also encapsulates its method.

principle

Do not know the meaning of the name. The P in the name means padding and “filling”. This method does not use ordinary JSON formatted text in the communication process, but “JavaScript script with filling function”.
How to understand “JavaScript script with filling function”? Take a look at the following example.
Assuming that there is this getanimal function on the global window, and then a script that calls this function and passes in data through the script tag, cross source communication can be realized.

//There is this function globally
function getAnimal(data){
  //Get data
  var animal = data.name
  // do someting
}

Another script:

//Call function
getAnimal({
  name: 'cat'
})

That is to say, using the feature that JavaScript script will run automatically when the browser is introduced, it can be used to transfer data to global functions. If the script calling the function is used as the output of the server API, cross source communication can be realized. This is the core principle of json-p method, which is filled with the data of global functions.

technological process

  1. Define the callback function globally, that is, the function to be called in the JS script output by the server API;
  2. Create a new script tag, SRC is the API address. Insert the tag into the page and the browser will initiate a get request;
  3. The server generates JS script according to the request and returns it;
  4. When the page waits for the script tag to be ready, it will automatically call the globally defined callback function to obtain data.

[PS] not only script tags, but all tags that can use SRC attribute can initiate get requests without restriction of homology policy (when CSP is not configured), such as img, object, etc., but only script tags can automatically run JS code.

[cross domain]

error handling

  • The front end can capture the network error through the error event of script tag, but the specific error reason is unknown, that is, the response status code of the server cannot be obtained;
  • If the script returned by the server runs incorrectly, the front end can only capture it through the global error event.

Practical tips

  • front end

    • In order to avoid polluting the global, it is not recommended that the front end generate a large number of global functions with random names. You can save all callback functions with one object and give each callback function a unique ID. the global only exposes the unified actuator and calls the callback function by relying on the ID;
    • If you follow the suggestions of the previous article, the callback function in the global object needs to be cleaned up in time;
    • Each request should generate a new script tag, which should be cleaned up in time after completion;
    • For flexibility, you can also agree with the server to pass the callback function name as a parameter to reserve the expansion space of multiple global objects.
  • Server

    • You only need to receive the request of get method, and other methods can be determined as illegal;
    • You can only get parameters in the requested URL, such as query or path;
    • The content type of the response message header is set to text / JavaScript;
    • It is strongly recommended to close the HTTP protocol cache to avoid data inconsistency. Please refer to the relevant methods of the authorHTTP articles
    • The returned script is written into the response message in plain text format. Since the script is run directly, special attention should be paid to XSS attack.

The design idea of “one object saves all callback functions” in the front end:

function initJSONPCallback() {
  //The object that holds the callback object
  const cbStore = {}
  //Here, a closure is formed, which can only operate cbstore in a specific way.
  return {
    //Unified actuator (function).
    run: function (statusCode, data) {
      const { callbackId, msg } = data
      try {
        //Failed to run branch.
        if (...) {
          cbStore[callbackId].reject(new Error(...))
          return
        }
        //Run branch successfully.
        cbStore[callbackId].resolve(...)
      } finally {
        //Perform cleanup.
        delete cbStore[callbackId]
      }
    },
    //Set the callback object, which is called when a request is made.
    set: function (callbackId, resolve, reject) {
      //The callback object contains two branch functions: success and failure.
      cbStore[callbackId] = {
        resolve,
        reject
      }
    },
    //The callback object is deleted and called during cleanup.
    del: function (callbackId) {
      delete cbStore[callbackId]
    }
  }
}

//Initialize
const JSONPCallback = initJSONPCallback()
//Globally expose the executor, which is also the function called by the API return script.
window.JSONPCb = JSONPCallback.run

For specific code, please refer toJsonp part of demonstration caseSource code.

summary

  • advantage

    • Simple and fast, which is really fast compared with the scheme requiring iframe (you can experience it in the demonstration case);
    • Support ancient browser (IE8 -);
    • There are no requirements for domain and can be used for third-party APIs.
  • shortcoming

    • It can only be get method, and cannot customize the request message header or write the request message style;
    • The amount of requested data is limited by the maximum length of the URL (different browsers);
    • The debugging is difficult, and the server error cannot detect the specific reason;
    • Special interface support is required, and standard API specifications cannot be used.

Subhostproxy (subdomain name proxy)

Subdomain proxy is a very practical cross source solution under specific environmental conditions. It can provide an experience that is no different from that of normal Ajax requests.

principle

First find out what is a subdomain? Domain name resolution is from right to left. When we apply for a domain name, we apply for the two rightmost segments (segmented by points), and the latter part can be customized for the owner. You can add a few more segments if you want. These derived domains are sub domains. for instance,api.demo.comnamelydemo.comSubdomain of.
Theoretically, the two domains in the example are different. According to the above mentioned domain is part of the origin, so they are also different sources. However, the browser allows to change the domain of the page document to the parent of the current domain, that is, inapi.demo.comThe following code can be changed todemo.comHowever, this modification only affects the permission of document, and has no effect on Ajax.

//In API demo. Com page write the following code
document.domain = 'demo.com'

【PS】document.domainFeatures: it can only be set once; Only the domain part can be changed, and the port number and protocol of the page cannot be modified; The port of the current page will be reset to the protocol default port (i.e. 80 or 433); It only works on document and does not affect the homology strategy of other objects.

Therefore, the principle of this scheme is to enable the parent page to have access to the document of the sub domain page through this method. The sub domain happens to be the domain of API, and then initiate the request through the sub domain page agent to realize cross source communication.

technological process

Suppose the domain of the server API isapi.demo.com, page field isdemo.com, running on HTTP protocol, port 80.

  1. A proxy page is deployed at the lower part of the sub domain, and its domain is set todemo.comAnd can include tools to launch Ajax (jQuery, Axios, etc.);
  2. The main page is also set to domaindemo.com
  3. Create a new iframe tag on the main page and link it to the agent page;
  4. When the proxy page in iframe is ready, the parent page can be usediframe.contentWindowTake control of the proxy page and use it to initiate Ajax requests.

[cross domain]

error handling

  • The error event of iframe is invalid in most browsers (default), so the error in iframe is unknown to the main page;
  • Through the load event of iframe, you can check whether the proxy page is loaded, so as to indirectly judge whether there is a network error, but you don’t know the specific cause of the error, that is, you can’t get the response status code of the server;
  • When the main page obtains control of the proxy page, the error handling is no different from normal Ajax.

Practical tips

  • front end

    • Loading the proxy page takes time (it’s actually quite slow), so pay attention to the timing of initiating the request to avoid the request before the proxy page is loaded;
    • It is not necessary to load a new proxy page for each request. It is strongly recommended to keep only one and share multiple requests;
    • If you follow the suggestions in the previous article, you also need to consider the loading failure of the proxy page to avoid the subsequent failure after one failure;
    • You can use the preload method to load the proxy page in advance to avoid increasing the request time;
    • Master page must be useddocument.domainSetting, that is, the current domain has met the requirements, that is, although the current page has a domainxxx, but I still have to call it againdocument.domain='xxx'
  • Server

    • Only use standard port 80 (HTTP) or 443 (HTTPS) for deployment (or use reverse proxy);
    • The domain of the proxy page must be consistent with the domain of the API and have a common parent with the domain of the main page (or the domain of the main page is the parent);
    • In theory, as long as the proxy page is executeddocument.domain=xxxHTML format file, so it can be as concise as possible.

Design idea of shared iframe:

//Encapsulate the created iframe with promise and save it.
let initSubHostProxyPromise = null

//This function should be called before each request.
function initSubHostProxy() {
  if (initSubHostProxyPromise != null) {
    //If the promise already exists, it will be returned directly. Since the promise has been resolved, it is actually equivalent to returning the existing iframe.
    return initSubHostProxyPromise
  }
  //If not, recreate.
  initSubHostProxyPromise = new Promise((resolve, reject) => {
    const iframe = document.createElement('iframe')
    //Fill in the address of the agent page.
    iframe.src = '...'
    iframe.onload = function (event) {
      //This is a hack error detection method. See the demo case readme.
      if (event.target.contentWindow.length === 0) {
        //Failed branch
        reject(new Error(...))
        setTimeout(() => {
          //Clean up the failed promise so that it will be recreated next time.
          initSubHostProxyPromise = null
          //You also need to remove the iframe here.
          document.body.removeChild(iframe)
        })
        return
      }
      //Branch successfully and return iframe DOM object.
      resolve(iframe)
    }
    document.body.appendChild(iframe)
  })
  return initSubHostProxyPromise
}

For specific code, please refer toDemonstration case subhostproxy sectionSource code.

summary

  • advantage

    • Any type of request can be sent;
    • Standard API specifications can be used;
    • It can provide no different experience from normal Ajax requests;
    • Error capture is convenient and accurate (except for iframe network errors);
    • Support ancient level browsers (IE8 -).
  • shortcoming

    • It has strict requirements on domain and cannot be used in third-party API;
    • Iframe has a great impact on browser performance;
    • Non protocol default ports cannot be used.

Html-p / mockform (self filling HTML / mock form)

This scheme is generally called “simulated form” on the Internet, but I don’t think it is accurate. Using form to initiate request is not its core feature (there are several schemes used later). Its core should be “self filling HTML”.

principle

I call it html-p, which is based on the name of json-p. its idea is also very similar to the json-p scheme. The server API returns a JS script that can be run automatically for data filling. It can not directly return the whole HTML page.
But in fact, there are still restrictions on HTML data filling. The first is the homology restriction. If the parent and child pages have different sources, they cannot access each other. The solution is naturally mentioned in the “sub domain agent”document.domainModify Dafa, but its purpose is just opposite to that of “sub domain agent”. By modifying the domain of document, the sub page can obtain the access permission of the main page, so as to fill in the data of the main page and realize cross source communication.

//The API returns HTML containing the following script to access the global function of the parent page for data filling.
document.domain = 'xxx'
window.parent.callbackFunction(...)

As for the function of the form, it actually uses the target attribute of the form. When the form submits, it will jump the iframe of the specified name. The jump is actually to initiate the request. Therefore, the request methods supported by the browser form component can be used. Just because the form is used to initiate the request, the server API must return a text in HTML format.

technological process

Suppose the domain of the server API isapi.demo.com, page field isdemo.com, running on HTTP protocol, port 80.

  1. Define the callback function globally, that is, the function to be called in the HTML output by the server API;
  2. Set the main page domain todemo.com
  3. Create a new iframe tag on the main page and specify name;
  4. Create a new form tag, specify the target as the name of the iframe, and add data and configuration requests;
  5. Submit the form and jump in iframe;
  6. After receiving the request, the server generates an HTML page according to the request parameters and returns it. Its domain is set todemo.com
  7. Iframe completes the loading of HTML, the sub page calls the callback function defined globally on the main page, and the main page obtains data.

[cross domain]

error handling

  • The error event of iframe is invalid in most browsers (default), so the error in iframe is unknown to the main page;
  • Through the load event of iframe, you can check whether the proxy page is loaded, so as to indirectly judge whether there is a network error, but you don’t know the specific cause of the error, that is, you can’t get the response status code of the server;
  • The error occurred when the sub page calls the main page belongs to the error in iframe, so it is also unknown.

Practical tips

  • front end

    • In order to avoid global pollution, it is not recommended that the front end generate a large number of global functions with random names. You can save all callback functions with one object. For this, please refer to json-p above;
    • Master page must be useddocument.domainSetting means that the current domain has met the requirements.
    • Because the pages in iframe are different every time, iframe tags can be reused, but pages cannot be reused;
    • Multiple iframe pages will be generated simultaneously during concurrency, which will lead to extreme performance degradation. This scheme is not applicable to concurrency scenarios;
    • Form and iframe labels shall be cleaned in time after completion;
  • Server

    • Only use standard port 80 (HTTP) or 443 (HTTPS) for deployment (or use reverse proxy);
    • The domain of API has a common parent with the domain of the main page (or the domain of the main page is the parent);
    • Set the content type of the response message header to text / HTML;
    • It is strongly recommended to close the HTTP protocol cache to avoid data inconsistency. Please refer to the relevant methods of the authorHTTP articles
    • The returned html is written into the response style in plain text format. Because the script is run directly, special attention should be paid to XSS attack;
    • The generated HTML should be as concise as possible.

For specific code, please refer toMockform part of demo caseSource code.

summary

This scheme can be said to be a stitched version of “json-p” and “sub domain agent”, and its advantages and disadvantages are inherited.

  • advantage

    • Any type of request can be sent (subject to browser form label support);
    • Compared with “sub domain proxy”, no proxy page is an advantage,
    • Support ancient level browsers (IE8 -).
  • shortcoming

    • It has strict requirements on domain and cannot be used in third-party API;
    • Iframe has a great impact on browser performance, and concurrency requires multiple iframes, which can not be used in scenarios requiring concurrency;
    • Non protocol default ports cannot be used.
    • It is difficult to capture errors. The specific causes of server errors cannot be detected, and running errors cannot be captured;
    • Special interface support is required, and standard API specifications cannot be used.

WindowName

This is a window A scheme with the name feature as the core.

principle

This scheme makes use of window Name’s feature: once assigned, the value of the iframe will not change when it is redirected to a new URL. Although window Name still follows the same origin policy. Only the same origin can read the value, but we can realize cross source communication as long as we write the value on the non same origin page and redirect to the same origin page to read the value.
The method of initiating the request is the same as that of “html-p”, which is realized by triggering iframe jump with form.

//Get the window. ID from the load event of iframe The value of name.
iframe.onload = function (event) {
  const res = event.target.contentWindow.name
}

technological process

  1. Create a new iframe tag on the main page and specify name;
  2. Create a new form tag, specify the target as the name of the iframe, and add data and configuration requests;
  3. Submit the form and jump in iframe;
  4. The server receives the request, generates an HTML page according to the request parameters and returns it;
  5. Iframe loads HTML and runs the script to set the data to window Name, and redirect;
  6. Iframe loads HTML again, and the load event is triggered when it is completed;
  7. The main page monitors the load event of iframe and obtains its window The value of name.

[cross domain]

error handling

  • The error event of iframe is invalid in most browsers (default), so the error in iframe is unknown to the main page;
  • Through the load event of iframe, you can check whether the proxy page is loaded (hack method is required for non homology), so as to indirectly judge whether there is a network error, but you don’t know the specific cause of the error, that is, you can’t get the response status code of the server;
  • Other errors can be captured normally.

Practical tips

  • front end

    • The notes related to form and iframe are the same as “html-p”;
    • Theoretically, the page redirected to the same domain does not need any content. As long as there is HTML format, it should be as concise as possible, and since there is no need to change, it can be cached for a long time;
    • In theory, iframe’s load event will trigger twice (one non homologous page and one homologous page), but in fact, as long as the load is redirected before triggering, the load event of non homologous page will not be received;
    • Redirection should usewindow.location.replaceIn this way, history will not be generated, which will affect the back operation of the main page;
    • For flexibility, it is recommended to pass the URL of the redirected page to the server.
  • Server

    • Set the content type of the response message header to text / HTML;
    • It is strongly recommended to close the HTTP protocol cache to avoid data inconsistency. Please refer to the relevant methods of the authorHTTP articles
    • The returned html is written into the response style in plain text format;
    • The generated HTML should be as concise as possible.

For specific code, please refer toPresentation case windowname sectionSource code.

summary

  • advantage

    • Any type of request can be sent (subject to browser form label support);
    • There is no requirement for domain, which can be used for third-party API;
    • Support ancient level browsers (IE8 -).
  • shortcoming

    • Iframes have a great impact on browser performance. Two jumps are even worse, and multiple iframes are required for concurrency, which can not be used in scenarios requiring concurrency;
    • The almost blank homologous redirection page can be said to be meaningless traffic, affecting traffic statistics;
    • It is difficult to capture errors. The specific causes of server errors cannot be detected, and running errors cannot be captured;
    • Special interface support is required, and standard API specifications cannot be used.

WindowHash

This is a scheme based on the hash part of the URL.

principle

This scheme takes advantage ofwindow.location.hashFeatures: pages in different fields can be written unreadable. Changing only the hash part (after the pound sign) will not cause the page to jump. That is, the non homologous sub page can write the hash part of the main page URL. The main page realizes cross source communication by monitoring the hash changes.
The method of initiating the request is the same as that of “html-p”, which is realized by triggering iframe jump with form.

//Modern browsers have hashchange events to listen for.
window.addEventListener('hashchange', function () {
  //Read hash
  const hash = window.location.hash
  //Clean hash
  if (hash && hash !== '#') {
    location.replace(url + '#')
  } else {
    return
  }
})
//In the degradation scheme, read the hash circularly for "listening".
var listener = function(){
    //Read hash
    var hash = window.location.hash
    //Clean hash
    if (hash && hash !== '#') {
      location.replace(url + '#')
    }
    //Keep listening
    setTimeout(listener, 100)
}
listener()

technological process

  1. Create a new iframe tag on the main page and specify name;
  2. Create a new form tag, specify the target as the name of the iframe, and add data and configuration requests;
  3. Submit the form and jump in iframe;
  4. The server receives the request, generates an HTML page according to the request parameters and returns it;
  5. Iframe loads HTML and runs the script to modify the hash of the main page;
  6. The main page monitors the change of hash and clears the hash every time the hash value is obtained.

[cross domain]

error handling

  • The error event of iframe is invalid in most browsers (default), so the error in iframe is unknown to the main page;
  • Through the load event of iframe, you can check whether the proxy page is loaded (hack method is required for non homology), so as to indirectly judge whether there is a network error, but you don’t know the specific cause of the error, that is, you can’t get the response status code of the server;
  • Other errors can be captured normally.

Practical tips

  • front end

    • The notes related to form and iframe are the same as “html-p”;
    • Set the main page hash to usewindow.location.replaceIn this way, history will not be generated, which will affect the back operation of the main page;
    • Each hash setting requires a certain amount of cooling, and concurrent errors may occur;
    • It is not necessary to listen to hashchange events every time a request is made. You can set up a unified event handler during initialization, save the callback of each request with an object, assign a unique ID, and call the callback by ID through the unified event handler;
    • If you follow the suggestions of the previous article, the callback function in the global object needs to be cleaned up in time;
    • Since the iframe is a non homologous page (generated by the server), the URL of the main page is unknown, so the URL needs to be passed to the server through parameters.
  • Server

    • Set the content type of the response message header to text / HTML;
    • It is strongly recommended to close the HTTP protocol cache to avoid data inconsistency. Please refer to the relevant methods of the authorHTTP articles
    • The returned html is written into the response style in plain text format;
    • The generated HTML should be as concise as possible.

Design idea of front-end “unified event processor”:

function initHashListener() {
  //The object that holds the callback object
  const cbStore = {}
  //Set monitoring, only one.
  window.addEventListener('hashchange', function () {
    //Process hash.
    ...
    try {
      //Failed to run branch.
      if (...) {
        cbStore[callbackId].reject(new Error(...))
        return
      }
      //Run branch successfully.
      cbStore[callbackId].resolve(...)
    } finally {
      //Perform cleanup.
      delete cbStore[callbackId]
    }
  })
  //Here, a closure is formed, which can only operate cbstore in a specific way.
  return {
    //Sets the method of the callback object.
    set: function (callbackId, resolve, reject) {
      //The callback object contains two branch functions: success and failure.
      cbStore[callbackId] = {
        resolve,
        reject
      }
    },
    //Method to delete the callback object.
    del: function (callbackId) {
      delete cbStore[callbackId]
    }
  }
}
//Initialization. Each request calls its set method to set the callback object.
const hashListener = initHashListener()

For specific code, please refer toWindowhash part of demo caseSource code.

summary

  • advantage

    • Any type of request can be sent (subject to browser form label support);
    • There is no requirement for domain, which can be used for third-party API;
    • Support ancient level browsers (IE8 -).
  • shortcoming

    • Iframe has a great impact on browser performance, and concurrency requires multiple iframes, which can not be used in scenarios requiring concurrency;
    • In concurrent scenarios, it is easy to cause hash operation crash. This problem will be more serious if the method of cyclic reading hash is used for monitoring. Unless there is a more rigorous anti-collision mechanism, it is strongly not recommended to use it concurrently;
    • The amount of requested data is limited by the maximum length of the URL (different browsers);
    • It is difficult to capture errors. The specific causes of server errors cannot be detected, and running errors cannot be captured;
    • Special interface support is required, and standard API specifications cannot be used.

In the next part, we will discuss the cross source of modern standard (HTML5),Today’s article