Руководство по междоменным решениям Vue

Vue.js
Руководство по междоменным решениям Vue

Междоменный означает, что он нарушает браузерТа же политика происхожденияпределы

Проще говоря, инструменты запросов, которые мы обычно используем, построены на основе объекта XMLHttpRequest, который строго придерживается политики одного и того же источника, поэтому, когда требуется кросс-доменность, инструменты на ее основе не могут работать, и нам нужны какие-то дополнительные средства для решить эту проблему В этом документе в основном рассматриваются и обобщаются несколько известных междоменных решений.

1. jsonp

Хотя jsonp не является официальным кросс-доменным решением, это самое раннее междоменное решение.Кроме того, независимо от того, как вы используете кросс-доменный jsonp, == сервер поддерживает этот вид междоменного доступа, это обязательное условие==.

недостаток

Поскольку тег jsonp использует сценарии, img и iframe без ограничений по происхождению, он поддерживает только запросы на получение.

Кроме того, механизм обработки ошибок jsonp не идеален.

преимущество

Преимущество jsonp в том, что он совместим с браузерами более ранних версий.

1. Принцип

jsonp использует теги script, img и iframe без ограничений гомологии, отправляет запрос на сервер и использует возвращенные данные в качестве параметра указанной функции обратного вызова.Функция, представленная тегом script, принадлежит глобальному окну, поэтому вы только нужно добавить его в другой В скрипте указана функция обратного вызова, чтобы можно было получить данные сервера

Это самый простой пример междоменного jsonp.

// 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>

Консоль печатает следующим образом

На этом этапе посмотрите на предварительный просмотр в новой работе, и он возвращает функцию.Если функция обратного вызова существует, она будет выполнена.

Вышесказанное только для того, чтобы поговорить о его принципе.Далее, давайте перейдем к сути, как работает vue?

2. Пакет

Чтобы использовать jsonp для vue, вы можете использовать сторонний инструментарий, рекомендуется использоватьjsonp, если вам нравится стиль axios, он определенно будет удобен в использовании.

А. Скачать
cnpm i jsonp --save
б) представить

Создайте новый файл, чтобы поместить наш упакованный инструмент jsonp.

// http.js
import jsonp from "jsonp"

Согласно документации jsonp, мы знаем, что jsonp принимает три параметра.

Кроме того, вам необходимо переопределить объект конфигурации

var options = {
    parma: "callback",
    timeout: 5000,
    prefix: "",
    name: "callbackFun"
  };
  // jsonp会根据这个配置拼接url
  // 例如上面的配合拼接后是:url+?callback=callbackFun
  // 假如url中已经有参数了:url+&callback=callbackFun

Затем верните функцию jsonp в стиле Promise, возьмите URL в качестве формального параметра, параметры в качестве фактического параметра и добавьте функцию обратного вызова.

// 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) {
            // 可以在这里对错误进行处理,比如引入element-ui等组件库的toast等
            // 将错误提示给用户
            // 也可以根据不同http状态码,导入不同的页面
                reject(err);
              } else {
                resolve(res);
            }
        });
    });
}

Иногда нам также нужно передать параметры на сервер. В этот раз нам нужно вставить объект параметра в URL-адрес. Мы также можем инкапсулировать это.

// 将Parma参数拼接到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;
}

Итак, полный код выглядит так

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

// 将Parma参数拼接到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 (parma) {
    
    url = joinParma(url, parma);
  }
  return new Promise((resolve, reject) => {

    jsonp(url, options, (err, res) => {
      if (err) {
        // 可以在这里对错误进行处理,比如引入element-ui等组件库的toast等
        // 将错误提示给用户
        // 也可以根据不同http状态码,导入不同的页面
         switch (err.response.status) {
            case 500:
               router.push({
                 path: "/404"
               });
               break;
             case 401:
               router.push({
                 path: "/401"
               });
               break;
        }
        reject(err);
      } else {
        // 你也可以根据与后端约定的消息处理机制,作出提示
        // 你可以引入你使用的UI库的toast模块提示给用户
        if(res.data.code == 200){
            console.log("操作成功")
        }else if(res.data.code == 300){
            console.log("没有这条数据或者查询失败")
        }else{
            console.log("操作失败")
        }
        resolve(res);
      }
    });
  });
}

3. Используйте

Вставьте упакованный jsonp в наш файл интерфейса, вы можете использовать его в соответствии со своей ситуацией.

// 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)
}

Затем введите функцию интерфейса в компонент, который будет использоваться.

// 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>

2. CORS (обмен ресурсами между источниками)

CORS по-прежнему использует XMLHttpRequest для отправки запросов, но есть больше полей, чтобы сообщить серверу о разрешении междоменного доступа Аналогично, == ему также нужна поддержка сервера ==

преимущество

Доступ к нескольким методам запроса

Идеальный механизм обработки

Соответствует спецификации http, для сложных запросов требуется еще одна проверка для большей безопасности.

недостаток

Браузеры ниже IE10 не поддерживаются

Для иллюстрации вот код тестового сервера

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/");

И клиентский код, если вам интересно, вы можете разместить его на чистой html-странице и в проекте vue.

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();

1. Принцип

Cross-Origin Resource Sharing (CORS) – это междоменное решение, официально рекомендованное http. Это механизм, который использует дополнительные заголовки HTTP, чтобы сообщить браузерам, что веб-приложениям, работающим в одном источнике (домене), разрешен доступ из разных источников. указанный ресурс на сервере, который является стандартным определением CORS MDN. Проще говоря, поле с именем origin добавляется на сервер при отправке запроса. Служба оценивает, что это допустимый диапазон, и возвращает Access-Control -Allow - Поле происхождения

Например, в приведенном выше примере, будь то проект vue или отдельная html-страница, вы можете увидеть «hello world», возвращаемый сервером в консоли,

и поля следующие

// request header
// vue项目
"Origin": "http://localhost:8080"
// html页面
"Origin": null


// response header
"Access-Control-Allow-Origin":"*"

Когда значение поля сервера изменяется на http://localhost:8080, обнаруживается, что только vue-проект http://localhost:8080 может нормально пересекать домены, а html-страница сообщает об ошибке.

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.

==Обратите внимание, что http://localhost:8080 и http://127.0.0.1:8080 отличаются на стороне сервера==

CORS делит запросы на «простые запросы» и «непростые запросы» в зависимости от того, используются ли поля и методы, поддерживаемые по умолчанию. Браузеры будут обрабатывать разные типы запросов. Разница между ними заключается в том, что простые запросы не вызывают проверку механизм, не простой Запрос вызовет механизм аутентификации

Для простых запросов (по умолчанию CORS) его методы запроса не превышают следующих трех, а информация заголовка не превышает следующих полей.

(1) 请求方法:
     HEAD
     GET
     POST
(2)HTTP的头信息:
     Accept
     Accept-Language
     Content-Language
     Last-Event-ID
     Content-Type:
         application/x-www-form-urlencoded、 multipart/form-data、text/plain

== ПРИМЕЧАНИЕ == Простой запрос:

  1. Не удается получить или отправить файлы cookie
  2. Вы не можете установить собственные заголовки с помощью setRequestHeader()
  3. Пустая строка, полученная вызовом getAllResponseHeader()

Если нам нужно использовать методы или поля, отличные от перечисленных выше, это непростой запрос.Для этого типа запроса браузер сначала вызовет механизм аутентификации прозрачного сервера Preflighted Request, использует метод OPTIONS и аутентификацию сервера. , и пройдите аутентификацию. Браузер официально запросит сервер.

Как упоминалось ранее, CORS по умолчанию (простой запрос) не поддерживает вызов setRequestHeader и getAllResponseHeader, В непростых запросах вы можете использовать метод OPTIONS, чтобы преодолеть это ограничение по умолчанию.

// 客户端
ajax.open("GET", "http://127.0.0.1:8090", true);
ajax.setRequestHeader("X-PINGOTHER", "pingpong");
ajax.setRequestHeader("Content-Type", "application/xml");
ajax.send();

// 服务端需要设置允许改变的字段
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"
    );

В это время в сети будет дополнительный запрос на верификацию.

Если сервер не устанавливает разрешение для этого поля, он сообщит об ошибке в консоли

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

Если вам нужно отправлять или принимать файлы cookie, вы можете установить для атрибута withCredentials экземпляра значение true. В то же время вам также необходимо сотрудничество сервера, чтобы разрешить получение запросов с файлами cookie. Информация заголовка ответа будет возвращать следующие поля. и ценности.

// 客户端
ajax.open("GET", "http://127.0.0.1:8090", true);
ajax.withCredentials = true;
ajax.send();

// 服务端
res.setHeader("Access-Control-Allow-Credentials", true);

// 此时响应报文中多了下面的字段
"Access-Control-Allow-Credentials": true

Если сервер устанавливает для учетных данных значение false, клиент запускает программу onerror, а статус равен 0, а responseText представляет собой пустую строку.

res.setHeader("Access-Control-Allow-Credentials", false);

2. Пакет

Используйте axios в проекте vue, чтобы инкапсулировать его бизнес

А. Скачать
cnpm i axios -s
б) представить

Создайте новый http/request.js для хранения инкапсулированного файла axios.

// http/request.js
// 引入
import axios from "axios";

// 创建一个实例
const server = axios.create({
    // 将我们访问的地址设为baseURL
    baseURL: "http://127.0.0.1:8090",
    // 设置超时时间
    timeout: 5000,
    headers:{
        "Content-Type":"text/plain",
        "Access-Control-Allow-Credentials": true
    }
});

В проекте обычно нужно единообразно обрабатывать запросы, например, приносить куки на каждый запрос, отвечать на все ответы, импортировать разные страницы по разным состояниям ошибок, а также заимствовать перехватчик axios, мы можем делаем что хотим Эффект

// 设置拦截器
// 请求拦截器
server.interceptors.request.use(
  config => {
    // 每个请求都带token与服务器进行身份匹配
    config.headers.token = localStorage.getItem("token");
    return config;
  },
  error => {
    console.log(error);
    Promise.reject(error);
  }
);
// 响应拦截器
server.interceptors.response.use(
  response => {
    // 你也可以根据与后端约定的消息处理机制,作出提示
    // 你可以引入你使用的UI库的toast模块提示给用户
    if(response.data.code == 200){
        console.log("操作成功")
    }else if(response.data.code == 300){
        console.log("没有这条数据或者查询失败")
    }else{
        console.log("操作失败")
    }
    return response;
  },
  error => {
    switch (
      error.response.status
    ) {
    case 500:
       router.push({
         path: "/404"
       });
       break;
     case 401:
       router.push({
         path: "/401"
       });
       break;
    }
  }
);

export default server;

3. Используйте

Внесите упакованный axios в наш файл интерфейса, и вы можете использовать его.

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

export function getTotal(){
    return axios({
        url: "/system/user/getTotal",
        method: 'get'
    })
}

Затем введите функцию интерфейса в компонент, который будет использоваться.

// 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>

Три, прокси (серверный прокси)

В проекте vue опция webpack devServer имеет атрибут прокси, который обычно используется для отправки всех запросов с указанной строкой на целевой целевой сервер. Проверьте документацию и найдите, что devServer вызываетhttp-proxy-middlewareПлагин перенаправляет запрос на целевой сервер.

Ниже приведен пример, приведенный на официальном сайте.

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

Чтобы проиллюстрировать этот принцип, я сам создал две службы узлов: одну для имитации клиента и одну для имитации целевого сервера.

1. Принцип

На стороне клиента создайте службу с http и верните отображаемую страницу

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/");

Кроме того, на странице вы можете определить или сослаться на уже написанный ajax и отправить запрос на клиентский сервер.Когда клиент получает этот запрос, он перенаправляет его на целевой сервер.Реализация выглядит следующим образом

<!DOCTYPE html>
<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()">测试连接</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>

// 客户端服务器
 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, "进来了");
  }

Целевой сервер отвечает за получение разных URL-адресов и отдельный ответ.

// 目标服务器
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") {
    // 这里给客户端服务器发送一个json对象
    let data = {
      id: 1,
      name: "cjr"
    };
    res.end(JSON.stringify(data));
  }
});
console.log("Server running at http://127.0.0.1:8091/");

В это время консоль браузера может получать данные json, отправленные целевым сервером.

2. Упаковка и использование

В дополнение к установке базового пути нам необходимо указать междоменный адрес целевого сервера.

devServer:{
    '/api':{
        target: "http://127.0.0.1:8091", // 这里一定要记得添加http
        changeOrigin: true  // 这里设置允许跨域
    }
}

Также при использовании axios инкапсуляция и использование такие же, как и у cros, но baseURL отличается, здесь приведен только полный код

// 引入
import axios from "axios";

// 创建一个实例
const server = axios.create({
    // 将我们访问的地址设为baseURL
    baseURL: "/v1",
    // 设置超时时间
    timeout: 5000
});

// 设置拦截器
// 请求拦截器
server.interceptors.request.use(
  config => {
    // 每个请求都带token与服务器进行身份匹配
    config.headers.token = localStorage.getItem("token");
    return config;
  },
  error => {
    console.log(error);
    Promise.reject(error);
  }
);
// 响应拦截器
server.interceptors.response.use(
  response => {
    // 你也可以根据与后端约定的消息处理机制,作出提示
    // 你可以引入你使用的UI库的toast模块提示给用户
    if(response.data.code == 200){
        console.log("操作成功")
    }else if(response.data.code == 300){
        console.log("没有这条数据或者查询失败")
    }else{
        console.log("操作失败")
    }
    return response;
  },
  error => {
    switch (
      error.response.status
    ) {
    case 500:
       router.push({
         path: "/404"
       });
       break;
     case 401:
       router.push({
         path: "/401"
       });
       break;
    }
  }
);

export default server;

4. Больше планов

В дополнение к упомянутому выше междоменному решению, вы также можете использовать обратный прокси-сервер nginx, который очень похож на прокси-сервер тем, что он обращается к среднему слою, а средний уровень обращается к целевому серверу.

Кроме того, есть много хитрых способов междоменного доступа, например, вы можете использовать тег iframe на странице, использовать хеш-метод location.hash для междоменного доступа и передачи данных, вы также можете использовать имя окна или H5. интерфейс postMessage

Эти междоменные решения относительно непопулярны в реальном использовании, и я не практиковал их, поэтому кратко упомяну

Если в статье есть ошибки, пожалуйста, укажите, и я их вовремя исправлю.