preface

Following up on the popular cross-source/domain solutions of the past, this article talks about cross-source solutions of modern standards (post-HTML5). The basic concepts are in the old chapters. For beginners, be sure to read the old chapters first. Matching demo case portal. My personal ability is limited, welcome criticism and correction.

PostMessage

The solution uses HTML5’s new window.postMessage interface, which is designed specifically for communication between different source pages and is a classic “subscription-notification” model.

The principle of

The principle of this scheme is very similar to the “sub-domain proxy” in previous chapters. In both cases, the main page uses non-homologous sub-pages in the iframe as the proxy to interact with the server to obtain data. The difference is that the “child domain agent” needs to modify document.domain so that the main page can obtain the document operation permissions of the child pages, while window.postMessage already provides the native way for the main page to communicate with the child pages. Therefore, the main page only needs to command the child page through window.postMessage, and then notify the main page with this command after the child page completes the request, so as to realize cross-source communication. In other words, the child page becomes a similar forwarding service. There is no need to modify document.domain, which means that the strict domain restrictions of “sub-domain proxy” are freed, and the third-party API can be applied more freely. Windows. PostMessage is one of the few browser APIs that is not subject to the same origin limit. To be precise, it does not have the right to call. Here’s an example:

// Assume that the page is subscribed within an iframe. Window.addEventListener ('message', event => {// Verify sender, if sender does not match, it can be ignored. if (event.origin ! == 'http://demo.com') return // This is the message sent. Const data = event.data // This is the window instance of the sender, and we can call the postMessage above to return the message. const source = event.source })
// Main page notification. The second parameter is the source of the receiver, and the page that receives the message needs to match the source exactly. (See previous article for the definition of "source") // Setting to * enables broadcasting, but is generally not recommended. iframe.contentWindow.postMessage('hello there! ', 'http://demo.com')

process

  1. The API domain deploys a proxy page that listens for Message events, including the ability to send Ajax and postMessage the response back to the main page.
  2. The main page also sets the message event listening, and carries out the content distribution;
  3. Create a new 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 is ready to useiframe.contentWindow.postMessageSend a request to the proxy page;
  5. The proxy page receives the request, and then initiates Ajax to the server API.
  6. The server processes and responds, and the proxy receives the response and passesevent.source.postMessagePass to the main page.

Error handling

  • The load event of the iframe can be used to check whether the proxy page is loaded (non-homologies require hack), so as to indirectly determine whether there is a network error, but the specific cause of the error is unknown, that is to say, the response status code of the server cannot be obtained.
  • Iframe error event is not valid in most browsers (default), sending Ajax is done in the iframe, if the error can only be forwarded to the home page through postMessage, so it is recommended not to handle the error in the iframe, should be handled by the home page.

Practical tips

  • The front end

    • Loading the proxy page takes time, so pay attention to the timing of the request. Do not request the proxy page before it is finished loading.
    • It is not necessary to load a new proxy page every time a request is made. It is highly recommended to keep only one proxy page and share multiple requests.
    • If you follow the advice in the previous article, you should also consider the case of the agent page loading failure to avoid a failure after the subsequent;
    • The proxy page can be preloaded to avoid increasing the time of the request.
    • Both the receiver and the sender should set and verify the PostMessage targetOrigin to ensure security;
    • There is no need to listen for the message event every time a request is made. You can set up a single event handler for content distribution at initialization time, store each request callback in an object, assign a unique ID, and call the callback by ID through the single event handler.
    • If you follow the advice in the previous section, the callback functions within the global object need to be cleaned up in time.
  • The service side

    • The domain of the proxy page must be consistent with the domain of the API;
    • Proxy pages generally do not need to be updated frequently and can be cached over time;
    • The proxy page should be minimal, and the result of an Ajax request should be postMessage to the main page, whether it succeeds or fails.

Refer to the previous article “Subdomain Agents” for the design idea of sharing iframes. The design idea of the front-end “Unified Event Processor” :

Function initMessageListener() {// Save the object of the callback object. Const CBStore = {} // Set listener, just one. Window.addEventListener ('message', function (event) {// Verify the sending domain. if (event.origin ! == targetOrigin) { return } // ... // Failed to run the branch. if (...) { cbStore[msgId].reject(new Error(...) ) return} // Run the branch successfully. cbStore[msgId].resolve(...) } finally {// Perform the cleanup. DELETE CBStore [msgId]}}) // There is a closure that can only be used to manipulate CBStore in certain ways. Return {// Sets the method for the callback object. Set: function (msgId, resolve, reject) {// The callback object contains two branch functions: success and failure. CbStore [msgId] = {resolve, reject}}, // Del: function (msgId) {DELETE CBStore [msgId]}}} // Set the callback object by calling its set method on each request. const messageListener = initMessageListener()

With the “Unified Event Handler” above, the msgId does not need to be passed to the server. It can be handled in the proxy page:

Window.addEventListener ('message', event => {// Verify the sending domain. if (event.origin ! == targetOrigin) {return} // This is the data for the postMessage on the main page. The msgId is related to the "Unified Event Handler", and the other parameters are related to Ajax, which can be passed as needed. Const {msgId, method, URL, data} = event.data // Send Ajax. xhr(...) .then(res => {// Add msgId to return data, leave the rest as is. res.response.data = { ... Res.response. data, msgId} // Send back to the main page. event.source.postMessage(res, targetOrigin) }) })

Please refer to the PostMessage section source code of the demo case for specific code.

conclusion

  • advantages

    • You can send any type of request;
    • Standard API specifications can be used;
    • Provides an experience indistinguishable from a normal Ajax request;
    • Easy and accurate error capture (except for iframe network errors);
    • No domain requirements and can be used with third-party APIs.
  • disadvantages

    • Iframe has a significant impact on browser performance;
    • In actual tests, the PostMessage interface has a small delay in forwarding;
    • Only works in modern browsers.

CORS (Cross-source Resource Sharing)

The full name of COS is Cross-Origin Resource Sharing, which is a standard cross-source solution (portal) developed by the W3C organization. It can also be said to be the official ultimate cross-source solution, which makes modern Web development a lot more convenient.

The principle of

In simple terms, CORS is a set of negotiation mechanism between the server and the browser. It is implemented through the header, the browser tells the server the origin and the methods it wants to allow. The server returns a “whitelist” (also a group of headers), and the browser decides whether to allow the request based on the whitelist. Can be applied with Ajax, Canvas and other cross-source situations. CORS can be divided into simple requests and complex requests. The main difference between them is whether PreFlight is required or not. The simple request needs to meet the following conditions (select the key points only) :

  • The method is one of the following

    • GET
    • POST
    • HEAD
  • Only the following headers are allowed

    • Accept
    • Accept-Language
    • Content-Language
    • Content-type (only three allowed)

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

Any request that does not meet the above criteria is considered a complex request. In practice, requests from forms are generally allowed. If you want to pass data in JSON format (i.e. Content-type: application/json), then it must be a complex request. Complex requests are pre-checked, that is, they check with the server, and if the “whitelist” returned meets the requirements, they make a formal request. Precheck request is a request with method as OPTION, which does not need to carry any business data and only sends CORS-related request header to the server as required. 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 source of the requesting page, which is automatically added by the browser. Manual setting is not allowed.
    • Access-Control-Request-Method: The Method that is expected to be allowed by the server, which is automatically added according to the need of the formal Request during the browser pre-check. Manual setting is not allowed.
    • Access-Control-Request-Headers: A Request header that is expected to be allowed by the server. This header is automatically added by the browser to precheck the Request according to the need of the official Request. Manual setting is not allowed.
  • CORS related response header (that is, “whitelist”)

    • Access-Control-Alone-Origin: The domain that allows Access to this resource. This is the response header that CORS is bound to return when turned onRequests from all domains are allowed if not is specifiedNeed to use the source as the basis of the cache judgment, so addVary: OriginTo avoid being confused by caching when the API returns different data to different source pages;
    • Access-Control-Expose-Headers: In the cross-domain case, the getResponseHeader() method of the XMLHttpRequest object can only get the most basic response Headers. If you want to get the outer Headers, you need to specify them.
    • Access-Control-Max-Age: The maximum period of time (in seconds) during which the browser will not need to precheck again and will send a formal request directly.
    • Access-Control-Allow-Credentials: Credentials are allowed to carry cookies. Default is false. When set to true, Access-Control-Allow-Origin is not allowed to set to *.
    • 11. access-control-allow-methods: A request method that can be used;
    • Access-Control-Alone-HEADERS: A header that allows the use of request Headers, often used to add custom Headers.

process

A simple request is identical to a normal Ajax process in that the browser sends the Origin request header and the server returns the Access-Control-Allow-Origin response header. Complex requests are described in more detail below. Suppose the web page source is http://demo.com, the server API source is red, the request method is POST, the data type is JSON, and the custom header token.

  1. When the browser checks that the API source to make the Ajax request is different from the current page source, it enters CORS negotiation;
  2. The data type is JSON, but also custom header, determine this is a complex request;
  3. Send a precheck OPTION request with the following header Settings for CORS:

    • Read the source write to the current pageOrigin: http://demo.com;
    • Because a POST request is required, thenAccess-Control-Request-Method: POST;
    • Because the required data type is JSON, that is, the default three content-types do not meet the requirements, and there is a custom header tokenAccess-Control-Request-Headers: content-type, token;
  4. The server receives a precheck request and responds. The header for CORS is set as follows:

    • Write to allowed fieldsAccess-Control-Allow-Origin: http://demo.com;
    • Write the allowed methodAccess-Control-Allow-Methods: POST, GET, OPTIONS;
    • Writes the allowed headerAccess-Control-Allow-Headers: Content-Type, token;
    • writeVary: Origin(As noted above, it does not belong to the CORS header, but must)
  5. The browser receives the response, validates the CORS response to the header, and sends the formal POST request immediately after the validation passes, simply addingOrigin: http://demo.com, the rest are consistent with the normal request;
  6. The server receives a formal request, processes it and responds by simply adding itAccess-Control-Allow-Origin: http://demo.comVary: Origin, the rest were consistent with the normal response;
  7. The browser receives the response, validates the CORS response header, and completes the request if validation passes.

Error handling

  • Server errors can be caught as normal requests to obtain the exact status code;
  • When cross-source related errors occur, they can be caught in the error event of the XMLHttpRequest object.
  • Cross-source related errors generally fall into two categories.

    • Error in intercepting response: for example, when a simple request receives the response data, but the response header validation fails. At this time, the browser will report an error even though the request has been completed according to the packet capture.
    • Error limiting requests: For example, when a complex request is requested, the response header verification returned by the precheck fails, the browser will not initiate a formal request, but will report an error directly. At this time, the packet capture will not see the formal request.

Practical tips

  • The front end

    • This scheme has very little impact on the front end, which is almost automatically completed by the browser, and can be initiated as a general request.
    • The error handling section mentions two types of cross-source related errors that you should be aware of when debugging.
  • The service side

    • It is not recommended to add CORS-related response header without brain, but should be added as needed to avoid header redundancy. By referring to the above process, it can be roughly divided into two groups.

      • Request header: Access-Control-Allow-Origin and Vary
      • Precheck request headers: Select CORS headers as needed, plus Vary.
    • Access-Control-Max-Age is an effective optimization that can reduce frequent precheck requests and save resources.
    • Setting Access-Control-Allow-Origin to * is not recommended unless it is a public third-party API.
    • For security reasons, it’s better to validate the Origin request header rather than ignore it, and return the 403 status code when it doesn’t meet the requirements.

Specific code please refer to the demonstration case CORS part source code.

conclusion

  • advantages

    • You can send any type of request;
    • Standard API specifications can be used;
    • Provides an experience indistinguishable from a normal Ajax request;
    • Error capture is convenient and accurate;
    • No domain requirements and can be used with third-party APIs.
  • disadvantages

    • Only works in modern browsers.