-.- -.-
首页
  • 分类
  • 标签
  • 归档
  • Lodash 源码分析 (opens new window)
Mozilla (opens new window)
GitHub (opens new window)

江月何年初照人

首页
  • 分类
  • 标签
  • 归档
  • Lodash 源码分析 (opens new window)
Mozilla (opens new window)
GitHub (opens new window)
  • 算法

  • JS相关内容

    • 从深拷贝看JS中的循环引用
    • 0.1+0.2 为什么不等于 0.3
    • JS中的位运算
    • 稀疏数组与稠密数组
    • this到底指向谁
    • JS中的比较规范
    • 连续赋值
    • 跨域及解决方案
      • 什么是跨域
        • 同源策略会限制哪些内容?
        • 在 DOM 元素中存在以下标签,是允许跨域访问资源的
        • 针对于 Ajax 请求的跨域问题
      • 跨域解决方案
        • jsonp
        • CORS
        • postMessage
        • window.name + iframe
        • location.hash + iframe
        • document.domain + iframe
        • WebSocket
        • nginx 反向代理
        • node 中间件实现跨域
      • 最后
    • 节流和防抖
    • 从输入 URL 到页面展示,这中间发生了什么?
  • 基础

  • js
  • JS相关内容
江月何年初照人
2021-03-23

跨域及解决方案

# 跨域及解决方案

# 什么是跨域

有跨域就会有同域,也就是我们常说的同源策略 (opens new window) , MDN 上对于同源策略的解释为

同源策略

是一个重要的安全策略,它用于限制一个 origin 的文档或者它加载的脚本如何能与另一个源的资源进行交互。它能帮助阻隔恶意文档,减少可能被攻击的媒介。

如果两个 URL 的 protocol、port (如果有指定的话) 和 host 都相同的话,则这两个 URL 是同源。这个方案也被称为 “协议 / 主机 / 端口元组”,或者直接是 “元组”。(“元组” 是指一组项目构成的整体,双重 / 三重 / 四重 / 五重 / 等的通用形式)。

是什么意思呢,简单来说就是同源策略是一种约束,作用是为了安全性考虑,它用来防止一些例如 XSS、CSRF 等攻击。

同源是指 协议 + 域名 + 端口 三者相同,子域名不同也认为不同源

# 同源策略会限制哪些内容?

  1. LocalStorage 、 Cookie 、 IndexDB 等存储相关,只能同域访问
  2. DOM 元素也存在同源策略,例如 <iframe></iframe>
  3. Ajax 请求的返回结果,Ajax 跨域访问拦截的不是请求,是返回的结果,浏览器会认为不安全,会拦截

# 在 DOM 元素中存在以下标签,是允许跨域访问资源的

  1. <img src="https://www.baidu.com/test.png" />
  2. <link href="https://www.google.com/test.css" />
  3. <script src="https://www.google.com/test.js"></script>

# 针对于 Ajax 请求的跨域问题

跨域并不是请求发不出去,请求能发出去,服务端能收到请求并正常返回结果,只是结果被浏览器拦截了, 因为 Ajax 请求会读取返回内容,因此浏览器不允许你这么做(一个域名的 JS ,在未经允许的情况下,不得读取另一个域名的内容。但浏览器并不阻止你向另一个域名发送请求。)。但是 form 表单却不存在 跨域的问题,是因为 form 表单再提交后,并不能获取到返回的内容,所以浏览器认为这是安全的。

Ajax 和 form 表单提交,都可以携带 cookie ,所以 form 表单提交 和 Ajax 并不能阻止 CSRF 攻击,因为请求已经发出去了,通过 cookie 的新属性 SameSite 可以避免这个问题

# 跨域解决方案

# jsonp

# jsonp 原理

JSONP 的原理非常简单,就是利用 HTML 标签的 src 属性可以跨域访问资源的特性,通过 script 标签来执行跨域的 JS 代码,通过这些代码,来实现前端跨域请求数据

# jsonp 的实现

  1. 首先声明一个回调函数,函数名当做参数,传递给跨域请求的服务器

    <script>
      function callback(data) {
        console.log(data)
      }
    </script>
    
    1
    2
    3
    4
    5
  2. 创建一个 script 标签,script 标签的 src 为需要跨域请求的服务器地址,然后拼接回调函数参数,回调函数的名称按照约定来书写,以百度搜索举例

    我们在百度搜索时,百度搜索存在根据用户输入自动识别显示匹配列表,这个过程就是一个 jsonp 的请求过程

    在 Network 中,搜索不属于 XHR 请求,它是一个 jsonp 的请求,我们以搜索 aab 为例,得到搜索的地址如下

    https://www.baidu.com/sugrec?pre=1&p=3&ie=utf-8&json=1&prod=pc&from=pc_web&sugsid=33358,33261,33344,31660,33692,33595,33759,33675,33714,26350&wd=aab&req=2&csor=3&pwd=a&cb=jQuery110207690587662328763_1616507377096&_=1616507377102
    
    1

    这里 wd=aab 是我们搜索的关键字,cb=jQuery110207690587662328763_1616507377096 则是 jsonp 的回调函数

    我们拿到关键的信息,简化 jsonp 地址,得到如下地址

    https://www.baidu.com/sugrec?prod=pc&wd=aab&cb=callback
    
    1

    访问结果如下

    // https://www.baidu.com/sugrec?prod=pc&wd=aab&cb=show
    
    show({
      "q": "aab",
      "p": false,
      "g": [
        {
          "type": "sug",
          "sa": "s_1",
          "q": "aabc式词语大全"
        },
        {
          "type": "sug",
          "sa": "s_2",
          "q": "aab式的词语"
        },
        {
          "type": "sug",
          "sa": "s_3",
          "q": "aabb式的词语"
        },
        {
          "type": "sug",
          "sa": "s_7",
          "q": "aabc式词语大全三年级下册"
        },
        {
          "type": "sug",
          "sa": "s_8",
          "q": "aabc式词语大全 成语二年级"
        },
        {
          "type": "sug",
          "sa": "s_9",
          "q": "aabc式词语四字"
        },
        {
          "type": "sug",
          "sa": "s_10",
          "q": "aabc成语四字"
        }
      ],
      "slid": "7841404972524760491",
      "queryid": "0x644446e751edab"
    })
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45

    那么此时,我们可以创建一个 script 标签,配合第一步创建的函数,来实现 jsonp ,拿到返回的数据

    <script>
        function callback(data) {
            console.log(data)
        }
    </script>
    <script src="https://www.baidu.com/sugrec?prod=pc&wd=aab&cb=callback"></script>
    
    1
    2
    3
    4
    5
    6
  3. 至此就实现了一个简单的 jsonp 请求

# jsonp 的封装

在日常开发中,如果每次都需要这么写,就显得很繁琐,我们可以根据实现思路,封装一个 jsonp 请求

function jsonp({ url, params, cb }) {
  // 处理参数拼接
  function handlerParams(query) {
    return Object.keys(query).reduce((result, k, i) => (result = result + (i < 1 ? '' : `&`) + `${k}=${query[k]}`, result), '?')
  }
  return new Promise((resolve, reject) => {
    // 1. 首先创建 script 标签
    const script = document.createElement('script')
    // 2. 创建一个挂载到 window 上的函数,方便访问,在函数里将数据抛出去,然后降创建的 script 标签删除掉
    window[cb] = function(data) {
      resolve(data);
      document.body.removeChild(script)
    }
    // 3. 将回调函数名拼接到最后
    params = {...params, cb}
    // 4. 处理参数后拼接url
    script.src = url + handlerParams(params)

    // 5. 将 script 标签添加到 body 中
    document.body.appendChild(script)
  })
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22

# 其他 jsonp 的实现

/**
 * Module dependencies
 */

var debug = require('debug')('jsonp');

/**
 * Module exports.
 */

module.exports = jsonp;

/**
 * Callback index.
 */

var count = 0;

/**
 * Noop function.
 */

function noop(){}

/**
 * JSONP handler
 *
 * Options:
 *  - param {String} qs parameter (`callback`)
 *  - prefix {String} qs parameter (`__jp`)
 *  - name {String} qs parameter (`prefix` + incr)
 *  - timeout {Number} how long after a timeout error is emitted (`60000`)
 *
 * @param {String} url
 * @param {Object|Function} optional options / callback
 * @param {Function} optional callback
 */

function jsonp(url, opts, fn){
  if ('function' == typeof opts) {
    fn = opts;
    opts = {};
  }
  if (!opts) opts = {};

  var prefix = opts.prefix || '__jp';

  // use the callback name that was passed if one was provided.
  // otherwise generate a unique name by incrementing our counter.
  var id = opts.name || (prefix + (count++));

  var param = opts.param || 'callback';
  var timeout = null != opts.timeout ? opts.timeout : 60000;
  var enc = encodeURIComponent;
  var target = document.getElementsByTagName('script')[0] || document.head;
  var script;
  var timer;


  if (timeout) {
    timer = setTimeout(function(){
      cleanup();
      if (fn) fn(new Error('Timeout'));
    }, timeout);
  }

  function cleanup(){
    if (script.parentNode) script.parentNode.removeChild(script);
    window[id] = noop;
    if (timer) clearTimeout(timer);
  }

  function cancel(){
    if (window[id]) {
      cleanup();
    }
  }

  window[id] = function(data){
    debug('jsonp got', data);
    cleanup();
    if (fn) fn(null, data);
  };

  // add qs component
  url += (~url.indexOf('?') ? '&' : '?') + param + '=' + enc(id);
  url = url.replace('?&', '?');

  debug('jsonp req "%s"', url);

  // create script
  script = document.createElement('script');
  script.src = url;
  target.parentNode.insertBefore(script, target);

  return cancel;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97

# jsonp 限制

jsonp 只支持 get 请求,对于其他请求不支持。优点是兼容性好。

# CORS

# CORS (opens new window) 是什么

CORS

跨源资源共享 (CORS) (或通俗地译为跨域资源共享)是一种基于 HTTP 头的机制,该机制通过允许服务器标示除了它自己以外的其它 origin(域,协议和端口),这样浏览器可以访问加载这些资源。跨源资源共享还通过一种机制来检查服务器是否会允许要发送的真实请求,该机制通过浏览器发起一个到服务器托管的跨源资源的 "预检" 请求。在预检中,浏览器发送的头中标示有 HTTP 方法和真实请求中会用到的头。

原理

跨源资源共享标准新增了一组 HTTP 首部字段,允许服务器声明哪些源站通过浏览器有权限访问哪些资源。另外,规范要求,对那些可能对服务器数据产生副作用的 HTTP 请求方法(特别是 GET 以外的 HTTP 请求,或者搭配某些 MIME 类型的 POST 请求),浏览器必须首先使用 OPTIONS 方法发起一个预检请求(preflight request),从而获知服务端是否允许该跨源请求。服务器确认允许之后,才发起实际的 HTTP 请求。在预检请求的返回中,服务器端也可以通知客户端,是否需要携带身份凭证(包括 Cookies 和 HTTP 认证相关数据)。

CORS 请求失败会产生错误,但是为了安全,在 JavaScript 代码层面是无法获知到底具体是哪里出了问题。你只能查看浏览器的控制台以得知具体是哪里出现了错误。

注意

IE 10 提供了对规范的完整支持,但在较早版本(8 和 9)中,CORS 机制是借由 XDomainRequest 对象完成的

# CORS 发送请求的情况

  1. 不会触发 CORS 预检的请求,也就是简单请求,若请求满足所有下述条件,则该请求可视为 简单请求 (opens new window) :

    • 使用下列方法之一
      • GET
      • HEAD
      • POST
    • Content-Type 的值仅限于下列三者之一
      • text/plain
      • multipart/form-data
      • application/x-www-form-urlencoded
    • 请求中的任意 XMLHttpRequestUpload 对象均没有注册任何事件监听器;XMLHttpRequestUpload 对象可以使用 XMLHttpRequest.upload 属性访问

    还有其他情况可参考 简单请求 (opens new window) , 这里列出了一些比较常用的

  2. 会触发 CORS 预检的请求

    不符合简单请求的情况下,会触发。需预检的请求 要求必须首先使用 OPTIONS 方法发起一个预检请求到服务器,以获知服务器是否允许该实际请求。 预检请求 的使用,可以避免跨域请求对服务器的用户数据产生未预期的影响。

    OPTIONS 是 HTTP/1.1 协议中定义的方法,用以从服务器获取更多信息。该方法不会对服务器资源产生影响。 预检请求中同时携带了下面两个首部字段

    Access-Control-Request-Method: POST
    Access-Control-Request-Headers: X-PINGOTHER, Content-Type
    
    1
    2

    首部字段 Access-Control-Request-Method 告知服务器,实际请求将使用 POST 方法。首部字段 Access-Control-Request-Headers 告知服务器,实际请求将携带两个自定义请求首部字段:X-PINGOTHER 与 Content-Type。服务器据此决定,该实际请求是否被允许。

  3. HTTP 响应首部字段 (opens new window) ,也就是 CORS 的根本,这些字段是 服务器响应时,需要配置的字段及值,分别如下

    • Access-Control-Allow-Origin , origin 参数的值指定了允许访问该资源的外域 URI。对于不需要携带身份凭证的请求,服务器可以指定该字段的值为通配符,表示允许来自所有域的请求
      Access-Control-Allow-Origin: <origin> | *
      
      1
    • Access-Control-Expose-Headers 头让服务器把允许浏览器访问的头放入白名单
      Access-Control-Expose-Headers: X-My-Custom-Header, X-Another-Custom-Header
      
      1
    • Access-Control-Max-Age 头指定了 预检 请求的结果能够被缓存多久,delta-seconds 参数表示 预检 请求的结果在多少秒内有效
      Access-Control-Max-Age: <delta-seconds>
      
      1
    • Access-Control-Allow-Credentials 头指定了当浏览器的 credentials 设置为 true 时是否允许浏览器读取 response 的内容。 也就是在请求是携带 cookie 凭证。在请求携带 cookie 时,有一点需要注意,如果服务器端 Access-Control-Allow-Origin 设置为通配符 *,请求将会失败。
      Access-Control-Allow-Credentials: true
      
      1
    • Access-Control-Allow-Methods 首部字段用于预检请求的响应。其指明了实际请求所允许使用的 HTTP 方法。
      Access-Control-Allow-Methods: <method>[, <method>]*
      
      1
    • Access-Control-Allow-Headers 首部字段用于预检请求的响应。其指明了实际请求中允许携带的首部字段。
      Access-Control-Allow-Headers: <field-name>[, <field-name>]*
      
      1

    后端设置响应的 HTTP 响应头是 CORS 实现的关键

    相关示例可查看 MDN 中对于 CORS 的说明 (opens new window)

# postMessage (opens new window)

# postMessage 是什么

postMessage

postMessage 是 html5 引入的 API,postMessage () 方法允许来自不同源的脚本采用异步方式进行有效的通信,可以实现跨文本文档,多窗口,跨域消息传递。多用于窗口间数据通信,这也使它成为跨域通信的一种有效的解决方案.

window.postMessage() 方法可以安全地实现跨源通信。

otherWindow.postMessage(message, targetOrigin, [transfer]);
1
  • otherWindow 其他窗口的一个引用,比如 iframe 的 contentWindow 属性、执行 window.open 返回的窗口对象、或者是命名过或数值索引的 window.frames。
  • message 将要发送到其他 window 的数据。
  • targetOrigin 通过窗口的 origin 属性来指定哪些窗口能接收到消息事件,其值可以是字符串 *(表示无限制)或者一个 URI。*如果你明确的知道消息应该发送到哪个窗口,那么请始终提供一个有确切值的 targetOrigin,而不是 。不提供确切的目标将导致数据泄露到任何对数据感兴趣的恶意站点
  • transfer 可选。是一串和 message 同时传递的 Transferable (opens new window) 对象。这些对象的所有权将被转移给消息的接收方,而发送一方将不再保有所有权。

# 示例

从 a.html 向 iframe 的 b.html 发送消息

<!-- a.html-->
<iframe src="http://localhost:8899/b.html" id="frame" onload="load()"></iframe>
<script>
    function load() {
        const frame = document.getElementById('frame')
        frame.contentWindow.postMessage('from a', 'http://localhost:8000/a.html') // 发送数据
        window.onmessage = function (e) { // 接收数据
           console.log(e.data) // from b
        }
    }
</script>
1
2
3
4
5
6
7
8
9
10
11
<!--b.html-->
<script>
   window.onmessage = function (e) { // 接收数据
      console.log(e.data) // from a
      e.source.postMessage('from b', e.origin)
   }
</script>
1
2
3
4
5
6
7

message 属性具有:

  • data 从其他 window 中传递过来的对象
  • origin 调用 postMessage 时消息发送方窗口的 origin
  • source 对发送消息的窗口对象的引用

# window.name + iframe

window.name

window.name 有一个性质, 页面如果设置了 window.name,那么在不关闭页面的情况下,即使进行了页面跳转,这个 window.name 还是会保留

根据 window.name 的这个性质,我们可以实现 跨域访问数据

假设我们需要从 a.html 拿到 c.html 中的数据,那么我们可以找一个和 a.html 同源的页面 b.html,也就是说

  • a 和 b 是同源的
  • a 要拿到 c 的数据

实现就是 a 先引用 c,然后 c 设置 window.name ,然后 把 a 的引用地址改为 b

<!--a.html-->
<!--http://localhost:8000/a.html-->
<iframe src="http://localhost:8899/c.html" id="iframe" onload="load"></iframe>
<script>
   let type = true
   function load() {
      const iframe = document.getElementById('iframe')
      if (type) {
         iframe.src = 'http://localhost:8000/b.html'
         type = false
      } else {
        console.log(iframe.contentWindow.name) // from c
      }
   }
</script>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<!--c.html-->
<!--http://localhost:8899/c.html-->
<script>
   window.name = 'from c'
</script>
1
2
3
4
5

以 b.html 作为中间页,监听 iframe 第二次的 onload 事件,就可以拿到想要的数据

# location.hash + iframe

这个办法比较绕,但是可以解决完全跨域情况下的脚步置换问题。原理是利用 location.hash 来进行传值。

hash

http://www.google.com#abc 中的 #abc 就是 location.hash , 改变 location.hash 并不会导致页面刷新,所以可以通过 location.hash 来传递数据,当然容量是有限的

# 实现过程

假设,同样有两个页面 a 、 c, a 要和 c 通信

第一步,a 先引用 c

<!--a.html-->
<!--http://localhost:8000/a.html-->
<iframe src="http://localhost:8899/c.html"></iframe>
1
2
3

在引用时,可以通过 location.hash 传值,也就是

<!--a.html-->
<!--http://localhost:8000/a.html-->
<iframe src="http://localhost:8899/c.html#abc"></iframe>
1
2
3

第二步,在 c 里面接收传入的 hash,并且传递数据给 a 的同源页面 b

<!--c.html-->
<!--http://localhost:8899/c.html-->
<script>
   console.log(location.hash) // abc
   const iframe = document.createElement('iframe')
   iframe.src = "http://localhost:8000/b.html#c2a"
   document.body.appendChild(iframe)
</script>
1
2
3
4
5
6
7
8

第三步,在 b 页面中,将 hash 传递给 a ,因为 b 和 a 是同源,所以可以传递信息

<!--b.html-->
<!--http://localhost:8000/b.html-->
<script>
   window.parent.parent.location.hash = location.hash // b 传递信息给 a, b 的父级是 c ,c 的父级是 a, b 和 a 是同源
</script>
1
2
3
4
5

第四步,在 a 里面进行监听,然后处理逻辑

<!--a.html-->
<!--http://localhost:8000/a.html-->
<iframe src="http://localhost:8899/c.html#abc"></iframe>
<script>
   window.onhashchange = function () {
     console.log(location.hash) // c2a
   }
</script>
1
2
3
4
5
6
7
8

这样也就实现了 跨域传递数据

# document.domain + iframe

提示

该方式只能用于主域名相同的情况下,比如 https://a.google.com 和 https://b.google.com

# 原理

原理就是通过设置 document.domain 使得 a 和 b 属于同源,绕过浏览器的检查

注意

端口号是由浏览器另行检查的。任何对 document.domain 的赋值操作,包括 document.domain = document.domain 都会导致端口号被重写为 null 。因此 company.com:8080 不能仅通过设置 document.domain = "company.com" 来与 company.com 通信。必须在他们双方中都进行赋值,以确保端口号都为 null 。

注意

使用 document.domain 来允许子域安全访问其父域时,您需要在父域和子域中设置 document.domain 为相同的值。这是必要的,即使这样做只是将父域设置回其原始值。不这样做可能会导致权限错误。

# 实现

<!--a.html-->
<!--http://a.test.com:8080/a.html-->
<iframe src="http://b.test.com:8080/b.html" onload="load" id="iframe"></iframe>
<script>
   document.domain = 'test.com'
   function load() {
        const iframe = document.getElementById('iframe')
        console.log(iframe.contentWindow.temp) // b
   }
</script>
1
2
3
4
5
6
7
8
9
10
<!--b.html-->
<!--http://b.test.com:8080/b.html-->
<script>
   document.domain = 'test.com'
   var b = 'b'
</script>
1
2
3
4
5
6

也就是通过设置 document.domain 实现了 跨域通信

# WebSocket (opens new window)

提示

WebSocket 是 HTML5 开始提供的一种在单个 TCP 连接上进行全双工通讯的协议,WebSocket 本身不存在同源策略限制,所以可以实现跨域。WebSocket 使得客户端和服务器之间的数据交换变得更加简单,允许服务端主动向客户端推送数据。在 WebSocket API 中,浏览器和服务器只需要完成一次握手,两者之间就直接可以创建持久性的连接,并进行双向数据传输。

# 实现思路

在建立 WebSocket 连接后,你可以通过 send() 方法来向服务器发送数据,并通过 onmessage 事件来接收服务器返回的数据。

<!--客户端-->
<script>
   const socket = new WebSocket('ws://localhost:8080');
   socket.onopen = function () {
      socket.send('客户端') // 向服务器发送数据
   }
   socket.onmessage = function (e) {
      console.log(e.data) // 接收服务器返回的数据
   }
</script>
1
2
3
4
5
6
7
8
9
10
// 服务端,使用 node.js 模拟
const express = require('express')
const app = express()

const WebSocket = require('ws')
const wss = new WebSocket.Server({ port: 8080 })

wss.on('connection',function(ws) {
   ws.on('message', function (data) {
      console.log(data);
      ws.send('服务端')
   });
})
1
2
3
4
5
6
7
8
9
10
11
12
13

使用 WebSocket 同样也可以实现跨域

# nginx 反向代理

提示

使用 nginx 反向代理实现跨域,只需要修改 nginx 的配置即可解决跨域问题。

a 网站向 b 网站请求某个接口时,向 b 网站发送一个请求,nginx 根据配置文件接收这个请求,代替 a 网站向 b 网站来请求。 nginx 拿到这个资源后再返回给 a 网站,以此来解决了跨域问题。

# 实现

配置 nginx,监听本地 8080 端口,将 /api 请求转发到 localhost:9000

# nginx.conf
server {
    listen       8080;
    server_name  localhost;
    location / {
        root   html;
        index  index.html index.htm;
    }
    location /api {
        proxy_pass http://localhost:9000;
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
// server.js
let express = require('express')
let app = express()
app.get('/api/getData', function(req, res) {
   const list = {'a': 1, 'b': 2}
   res.end(JSON.stringify(list))
})
app.listen(9000)
1
2
3
4
5
6
7
8

在 http://localhost:8080 的 index 页面中,使用 ajax 去请求 /api/getData , 会得到 JSON 数据

{
  "a": 1,
  "b": 2
}
1
2
3
4

这样也就实现了跨域

# nginx 常用命令

nginx -s reload     # 优雅重启,并重新载入配置文件nginx.conf

nginx -s quit       #  优雅停止nginx,有连接时会等连接请求完成再杀死worker进程 

nginx -s reopen     # 重新打开日志文件,一般用于切割日志

nginx -v            # 查看版本  

nginx -t            # 检查nginx的配置文件

nginx -h            # 查看帮助信息

nginx  -c filename  # 指定配置文件
1
2
3
4
5
6
7
8
9
10
11
12
13

# 文档

连前端都看得懂的《Nginx 入门指南》 (opens new window)

# node 中间件实现跨域

提示

跨域问题是浏览器的同源策略的安全机制引起的,服务器之间是不存在跨域问题的,这也不是说服务器之间没有安全机制,只是服务器之间的调用无论是通过 http 访问还是通过 rpc 调用都是协议层面的机制,并没有限制必须同源。 这也就是 Node 层跨域的实质,我们把静态文件和 Node 中间层放在同一个域下,这样前端资源和 Node 层的通信不会受同源策略影响,然后我们通过 Node 中间层把前端的资源请求转发到真实的请求地址,在通过中间层把请求返回的数据传给前端,这样就实现了此域跨彼域的串门操作。

# 实现

本地的 http://localhost:3000/a.html 页面需要请求 http://localhost:9001 的数据,但是 9001 的服务端又不能使用 CORS 的方式来实现跨域,我们可以使用 node 实现一个中间件,达到跨域的效果

第一步,创建一个请求,不请求 9001, 请求 4000

<!--客户端-->
<!--http://localhost:3000/a.html-->
<script>
   const xhr = new XMLHttpRequest()
   xhr.open('get', 'http://localhost:4000/api/getData')
   xhr.send()
   xhr.onload = function (){
     if (xhr.status === 200) {
       console.log(xhr.response)
     }
   }
   xhr.onerror = function() {
      throw new Error('error')
   };
</script>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

第二步,实现 node 中间件代理转发,以下有两种实现方式

// server.js
const express = require('express');
const { createProxyMiddleware } = require('http-proxy-middleware');
const app = express();
//设置允许跨域访问该服务.
app.all('*', function (req, res, next) {
   console.log(res)
   // 设置是否运行客户端设置 withCredentials
   // 即在不同域名下发出的请求也可以携带 cookie
   // res.header("Access-Control-Allow-Credentials", true)
   // 第二个参数表示允许跨域的域名,* 代表所有域名
   res.header('Access-Control-Allow-Origin', '*')
   res.header('Access-Control-Allow-Methods', 'GET, PUT, POST, OPTIONS') // 允许的 http 请求的方法
   // 允许前台获得的除 Cache-Control、Content-Language、Content-Type、Expires、Last-Modified、Pragma 这几张基本响应头之外的响应头
   res.header('Access-Control-Allow-Headers', 'Content-Type, Authorization, Content-Length, X-Requested-With')
   if (req.method == 'OPTIONS') {
      res.sendStatus(200)
   } else {
      next()
   }
});

const targetUrl = "http://localhost:9001";
app.use('/api/*', createProxyMiddleware({ target: targetUrl, changeOrigin: true }));

//配置服务端口
app.listen(4000, () => {
   console.log(`localhost:4000`);
});
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
// server.js
const http = require('http')
// 第一步:接受客户端请求
const server = http.createServer((request, response) => {
  // 代理服务器,直接和浏览器直接交互,需要设置CORS 的首部字段
  response.writeHead(200, {
    'Access-Control-Allow-Origin': '*',
    'Access-Control-Allow-Methods': '*',
    'Access-Control-Allow-Headers': 'Content-Type'
  })
  // 第二步:将请求转发给服务器
  const proxyRequest = http
    .request(
      {
        host: '127.0.0.1',
        port: 9001,
        path: request.url,
        method: request.method,
        headers: request.headers
      },
      serverResponse => {
        // 第三步:收到服务器的响应
        let body = ''
        serverResponse.on('data', chunk => {
          body += chunk
        })
        serverResponse.on('end', () => {
          // 第四步:将响应结果转发给浏览器
          response.end(body)
        })
      }
    )
    .end()
})
server.listen(4000, () => {
  console.log('The proxyServer is running at http://localhost:4000')
})
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37

第三步, 模拟服务器 9001 端口数据

// 服务端
let express = require('express')
let app = express()
app.get('/api/getData', function(req, res) {
  const list = {'a': 1, 'b': 2}
  res.end(JSON.stringify(list))
})
app.listen(9001)
1
2
3
4
5
6
7
8

直接从 3000 请求 9001 的数据是跨域的,不允许的,但是通过 node 中间件之后,数据就可以互通,实现了跨域

# 解释

http://localhost:9001 目标服务器,没有设置跨域,如果在客户端直接请求 9001 端口就会报跨域错误。

http://localhost:4000 node 中间件,因为设置了跨域,所以可以接收 3000 的请求,把 3000 的请求转发到 9001 服务器,9001 服务器返回数据到 4000,4000 在返回给 3000,就解决了跨域问题。

http://localhost:3000 客户端

# 最后

  • CORS 支持所有类型的 HTTP 请求,是跨域 HTTP 请求的根本解决方案
  • JSONP 只支持 GET 请求,JSONP 的优势在于支持老式浏览器,以及可以向不支持 CORS 的网站请求数据。
  • 不管是 Node 中间件代理还是 nginx 反向代理,主要是通过同源策略对服务器不加限制。
  • 日常工作中,用得比较多的跨域方案是 CORS 和 nginx 反向代理
编辑 (opens new window)
#JS
上次更新: 2021/03/25 20:47:33
连续赋值
节流和防抖

← 连续赋值 节流和防抖→

最近更新
01
a标签下载限制
08-08
02
必应每日一图
05-27
03
小球碰壁反弹动画
05-26
更多文章>
Theme by Vdoing | Copyright © 2021-2023 Himawari | MIT License
  • 跟随系统
  • 浅色模式
  • 深色模式
  • 阅读模式