yanyue404/blog

跨域请求数据

Opened this issue · 0 comments

前言

什么是跨域?

这就说到需要提到浏览器的同源策略,注意:是浏览器的。

那么什么是同源策略?

同源策略限制了从同一个源加载的文档或脚本如何与来自另一个源的资源进行交互。这是一个用于隔离潜在恶意文件的重要安全机制。如果是两个网页的协议,域名,端口一致,则这两个页面有相同的源。

那么不遵守同源策略则会涉及到跨域问题。

补充:非同源的限制:

(1) Cookie、LocalStorage 和 IndexDB 无法读取。

(2) DOM 无法获得。

(3) AJAX 请求不能发送。

1. jsonp 方法

JSONP(JSON with Padding)是数据格式 JSON 的一种“使用模式”,可以让网页从别的网域要数据。另一个解决这个问题的新方法是跨来源资源共享。

由于同源策略,一般来说位于server1.example.com的网页无法与 server2.example.com的服务器沟通,而 HTML 的 <script>元素是一个例外。利用 <script>元素的这个开放策略,网页可以得到从其他来源动态产生的 JSON 数据,而这种使用模式就是所谓的 JSONP。用 JSONP 抓到的数据并不是 JSON,而是任意的 JavaScript,用 JavaScript 解释器运行而不是用 JSON 解析器解析。

只是基于 get 请求的跨域,post 不支持
利用 script 的 src 属性天然可跨域的特性进行传递数据

操作步骤:

1.在全局准备一个函数,用来接收数据

function fnName() {}

2.在请求的时候将准备好的函数的名字传递给后端,callback 后面的名字

<script src = "http://www.example.com?callback=fnName">

3.后端返回函数的调用,并且将数据通过传递参数的形式传递过来

fnName({ name: "zhangsan", age: 18 });

4.前端得到,处理数据

function fnName(data) {
  console.log(data);
}

使用 jsonp

跨域的时候会报错,这里需要设置一个白名单,因为 jsonp 的跨域是敏感的,需要将 url 加入到可信任的白名单里面

自己封装 jsonp 请求方法

function myJsonp(url, data, callback) {
  var fnName = "myJsonp_" + Math.random().toString().replace(".", "");

  window[fnName] = callback;

  var querystring = "";

  for (var attr in data) {
    querystring += attr + "=" + data[attr] + "&";
  }

  var script = document.createElement("script");

  script.src = url + "?" + querystring + "callback=" + fnName;

  script.onload = function () {
    document.body.removeChild(script);
  };

  document.body.appendChild(script);
}

调用:

myJsonp(
  "https://api.douban.com/v2/movie/in_theaters",
  {
    start: 1,
    count: 10,
  },
  function (res) {
    console.log(res);
  }
);

2. postMessage

这种方式通常用于获取嵌入页面中的第三方页面数据。一个页面发送消息,另一个页面判断来源并接收消息。

// 发送消息端
window.parent.postMessage("message", "http://test.com");
// 接收消息端
var mc = new MessageChannel();
mc.addEventListener("message", (event) => {
  var origin = event.origin || event.originalEvent.origin;
  if (origin === "http://test.com") {
    console.log("验证通过");
  }
});

了解更多

3. 代理转发

日常开发中的 webpack proxy 就是通过代理的方式实现的跨域。

基本行为:使用本地开发服务器接收客户端发送的请求后转发给其他服务器,然后再将目标服务器的响应返回到客户端。

其目的是为了便于开发者在开发模式下解决跨域问题(浏览器安全策略限制)

想要实现代理首先需要一个中间服务器,webpack中提供服务器的工具为webpack-dev-server

webpack-dev-server

webpack-dev-serverwebpack 官方推出的一款开发工具,将自动编译和自动刷新浏览器等一系列对开发友好的功能全部集成在了一起

目的是为了提高开发者日常的开发效率,只适用在开发阶段

关于配置方面,在webpack配置对象属性中通过devServer属性提供,如下:

// ./webpack.config.js
const path = require("path");

module.exports = {
  // ...
  devServer: {
    contentBase: path.join(__dirname, "dist"),
    compress: true,
    port: 9000,
    proxy: {
      "/api": {
        target: "https://api.github.com",
      },
    },
    // ...
  },
};

devServetr里面proxy则是关于代理的配置,该属性为对象的形式,对象中每一个属性就是一个代理的规则匹配

属性的名称是需要被代理的请求路径前缀,一般为了辨别都会设置前缀为/api,值为对应的代理匹配规则,对应如下:

  • target:表示的是代理到的目标地址
  • pathRewrite:默认情况下,我们的 /api-hy 也会被写入到 URL 中,如果希望删除,可以使用 pathRewrite
  • secure:默认情况下不接收转发到 https 的服务器上,如果希望支持,可以设置为 false
  • changeOrigin:它表示是否更新代理后请求的 headers 中 host 地址

工作原理

proxy工作原理实质上是利用http-proxy-middleware 这个http代理中间件,实现请求转发给其他服务器

举个例子:

在开发阶段,本地地址为http://localhost:3000,该浏览器发送一个前缀带有/api标识的请求到服务端获取数据,但响应这个请求的服务器只是将请求转发到另一台服务器中

const express = require("express");
const proxy = require("http-proxy-middleware");

const 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

代理服务器

在开发阶段, webpack-dev-server 会启动一个本地开发服务器,所以我们的应用在开发阶段是独立运行在 localhost的一个端口上,而后端服务又是运行在另外一个地址上

所以在开发阶段中,由于浏览器同源策略的原因,当本地访问后端就会出现跨域请求的问题

通过设置webpack proxy实现代理请求后,相当于浏览器与服务端中添加一个代理者

当本地发送请求的时候,代理服务器响应该请求,并将请求转发到目标服务器,目标服务器响应数据后再将数据返回给代理服务器,最终再由代理服务器将数据响应给本地

在代理服务器传递数据给本地浏览器的过程中,两者同源,并不存在跨域行为,这时候浏览器就能正常接收数据

注意:服务器与服务器之间请求数据并不会存在跨域行为,跨域行为是浏览器安全策略限制

4. CORS 跨域

CORS 是一个 W3C 标准,全称是"跨域资源共享"(Cross-origin resource sharing)。出于安全原因,浏览器限制从脚本内发起的跨源 HTTP 请求。 例如,XMLHttpRequest 和 Fetch API 遵循同源策略。

而跨域资源共享它允许浏览器向跨源服务器,发出 XMLHttpRequest 请求,从而克服了 AJAX 只能同源使用的限制。而且比 JSONP 方法更为强大 ,支持所有类型的 HTTP 请求。

服务端设置 Access-Control-Allow-Origin 就可以开启 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

凡是不同时满足上面两个条件,就属于非简单请求。浏览器对这两种请求的处理,是不一样的

本文只讨论了简单请求的实现步骤

对于简单请求,浏览器发现这次跨源 AJAX 请求是简单请求,就自动在头信息之中,添加一个 Origin 字段。如下:

GET /cors HTTP/1.1
Origin: http://api.bob.com
Host: api.alice.com
Accept-Language: en-US
Connection: keep-alive
User-Agent: Mozilla/5.0...

上面的头信息中,Origin 字段用来说明,本次请求来自哪个源(协议 + 域名 + 端口)。服务器根据这个值,决定是否同意这次请求。如果在服务端不被允许,直接使用 ajax 访问,会有以下错误:

XMLHttpRequest cannot load http://www.server.com/server.PHP. No 'Access-Control-Allow-Origin' header is present on the requested resource.Origin 'http://www.client.com' is therefore not allowed access.

如果Origin指定的域名在许可范围内,服务器返回的响应,会多出几个头信息字段:

Access-Control-Allow-Origin: http://api.bob.com
Access-Control-Allow-Credentials: true
Access-Control-Expose-Headers: FooBar
Content-Type: text/html; charset=utf-8

上面的头信息中,有三个与 Cros 请求相关的字段,都以Access-Control-开头.

1.Access-Control-Allow-Origin

该字段是必须的。它的值要么是请求时 Origin 字段的值,要么是一个*,表示接受任意域名的请求。

2.Access-Control-Allow-Credentials

该字段可选。它的值是一个布尔值,表示是否允许发送 Cookie。默认情况下,Cookie 不包括在 CORS 请求之中。设为 true,即表示服务器明确许可,Cookie 可以包含在请求中,一起发给服务器。这个值也只能设为 true,如果服务器不要浏览器发送 Cookie,删除该字段即可。

3.Access-Control-Expose-Headers

该字段可选。CORS 请求时,XMLHttpRequest 对象的 getResponseHeader()方法只能拿到 6 个基本字段:Cache-Control、Content-Language、Content-Type、Expires、Last-Modified、Pragma。如果想拿到其他字段,就必须在 Access-Control-Expose-Headers 里面指定。上面的例子指定,getResponseHeader('FooBar')可以返回 FooBar 字段的值。

实现一个使用 CORS 的跨域:

本地设置目录情况如下

  1. 域名 api.study.com
    • 文件 03-cors.php
  2. 域名 study.com
    • 文件 cors.html
      • jquery.js

前端

    $.ajax({
      type: "post",
      url: "http://api.study.com/03-cors.php",
      data: "data",
      success: function (response) {
        alert(response)
      }
    });
    </script>

php

    <?php
    header("Content-Type:text/html;charset=UTF-8");
    // 指定允许其他域名访问
    header('Access-Control-Allow-Origin: http://m.juejin.com/');  // 第二个参数填写允许跨域的域名称,不建议直接写 "*"
    // 响应类型
    header('Access-Control-Allow-Methods:PUT,POST,GET,DELETE,OPTIONS');
    // 响应头设置
    header('Access-Control-Allow-Headers:x-requested-with,content-type');
    echo 'hellow world ,cors';
    ?>

总结:在实现 CORS 跨域获取数据的过程中关键在于后端的设置, 在被请求的 Response header 中加入参数设置项

参考