Cross-domain is a violation of the browser’s same-origin policy

In short, the most common request tools we use are built on the XMLHttpRequest object, which strictly adheres to the same origin policy, so when cross-domains are required, tools based on it do not work. We need some additional tools to solve this problem. This article focuses on the practice and summary of several known cross-domain solutions

A, the json

Jsonp is not the official cross-domain solution, but it is the first cross-domain solution, and regardless of how you use jSONP cross-domain, == server support for such cross-domain ==.

disadvantages

Since JSONP is a tag that uses script, img, and iframe without origin restrictions, it only supports GET requests

In addition, jSONP error handling mechanism is not perfect

advantages

The advantage of JSONP is that it is compatible with older browsers

Principle 1.

Jsonp uses script, img, iframe tags with no homologous constraints to send requests to the server, taking the returned data as arguments to the specified callback function. The function introduced by the script tag belongs to the window global, so you only need to specify the callback function in another script. In this way, the server data can be retrieved

This is the most basic jSONP cross-domain example

// test.html
<script>
  function test(data) {
    console.log(data);
    return data;
  }
</script>
<script
  type="text/javascript"
  src="Http://127.0.0.1:8090/v1/system/user/getTotal? callback=test"
></script>
Copy the code

The console print is as follows

2. The packaging

For vue to use JSONP, you can use third-party toolkits. Jsonp is recommended, if you like axios style, it will be very handy.

Download a.
cnpm i jsonp --save
Copy the code
B. to introduce

Create a new file for our packaged JSONP tool

// http.js
import jsonp from "jsonp"
Copy the code

According to the JSONP documentation, we know that jSONP requires three parameters

var options = {
    parma: "callback".timeout: 5000.prefix: "".name: "callbackFun"
  };
  // JSONP will concatenate the URL according to this configuration
  Url +? callback=callbackFun
  // If the url already has arguments: url+&callback=callbackFun
Copy the code

It then returns a Promise-style JSONp function that takes the URL as a parameter, options as an argument, and a callback function

// http.js
export function get(url) {
  var options = {
    parma: "callback".timeout: 5000.prefix: "".name: "callbackFun"
  };
    return new Promise((resolve, reject) = > {
    
        jsonp(url, options, (err, res) => {
            if (err) {
            // Errors can be handled here, such as introducing the toasts of component libraries such as element-UI, etc
            // Prompts the user for an error
            // You can import different pages according to different HTTP status codes
                reject(err);
              } else{ resolve(res); }}); }); }Copy the code

Sometimes, we also need to pass parameters to the server. In this case, we need to concatenate the parameter object into the URL. We can also encapsulate this

// Concatenate the Parma argument to the URL
function joinParma(url, parma) {
  let str = "";
  for (const key in parma) {
    str += key + "=" + parma[key] + "&";
  }
  str = str.substring(0, str.length - 1);
  str = url.indexOf("?") = =- 1 ? "?" + str : "&" + str;
  return url + str;
}
Copy the code

So the complete code looks like this

import jsonp from "jsonp"
import router from './router'

// Concatenate the Parma argument to the URL
function joinParma(url, parma) {
  let str = "";
  for (const key in parma) {
    str += key + "=" + parma[key] + "&";
  }
  str = str.substring(0, str.length - 1);
  str = url.indexOf("?") = =- 1 ? "?" + str : "&" + str;
  return url + str;
}

export function get(url, parma) {
  var options = {
    parma: "callback".timeout: 5000.prefix: "".name: "callbackFun"
  };
  // If there are parameters, concatenate parameters, otherwise no processing
  if (parma) {
    
    url = joinParma(url, parma);
  }
  return new Promise((resolve, reject) = > {

    jsonp(url, options, (err, res) => {
      if (err) {
        // Errors can be handled here, such as introducing the toasts of component libraries such as element-UI, etc
        // Prompts the user for an error
        // You can import different pages according to different HTTP status codes
         switch (err.response.status) {
            case 500:
               router.push({
                 path: "/ 404"
               });
               break;
             case 401:
               router.push({
                 path: "/ 401"
               });
               break;
        }
        reject(err);
      } else {
        // You can also make hints according to the message handling mechanism agreed with the back end
        // You can introduce the toast module of the UI library you are using to prompt the user
        if(res.data.code == 200) {console.log("Operation successful")}else if(res.data.code == 300) {console.log("No data available or query failed")}else{
            console.log("Operation failed") } resolve(res); }}); }); }Copy the code

Use 3.

By introducing packaged JSONP into our interface file, we can use it as we see fit

// api/getTotal.js
import { get } from '@/http/jsonp'

export function getTotal(data){
    return get("http://127.0.0.1:8090/v1/system/user/getTotal",data)
}
Copy the code

You then introduce interface functions into the components to be used

// list.vue
<script>
import { getTotal } from "@/api/getTotal"

export default {
    data(){
        total: 0.user: {id: 1.name: 'zs'
        }
    },
    created(){
        getTotal(this.user)
            .then((res) = >{
                if (res.data.code == 400) {this.total = res.data.total
                }
            })
            .catch(err= >{
                console.log(err)
            })
    }
}
</script>
Copy the code

Ii. CORS (Cross-domain Resource Sharing)

CORS still uses XMLHttpRequest to send requests, but with additional fields that tell the server to allow cross-domain, == it also requires server support ==

advantages

Multiple requests can be accessed

Perfect processing mechanism

Compliance with HTTP specifications, for complex requests, one more authentication, better security

disadvantages

Internet Explorer 10 or later is not supported

To illustrate, here is the server-side code for the test

const http = require("http");

const server = http
  .createServer((req, res) = > {
    res.setHeader("Access-Control-Allow-Origin"."*");
    res.writeHead(200, { "Content-Type": "text/plain" });
    res.end("Hello World\n");
  })
  .listen(8090);
console.log("Server running at http://127.0.0.1:8090/");
Copy the code

And a client code, which you can put in a pure HTML page and a vUE project if you are interested

let ajax;
  if (XMLHttpRequest) {
    ajax = new XMLHttpRequest();
  } else {
    ajax = new ActiveXObject();
  }
  ajax.onreadystatechange = function(res) {
    if (ajax.readyState == 4 && ajax.status == 200) {
      console.log(ajax.responseText); }}; ajax.open("GET"."http://127.0.0.1:8090".true);
  ajax.send();
Copy the code

Principle 1.

Cross-domain resource sharing (CORS) is a cross-domain solution officially recommended by HTTP. It is a mechanism that uses extra HTTP headers to tell browsers that Web applications running on one Origin (domain) are allowed to access specified resources from different source servers. This is the standard definition of MDN for CORS. Simply add an origin field to the server when sending the request. The server determines whether it is acceptable and returns an Access-Control-Allow-Origin field

For example, in the example above, whether it’s a VUE project or a separate HTML page, you can see the “Hello World” returned by the server on the console,

And the fields are as follows

// Request header // Vue project"Origin": "http://localhost:8080"/ / HTML page"Origin": null


// response header
"Access-Control-Allow-Origin":"*"
Copy the code

When the field value of the server is changed to http://localhost:8080, only the vUE project of http://localhost:8080 can cross domains, and the HTML page displays an error

Access to XMLHttpRequest at 'http://127.0.0.1:8090/' from origin 'null' 
has been blocked by CORS policy: The 'Access-Control-Allow-Origin' header 
has a value 'http://localhost:8080' that is not equal to the supplied origin.
Copy the code

Note that http://localhost:8080 and http://127.0.0.1:8080 are not the same for the server

CORS classifies requests into “simple requests” and “non-simple requests” based on whether or not they use fields and methods supported by default. The browser treats them differently depending on the type. The difference is that simple requests do not invoke validation, while non-simple requests invoke validation

For simple (CORS default) requests, it has no more than three methods and no more than the following header fields

(1) Request method: HEAD GET POST (2) HTTP header: Accept accept-language content-language last-event-id content-type: Application/X-www-form-urlencoded, multipart/form-data, text/plainCopy the code

== Note == Simple request:

  1. Cookies cannot be received or sent
  2. Custom headers cannot be set using setRequestHeader()
  3. Calling getAllResponseHeader() yields an empty string

But are we not surrounding a Request but a non-simple Request but a browser called a Preflrequest transparent server verification mechanism, using the OPTIONS method and the server verification, then the browser formally requested a server.

SetRequestHeader and getAllResponseHeader are not supported by CORS by default. For non-simple requests, you can use the OPTIONS method to override this default

// client ajax.open("GET"."http://127.0.0.1:8090".true);
ajax.setRequestHeader("X-PINGOTHER"."pingpong");
ajax.setRequestHeader("Content-Type"."application/xml"); ajax.send(); // The server needs to set the field res.setheader ("Access-Control-Allow-Headers"."Access-Control-Allow-Headers, Origin,Accept,X-PINGOTHER,X-Requested-With, Content-Type, Access-Control-Request-Method, Access-Control-Request-Headers"
    );
Copy the code

In this case, the network will have an additional request to verify

If the server does not set this field permission, it will report an error at the console

Request header field x-pingother is not allowed by Access-Control-Allow-Headers in preflight response.
Copy the code

If you need to send or receive cookies, you can set the withCredentials attribute to True, and the server needs to cooperate to accept requests with cookies. The response header returns the following fields and values

/ / the client
ajax.open("GET"."http://127.0.0.1:8090".true);
ajax.withCredentials = true;
ajax.send();

/ / the server
res.setHeader("Access-Control-Allow-Credentials".true);

// The following fields are added to the response packet
"Access-Control-Allow-Credentials": true
Copy the code

If the server sets the Credentials to false, the onError program is raised with status 0 and responseText as an empty string

res.setHeader("Access-Control-Allow-Credentials".false);
Copy the code

2. The packaging

Use AXIos in the VUE project to encapsulate the business

Download a.
cnpm i axios -s
Copy the code
B. to introduce

Create HTTP /request.js and store our wrapped AXIos file

// http/request.js
/ / introduction
import axios from "axios";

// Create an instance
const server = axios.create({
    // Set the address we visit to baseURL
    baseURL: "http://127.0.0.1:8090".// Set the timeout period
    timeout: 5000Headers: {"Content-Type":"text/plain"."Access-Control-Allow-Credentials": true}});Copy the code

In a project, there is often a need for uniform processing of requests, for example, putting cookies on each request, responding to all responses, importing different pages based on different error states, and using axios’s interceptor to achieve the desired effect

// Set interceptor
// Request interceptor
server.interceptors.request.use(
  config= > {
    // Each request is authenticated with a token to the server
    config.headers.token = localStorage.getItem("token");
    return config;
  },
  error => {
    console.log(error);
    Promise.reject(error); });// Response interceptor
server.interceptors.response.use(
  response= > {
    // You can also make hints according to the message handling mechanism agreed with the back end
    // You can introduce the toast module of the UI library you are using to prompt the user
    if(response.data.code == 200) {console.log("Operation successful")}else if(response.data.code == 300) {console.log("No data available or query failed")}else{
        console.log("Operation failed")}return response;
  },
  error => {
    switch (
      error.response.status
    ) {
    case 500:
       router.push({
         path: "/ 404"
       });
       break;
     case 401:
       router.push({
         path: "/ 401"
       });
       break; }});export default server;
Copy the code

Use 3.

Add the wrapped AXIos to our interface file and you can use it

// api/getTotal.js
import axios from '@/http/request'

export function getTotal(){
    return axios({
        url: "/system/user/getTotal".method: 'get'})}Copy the code

You then introduce interface functions into the components to be used

// list.vue
<script>
import { getTotal } from "@/api/getTotal"

export default {
    data(){
        total: 0,
    },
    created(){
        getTotal()
            .then((res) = >{
                if (res.data.code == 400) {this.total = res.data.total
                }
            })
            .catch(err= >{
                console.log(err)
            })
    }
}
</script>
Copy the code

Iii. Proxy (Server-side Proxy)

In the VUE project, the Webpack option devServer has a proxy attribute, which is typically used to send all requests with a specified string to the target target server. Looking at the documentation, devServer invokes the HTTP-proxy-Middleware plug-in to forward the request to the target server.

Here is an example from the official website

var express = require('express');
var proxy = require('http-proxy-middleware');

var app = express();

app.use(
  '/api',
  proxy({ target: 'http://www.example.org'.changeOrigin: true})); app.listen(3000);

// http://localhost:3000/api/foo/bar -> http://www.example.org/api/foo/bar
Copy the code

To illustrate how this works, I created two Node services myself, one emulated client and one emulated target server

Principle 1.

On the client side, create a service using HTTP and return the displayed page

const http = require("http");
const fs = require("fs");

const server = http.createServer();
server.listen(8090);
server.on("request", (req, res) => {
  res.writeHead(200, { "Content-Type": "text/html" });
  if (req.url == "/") {
    fs.readFile("./index.html", (err, data) => {
      if (err) {
        console.log(err);
      } else{ res.end(data); }}); }});console.log("Server running at http://127.0.0.1:8090/");
Copy the code

In addition, in the page, you can define or reference ajax already written, and send a request to the client server. When the client receives the request, it forwards it to the target server


      
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="Width = device - width, initial - scale = 1.0" />
    <meta http-equiv="X-UA-Compatible" content="ie=edge" />
    <title>hello world</title>
  </head>
  <body>
    <button onclick="openUrl()">Test the connection</button>
    <script>
      let ajax;
      if (XMLHttpRequest) {
        ajax = new XMLHttpRequest();
      } else {
        ajax = new ActiveXObject();
      }
      function openUrl() {
        ajax.open("get"."/api/index".false);
        ajax.send();
      }
    </script>
  </body>
</html>

Copy the code
// Client server
 if (req.url == "/") {
    fs.readFile("./index.html", (err, data) => {
      if (err) {
        console.log(err);
      } else{ res.end(data); }}); }else if (req.url.search("/api") != - 1) {
    http.get("http://127.0.0.1:8091" + req.url, response => {
      let data = "";
      response.on("data", chunk => {
        data += chunk;
      });
      response.on("end", () => {
        res.end(data);
      });
    });
    console.log(req.url, "Come in.");
  }
Copy the code

The target server is responsible for receiving different urls and responding to them separately

// Target server
const http = require("http");

const server = http.createServer();
server.listen(8091);
server.on("request", (req, res) => {
  res.writeHead(200, { "Content-Type": "application/json" });
  if (req.url == "/api/index") {
    // Here we send a JSON object to the client server
    let data = {
      id: 1.name: "cjr"
    };
    res.end(JSON.stringify(data)); }});console.log("Server running at http://127.0.0.1:8091/");
Copy the code

The browser console can now receive the JSON data sent by the target server

2. Encapsulation and use

In addition to setting the base path, we need to specify the cross-domain destination server address

devServer:{
    '/api':{
        target: "http://127.0.0.1:8091"// Remember to add HTTP changeOrigin:true// set this to allow cross-domain}}Copy the code

Again using AXIos, encapsulating and using the same as Cros, except baseURL is different and only the complete code is presented here

/ / introduction
import axios from "axios";

// Create an instance
const server = axios.create({
    // Set the address we visit to baseURL
    baseURL: "/v1".// Set the timeout period
    timeout: 5000
});

// Set interceptor
// Request interceptor
server.interceptors.request.use(
  config= > {
    // Each request is authenticated with a token to the server
    config.headers.token = localStorage.getItem("token");
    return config;
  },
  error => {
    console.log(error);
    Promise.reject(error); });// Response interceptor
server.interceptors.response.use(
  response= > {
    // You can also make hints according to the message handling mechanism agreed with the back end
    // You can introduce the toast module of the UI library you are using to prompt the user
    if(response.data.code == 200) {console.log("Operation successful")}else if(response.data.code == 300) {console.log("No data available or query failed")}else{
        console.log("Operation failed")}return response;
  },
  error => {
    switch (
      error.response.status
    ) {
    case 500:
       router.push({
         path: "/ 404"
       });
       break;
     case 401:
       router.push({
         path: "/ 401"
       });
       break; }});export default server;
Copy the code

More plans

In addition to the cross-domain approach mentioned earlier, you can also use a Nginx reverse proxy, which is very similar to a proxy in that it accesses the middle tier and the middle tier accesses the target server

There are also clever ways to cross domains, such as when you use iframe tags in your pages, hash location.hash, and cross domains and pass data. You can also use window.name or H5’s postMessage interface

These cross-domain solutions are unpopular in practice, and I have not practiced them, so it is briefly mentioned here

If there are any mistakes in the article, please point them out and I will correct them in time