Why cross-domain

For security reasons, browsers introduce the same origin policy. This strategy can be carried to our page of js to restrict access to the resource, for example, we cannot be accessed directly by js page DOM structure under different source, at the same time in different source sends a request to also can’t get to the server response content (server will be normal processing the request and returns the response content, but the content of the return blocked by the browser off). The concept of “source” is also involved here. If the protocol, domain name, or port of the destination URL we visit and the current page url are different, it is considered to belong to two different sources. Now that we understand the definition of sources, let’s look at what we can and can’t do on a page under the same origin policy.

Let’s start with what we can do, such as redirecting our page via JS (modify location.href), form submission, and so on. There is also the ability to load resources by embedding HTML tags, such as the script tag to introduce a script, the IMG tag to insert an image, the link tag to load a style file, and the iframe to embed a page from a different source. These are all perfectly normal operations in our daily development process.

However, the same origin policy restricts JS access to some sensitive resources. In addition to the two points mentioned at the beginning, there is also js can not access the cookie, LocalStorage content stored in the same source. Specifically, cookie and LocalStorage in control of which sources can access the problem is still subtle differences, the parent domain in setting the cookie can be set to allow sub-domain access to the cookie, and the cookie is only associated with the domain name and path, If it is the same domain name, different ports of the source still share the Cookie under the same domain name, and LocalStorage is managed by the source unit, independent of each other, different sources cannot access the content in LocalStorage.

Common cross-domain methods

The client communicates with the server

Communication between clients and servers of different sources is a common problem that needs to be solved in the normal development process. There are mainly the following ways:

CORS

This method should be the one used more often. The full name of CORS is cross-origin Resource Sharing, which translates to cross-domain Resource Sharing. The basic idea is to introduce custom HTTP headers to communicate between the client and the server.

For some simple requests, the browser will send the request with the Origin header, indicating the current source. The server will not check whether the current source of the request is legitimate when processing the request, but will still process the request and respond normally. Finally, after receiving the response, the browser checks the access-Control-Allow-Origin list of the server’s response to see if the source of the current page exists. If not, it blocks the current request.

A request that meets all of the following criteria is considered a simple request by the browser:

The request method is GET or POST. Contains only the Accept, Accept and Language, the Content and Language or the content-type (values for the application/x – WWW – form – urlencoded, multipart/form – the data, Or text/plain), and the rest are non-simple headers;

For non-simple requests, the browser first sends a Preflight request to the server that uses the Option method and contains the following headers:

  1. Origin
  2. Access-control-request-method: asks whether the server supports a Method.
  3. Access-control-request-headers: asks the server if it supports non-simple Headers contained in the Request.

The last two headers will only appear in Preflight requests. The browser then receives a server response containing the following Header:

  1. Access-Control-Allow-Origin
  2. Access-control-allow-methods: a list of request Methods to which the client responds to server support;
  3. Access-control-allow-headers: Headers supported by the server for the client;

The Preflight request ends at this point, and the browser checks to see if the source of the current request is in the access-Control-Allow-Origin list of the server’s response, and then sends the actual request. During the experiment, the browser did not have to send the actual request when the server supported the request method and Header of the Preflight request query. Instead, it sent the request after the Preflight request as long as the source of the request was legitimate.

JSONP

JSONP stands for JSON with Padding. When the front end specifies the URL to request, a parameter specifying the callback function name can be agreed with the back end to ensure that the callback function specified by the front end is called in the script fragment of the background response, so that multiple JSONP requests can be sent without interference. The specific JSONP implementation is as follows:

var delicious_callbacks = {};
function jsonp(url, callback) {
  var uid = (new Date()).getTime();
  delicious_callbacks[uid] = function (data) {
    delete delicious_callbacks[uid];
    callback(data);
  };
  url += "? jsonp=" + encodeURIComponent("delicious_callbacks[" + uid + "]");
  var script = document.createElement('script')
  script.src = url
  document.body.appendChild(script)
};

jsonp("http://example.com/api".function(data) { // here we get the data });
Copy the code

The JSONP approach itself has some drawbacks, as it can obviously only be used for GET requests. In addition, the back-end application may have 4xx errors, 5XX errors, or other unexpected conditions during the processing process, resulting in the failure to return the correct js function call format string, so you also need to listen for the onError event of the script tag to handle possible unexpected conditions.

Cookies are shared across domains

Cookie as a solution for client storage, there are also the following methods for setting cookies on the client:

  1. Configure the server to return the set-cookie response header
  2. Setting document.cookie directly to what we need to store in the JavaScript code on the page does not overwrite the existing cookie, for example:
document.cookie="name=Jack; path=/"
document.cookie="age=25; path=/" // Cookie will store both name and age fields
Copy the code

Cookies have an expiration date, and if the expiration date is not explicitly set as in the code above, the corresponding cookie will also be cleared after the browser exits.

Generally, the browser will automatically send cookies matching the current domain name and path to the server when accessing the page. In general, Ajax requests we make on a page will not automatically send the Cookie associated with the current URL to the server along with the request. If we need to send cookies to the server on the request with the currently accessed URL, we can set the withCredentials attribute of the XMLHttpRequest object to true.

If you want to send cookies from the domain name of the current page to the server under another domain name, you need to configure the server to support CORS, and note that the access-Control-allow-Origin returned by the server cannot be set to “*”. At the same time, the server must return access-Control-allow-credentials: true; otherwise, the response on the server will still be blocked by the browser.

After this configuration, the browser sends Ajax requests with credentials (i.e. cookies) and matches the path property of the current page’s domain name with the requested URL (e.g., /test/example, /, /test/, /test/example Cookies under path will be sent along with the cookies of the server. This enables cross-domain sharing of cookies.

Cross-page communication

In addition to the need to communicate with servers from different sources, we also encounter cross-page communication issues, requiring access to information on other pages, or persisting data to be retrieved by other pages. Specific methods are as follows:

document.domain

In this way, the two sources across domains must meet certain conditions, that is, the domain names of the two sources must be the parent-child domain relationship or the same domain. Because the page sets the value of document.domain to be the current domain itself, or the parent domain, not some other unrelated domain name. Only if the document.domain of both pages is set to the same value can the embedded and loaded pages of iframe get information about each other’s pages (including DOM structures, window objects, etc.).

In practice, two problems need to be paid attention to:

  1. If two pages have the same source, they can communicate directly, but if they have the same domain name but different port or other circumstances, they still need to set the same document.domain, otherwise they will still be blocked by the browser. Specific reasons are also mentioned in MDN:

The browser saves the port number separately. Any assignment, including document.domain = document.domain, will result in the port number being overwritten to NULL. Therefore company.com:8080 cannot communicate with company.com only by setting document.domain = “company.com”. Assignment must be made on both of them to ensure that the port numbers are null.

  1. The embedded iframe needs to be loaded before it can communicate with its loaded child page, otherwise the value may still be undefined.

Window object name property

Browsers have the feature that pages loaded by the same TAB or iframe frame share the same window.name attribute value, meaning that the window.name attribute value is the same for all pages opened in the same TAB (regardless of whether they are homologous). Using this property, you can use this property as a medium for passing data between different pages.

If iframe+window.name is used to pass data between two sources that have no parent-child domain relationship at all (assuming source A wants to fetch data from source B), The iframe on source A needs to jump to A page of source A after loading the target page of source B (where source B sets the data to window.name). So that the embedded iframe page gets the data in the IFrame by both setting document.domain as source A (described above) and the pages in the iframe. Example code is as follows:

// www.a.com/getData.html
<script type="text/javascript">
function getData() {
  var frame = document.getElementsByTagName("iframe") [0];
  frame.onload = function () {
    var data = frame.contentWindow.name;
    // Get the data here
    alert(data);
  };
  frame.contentWindow.location = "./aaa.html";
  // after loading www.b.com/data.html, load any page under www.a.com/ to get the data
}
</script>
<iframe src="http://www.b.com/data.html" style="display: none;" onload="getData();"></iframe>
Copy the code

HTML5 cross-document message

HTML5 has introduced another way of communicating across pages, called cross-document messaging. Data transfer can also be completed between the main page and embedded iframe sub-pages (or pages opened by the current page). In addition, this method can also be used to complete data exchange between the current JavaScript engine thread and other worker threads. If you are communicating with a child page loaded through an iframe, you need to get the window object of the target page that received the data (specifically through the method mentioned earlier), and send data to the target page through the postMessage method of this object.

<! --send.html-->
<iframe src="./receiver.html" id="frame"></iframe>
<button id="send-btn">send message</button>
<script>
  var frame = document.getElementById('frame')
  document.getElementById('send-btn').addEventListener('click'.function() {
    frame.contentWindow.postMessage({
      name: 'Jack'
    }, 'http://localhost:8888') // The source of the page receiving the information
  })
</script>

<! --receiver.html-->
<script>
  window.addEventListener('message'.function(e) {
    // Verify the source of the message sender
    if(e.origin === 'http://localhost:8888') {
      console.log(e.data)
      e.source.postMessage(...) // Echo the message}})</script>
Copy the code

If it is necessary to communicate with the worker on the page, the postMessage method of the created worker instance is directly called. In the script executed by the worker instance, self or this is used to access the worker instance, and then the postMessage method is called to complete the communication.

localStorage

LocalStorage is a client storage solution introduced by HTML5. Content stored through localStorage will always be stored in the client, unless it is explicitly removed by calling the removeItem method, otherwise the content will remain forever. The introduction to localStorage on MDN also mentions a way to implement localStorage through cookies on browsers that do not support localStorage, By setting the cookie expiration time to a point far into the future, you can simulate the permanent nature of localStorage, and the corresponding cookie will be used when the simulated localStorage removes the stored content. Further, if you do not set an expiration time for cookies, you can also use it to simulate another client-side storage scheme in browsers called sessionStorage. Unlike cookies, localStorage provides a larger upper limit of storage capacity.

As mentioned earlier, the content stored in localStorage is managed by source, which means that pages with the same domain name and different ports cannot communicate with localStorage. Open multiple homologous pages in multiple tabs of the browser. The Window object in these pages can listen to storage events. When the content in localStorage is set in the pages of other tabs, the event will be triggered to notify.

Other cross-domain problems

Font file loading

Font files referenced in the CSS also have cross-domain loading problems. You need to set CORS to load font files in other domains. By default, defining a new font does not immediately download the corresponding font file, only when the element on the page uses the font.

Cross-domain scripting error handling

For cross-domain script execution errors loaded on the page, window.onerror bound on the page cannot obtain specific error information by default. In this case, crossorigin attribute should be used on the tag of loading cross-domain script, that is, CORS should be executed when cross-domain script is requested. The crossorigin property can be set to:

  1. Anonymous: Scripts are requested without credentials
  2. Use-credentials: The credentials are carried when scripts are requested

Any other value is considered an anonymous keyword. Setting the Crossorigin property means that the server also needs to be configured to support CORS. Cross-domain scripts cannot be downloaded if CORS is not configured correctly on the server.

Canvas drawn content is converted to a file object

Dynamically loaded images in a Canvas can be drawn directly to the canvas, but there are cross-domain issues when converting a canvas into a file object, including the “Tainted Canvases may not be exported” error. In this case, you need to set the crossOrigin property for dynamically loaded image objects and also configure the server to support CORS.

let img = new Image()
img.crossOrigin = 'anonymous'
img.src = "//localhost:8888/images/1751527990314_.pic.jpg"
img.onload = (a)= > {
  let canvas = document.getElementById('canvas')
  let ctx = canvas.getContext('2d')
  ctx.drawImage(img, 0.0)
  canvas.toBlob(blob= > console.log(blob), 'image/jpeg'.75.)}Copy the code

conclusion

This article mainly introduces the same origin policy in the browser and how to complete the client and server communication belonging to different sources and cross-page communication under the same origin policy constraints. In practice, these cross-domain methods also need to start from specific scenarios and adopt appropriate methods according to different communication requirements. Above, if there are omissions, but also hope to be corrected.