2
头图

Preface

"Re-talking about coquettish cross-origin/domain solutions (old chapter)" , this article talks about modern standard (after HTML5) cross-origin solutions.
The basic concepts are all in the past articles, so beginners must read the past articles first.
The supporting demo case portal .
I have limited personal abilities, and I welcome criticism and corrections.

PostMessage

This solution uses HTML5's new window.postMessage interface. This method is specifically designed for communication between different source pages and is a classic "subscription-notification" model.

principle

The principle of this solution is very similar to the "subdomain proxy" in the previous article. The main page uses the non-homogenous subpage in the iframe as a proxy to interact with the server to obtain data. The difference is that the "subdomain proxy" needs to modify document.domain enable the main page to obtain the document operation authority of the window.postMessage , and 060b780ddec71f has provided the communication method between the main page and the subpage, so only the main page needs to window.postMessage Command, after the subpage request is completed, the main page can be notified to achieve cross-origin communication. In other words, the subpage becomes a similar forwarding service.
No need to modify document.domain also means getting rid of the strict domain restrictions of "subdomain proxy", and can be more freely applied to third-party APIs.
window.postMessage is a rare browser API that is not restricted by the same origin. To be precise, there is no restriction on calling permissions. It still has strict restrictions on the sending and receiving targets, which is also a manifestation of its security. for example:

// 假设在 iframe 内页面进行订阅。
window.addEventListener('message', event => {
  // 验证发送者,发送者不符合是可以不理会的。
  if (event.origin !== 'http://demo.com') return
  // 这就是发送过来的信息。
  const data = event.data
  // 这是发送者的 window 实例,可以调用上面的 postMessage 回传信息。
  const source = event.source
})
// 主页面通知。
// 第二个参数是接收者的源,需要源完全匹配的页面才会接收到信息。(“源”的定义见昔日篇)
// 设置为 * 可以实现广播,不过一般不推荐。
iframe.contentWindow.postMessage('hello there!', 'http://demo.com')

Process

  1. Deploy a proxy page in the domain where the API is located, set up the monitoring of message events, including the function of sending Ajax and responding to postMessage back to the main page;
  2. The main page also sets up monitoring of message events and distributes content;
  3. Create an iframe tag on the main page to link to the proxy page;
  4. When the proxy page in the iframe is ready, the main page can use iframe.contentWindow.postMessage send a request to the proxy page;
  5. After receiving the request, the proxy page initiates Ajax to the server API;
  6. The server processes and responds, and the proxy receives the response and passes it to the main page event.source.postMessage

PostMessage

Error handling

  • Through the load event of the iframe, you can check whether the proxy page is loaded (the non-same source requires a hack method) to indirectly determine whether there is a network error, but the specific cause of the error is not known, that is, the server's response status code cannot be obtained ;
  • The error event of the iframe is invalid in most browsers (default). Sending Ajax is done in the iframe. If an error occurs, it can only be forwarded to the main page via postMessage. Therefore, it is recommended not to handle the error in the iframe, but to the home page.面处理。 Surface treatment.

Practice tips

  • front end

    • It takes time to load the proxy page, so pay attention to the timing of initiating the request to avoid requesting when the proxy page has not been loaded;
    • It is not necessary to load a new proxy page for every request. It is strongly recommended to keep only one and share multiple requests;
    • If you follow the recommendations in the previous article, you also need to consider the failure of the proxy page to load, to avoid failure after one failure;
    • You can use preloading to load the proxy page in advance to avoid increasing the request time;
    • Both the receiver and the sender should set and verify the targetOrigin of postMessage to ensure safety;
    • It is not necessary to listen to the message event for each request. You can set up a unified event handler for content distribution during initialization, use an object to save the callback of each request, assign a unique id, and press the id through the unified event handler Call callback
    • If you follow the previous advice, the callback function in the global object needs to be cleaned up in time.
  • Server

    • The domain of the proxy page must be consistent with the domain of the API;
    • The proxy page generally does not need to be updated frequently and can be cached for a long time;
    • The proxy page should be as concise as possible, and the result of the Ajax request should be postMessage to the main page regardless of whether it succeeds or fails.

For the design ideas of sharing iframes, please refer to "Subdomain Proxy" in the previous article.
The design idea of the front-end "unified event processor":

function initMessageListener() {
  // 保存回调对象的对象。
  const cbStore = {}
  // 设置监听,只需一个。
  window.addEventListener('message', function (event) {
    // 验证发送域。
    if (event.origin !== targetOrigin) {
      return
    }
    // ...
    try {
      // 运行失败分支。
      if (...) {
        cbStore[msgId].reject(new Error(...))
        return
      }
      // 运行成功分支。
      cbStore[msgId].resolve(...)
    } finally {
      // 执行清理。
      delete cbStore[msgId]
    }
  })
  // 这里形成了一个闭包,只能用特定方法操作 cbStore。
  return {
    // 设置回调对象的方法。
    set: function (msgId, resolve, reject) {
      // 回调对象包含成功和失败两个分支函数。
      cbStore[msgId] = {
        resolve,
        reject
      }
    },
    // 删除回调对象的方法。
    del: function (msgId) {
      delete cbStore[msgId]
    }
  }
}
// 初始化,每次请求都调用其 set 方法设置回调对象。
const messageListener = initMessageListener()

With the "Unified Event Handler" above, msgId does not actually need to be passed to the server, and it can be processed on the proxy page:

window.addEventListener('message', event => {
  // 验证发送域。
  if (event.origin !== targetOrigin) {
    return
  }
  // 这是主页面 postMessage 的数据。
  // 其中 msgId 与“统一事件处理器”有关,其他参数与 Ajax 有关,按实际需要传递即可。
  const { msgId, method, url, data } = event.data
  // 发送 Ajax。
  xhr(...).then(res => {
    // 将 msgId 加入回传数据,其余保留原样。
    res.response.data = {
      ...res.response.data,
      msgId
    }
    // 回传给主页面。
    event.source.postMessage(res, targetOrigin)
  })
})

Please refer to the specific presentation case PostMessage part source.

to sum up

  • advantage

    • Any type of request can be sent;
    • Standard API specifications can be used;
    • Can provide an experience that is indistinguishable from normal Ajax requests;
    • Convenient and accurate error capture (except for iframe network errors);
    • No domain requirements, can be used for third-party APIs.
  • Disadvantage

    • iframe has a greater impact on browser performance;
    • In the actual test, the forwarding of the PostMessage interface has a small delay;
    • Can only be used in modern browsers.

CORS (Cross-Origin Resource Sharing)

The full name of CORS is Cross-origin resource sharing. It is a standard cross-origin solution ( portal ) formulated by the W3C organization. It can also be said to be the ultimate cross-origin official solution. It makes modern web development a lot easier.

principle

Simply put, CORS is a negotiation mechanism between the server and the browser. It is implemented through the message header. The browser informs the server of the origin and the method that it wants to allow. The server returns a "whitelist" (also a group of messages). Header), the browser judges whether to allow this request based on the "white list", and cross-origin conditions such as Ajax, canvas, etc. can be applied.
CORS is divided into simple request (simple) and complex request (complex). The main difference between them is that preflight is required.
Simple requests need to meet the following conditions (only the key points):

  • Method (method) is one of the following

    • GET
    • POST
    • HEAD
  • Only the following headers are allowed to be set

    • Accept
    • Accept-Language
    • Content-Language
    • Content-Type (only three are allowed)

      • text/plain
      • multipart/form-data
      • application/x-www-form-urlencoded
    • DPR
    • Downlink
    • Save-Data
    • Viewport-Width
    • Width

Those that do not meet the above conditions will be judged as complex requests. In terms of actual use, the requests issued by the form are basically allowed. If you want to use the json format to transfer data (that is, Content-Type: application/json ), it must be a complex request.
For complex requests, a pre-check request will be sent first, that is, to ask the server first, if the returned "white list" meets the requirements, then a formal request will be initiated.
The preflight request is a request whose method is OPTION. It does not need to carry any business data. It only sends CORS-related request headers to the server as needed. The server does not need to respond to any business data, and only returns the "whitelist" "To complete the negotiation.

  • CORS related request header

    • Origin: The origin of the request page, which is automatically added by the browser, and manual setting is not allowed;
    • Access-Control-Request-Method: It is hoped that the method allowed by the server will be automatically added according to the requirements of the formal request during the browser pre-check, and manual setting is not allowed;
    • Access-Control-Request-Headers: It is hoped that the request headers allowed by the server will be automatically added during the browser pre-check according to the requirements of the formal request. Manual settings are not allowed.
  • CORS-related response headers (ie "whitelist")

    • Access-Control-Allow-Origin: The domain that allows access to the resource. This is the response header that must be returned when CORS is turned on. Fill in allow requests from all domains. If you specify a , you need to change The source is used as the basis for caching judgment, so Vary: Origin is added to avoid being confused by the cache when the API returns different data to different source pages;
    • Access-Control-Expose-Headers: In the case of cross-domain, the getResponseHeader() method of the XMLHttpRequest object can only get some of the most basic response headers. If you want to get the outer header, you need to specify it;
    • Access-Control-Max-Age: The maximum validity period (seconds) of this pre-check. During this period of time, the browser will not need to pre-check again, but send a formal request directly;
    • Access-Control-Allow-Credentials: Whether to allow cookies, the default is false, when set to true, Access-Control-Allow-Origin is not allowed to be set to *;
    • Access-Control-Allow-Methods: Allowed request methods;
    • Access-Control-Allow-Headers: Allowed request headers, often used to add custom headers.

Process

The simple request is exactly the same as the general Ajax process, only the browser sends the Origin request header, and the server returns the Access-Control-Allow-Origin response header.
Let's talk about complex requests in detail below.
Assuming that the web page source is http://demo.com , the server API source is http://api.demo.com , the request method is POST, the data type is json, and the custom header token is used.

  1. The browser checks that the API source that initiates the Ajax request is different from the source of the current page, it enters the CORS negotiation;
  2. The data type is json, but you have to customize the header to determine that this is a complex request;
  3. Send a pre-check OPTION request, and the headers of CORS are set as follows:

    • Read the source of the current page and write Origin: http://demo.com ;
    • Since a POST request is required, Access-Control-Request-Method: POST ;
    • Since the required data type is json, that is, the default three content-types do not meet the requirements, and the custom message header token is Access-Control-Request-Headers: content-type, token ;
  4. The server responds after receiving the preflight request, and the headers of CORS are set as follows:

    • Write allowed domain Access-Control-Allow-Origin: http://demo.com ;
    • Write allowed method Access-Control-Allow-Methods: POST, GET, OPTIONS ;
    • Write the allowed header Access-Control-Allow-Headers: Content-Type, token ;
    • Write Vary: Origin (as explained above, it does not belong to the CORS header, but it must)
  5. The browser receives the response, verifies the CORS response header, and then sends the formal POST request after the verification is passed, only Origin: http://demo.com needs to be added, and the rest are the same as the normal request;
  6. The server receives the formal request and responds after processing, only adding Access-Control-Allow-Origin: http://demo.com and Vary: Origin , and the rest are the same as the normal response;
  7. The browser receives the response, verifies the CORS response header, and completes the request if the verification passes.

CORS

Error handling

  • Server errors can be captured like normal requests to obtain accurate status codes;
  • When a cross-source related error occurs, it can be captured in the error event of the XMLHttpRequest object;
  • Cross-source related errors are generally divided into two categories.

    • Intercepting response errors: For example, when a simple request is received, the response data is received, but the response header verification fails. At this time, although the request has been completed from the packet capture, the browser will still report an error;
    • Limit request errors: For example, when the request is complex, if the response header verification returned by the pre-check fails, the browser will not initiate a formal request, but directly report an error. At this time, the formal request cannot be seen in the packet capture.

Practice tips

  • front end

    • The impact of this solution on the front end is very small, it is almost done automatically by the browser, and it can be initiated like a normal request;
    • There are two types of cross-source related errors mentioned in the error handling section, which are the points that need to be paid attention to when debugging.
  • Server

    • It is not recommended to add CORS-related response headers without thinking. Add them as needed to avoid header redundancy. Refer to the above process, it can be roughly divided into two groups.

      • Simple request header: Access-Control-Allow-Origin and Vary can be used;
      • Preflight request header: Select the CORS header as needed, plus Vary.
    • Access-Control-Max-Age is an effective optimization method, which can reduce frequent pre-check requests and save resources.
    • Unless it is a public third-party API, it is not recommended to set Access-Control-Allow-Origin to *.
    • For security, it is better to verify the Origin request header instead of ignoring it. When the requirements are not met, a 403 status code can be returned.

For the specific code, please refer to the demo case CORS part of the source code.

to sum up

  • advantage

    • Any type of request can be sent;
    • Standard API specifications can be used;
    • Can provide an experience that is indistinguishable from normal Ajax requests;
    • Convenient and accurate error capture;
    • No domain requirements, can be used for third-party APIs.
  • Disadvantage

    • Can only be used in modern browsers.

calimanco
1.4k 声望766 粉丝

老朽对真理的追求从北爱尔兰到契丹无人不知无人不晓。