# 浏览器跨域

协议,端口和主机不是相同的,则跨域。其实主要是用来防止 CSRF 攻击的。简单点说,CSRF 攻击是利用用户的登录态发起恶意请求。

# 图像Ping

与服务器进行简单,单向的跨域通信的方式。请求的数据是通过查询字符串形式发送的,而响应可以是任意内容。

var img = new Image()
img.onload = img.onerror = function() {
  alert('Done')
}
img.src = "http://www.example.com/test?name=Nicholas"
1
2
3
4
5

缺点是只能发送GET请求,二是无法访问服务器的响应文本。

# JSONP

能够直接访问响应文本,支持在浏览器与服务器之间双向通信。

function jsonp(url, jsonpCallback, success) {
  let script = document.createElement('script')
  script.src = url
  script.async = true
  script.type = 'text/javascript'
  window[jsonpCallback] = function(data) {
    success && success(data)
  }
  document.body.appendChild(script)
}
jsonp('http://xxx', 'callback', function(value) {
  console.log(value)
})
1
2
3
4
5
6
7
8
9
10
11
12
13
  • JSONP缺点

首先JSONP是从其他域中加载代码执行。如果其他域不安全,很有可能会在响应中夹带一些恶意代码,而此时除了完全放弃JSONP调用之外,没有办法追究。要确定JSONP请求是否失败也不容易

TIP

JSONP解决跨域的原理:首先在客户端注册一个callback, 然后把callback的名字传给服务器。此时,服务器先生成一个function , function 名字就是传递上来的参数。最后将 json 数据直接以入参的方式,放置到 function 中,这样就生成了一段 js 语法的文档,返回给客户端。客户端浏览器,解析script标签,并执行返回的 javascript 文档,此时数据作为参数,传入到了客户端预先定义好的 callback 函数里。

# CORS

服务端设置Access-Control-Allow-Origin就可以开启CORS。该属性表示哪些域名可以访问资源,如果设置通配符表示所有网站都可以访问资源。虽然设置CORS和前端没什么关系,但是通过这种方式解决跨域问题的话,会在发送请求时出现两种情况,分别为简单请求和复杂请求。

  • 简单请求

某些请求不会触发CORS预检请求,这样的请求一般称为“简单请求”。

  1. 方法 get post head
  2. Content-Type仅限:text/plain,multipart/form-data,application/x-www-form-urlencoded
  3. HTTP头信息不超过Accept Accept-Language Content-Language Last-Evnet-ID Content-Type
  4. 请求中的任意XMLHttpRequestUpload对象没有注册任何事件监听器
  5. 请求中没有使用ReadableStream对象
  • 复杂请求

对于复杂请求来说,首先会发起一个预检请求,该请求是options方法的,返回码为204。通过该请求来知道服务端是否允许跨域请求。

跨域共享标准规范要求,对那些可能对服务器数据产生副作用的 HTTP 请求方法(特别是 GET 以外的 HTTP 请求,或者搭配某些 MIME 类型的 POST 请求),浏览器必须首先使用 OPTIONS 方法发起一个预检请求(preflight request),从而获知服务端是否允许该跨域请求。服务器确认允许之后,才发起实际的 HTTP 请求。

WARNING

由此可见,当触发预检时,跨域请求便会发送 2 次请求,既增加了请求数,也延迟了请求真正发起的时间,严重影响性能。

  • 优化Options请求
    • 转为简单请求,如用JSONP做跨域请求
    • options 请求进行缓存,服务器端设置 Access-Control-Max-Age 字段,那么当第一次请求该 URL 时会发出 OPTIONS 请求,浏览器会根据返回的 Access-Control-Max-Age 字段缓存该请求的 OPTIONS 预检请求的响应结果(具体缓存时间还取决于浏览器的支持的默认最大值,取两者最小值,一般为 10 分钟)。在缓存有效期内,该资源的请求(URLheader 字段都相同的情况下)不会再触发预检。(chrome 打开控制台可以看到,当服务器响应Access-Control-Max-Age 时只有第一次请求会有预检,后面不会了。注意要开启缓存,去掉 disable cache 勾选。)

TIP

关于corscookie问题 想要传递cookie满足3个条件:

  1. web请求设置withCredentials

默认情况下跨域请求浏览器不带cookie。所以需要设置

  1. Access-Control-Allow-Credentialstrue
  2. Access-Control-Allow-Origin为非*

# document.domain

该方式只能用于二级域名相同的情况下,比如a.test.comb.test.com。只需要给页面添加document.domain = 'test.com'表示二级域名相同就可以实现跨域。

// a.test.com
<body>
  helloa
  <iframe
    src="http://b.test.com/b.html"
    frameborder="0"
    onload="load()"
    id="frame"
  ></iframe>
  <script>
    document.domain = "test.com";
    function load() {
      console.log(frame.contentWindow.a);
    }
  </script>
</body>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// b.test.com
<body>
  hellob
  <script>
    document.domain = "test.com";
    var a = 100;
  </script>
</body>
1
2
3
4
5
6
7
8

# devServer vue项目(node的正向代理)

跨域是浏览器禁止的,服务端并不禁止跨域,所以浏览器可以发给自己的服务端然后由自己的服务端再转发给要跨域的服务端,做一层代理。vue-cliproxy用的就是http-proxy-middleware中间件,http-proxy-middleware内部用http-proxy

TIP

正向代理,意思是一个位于客户端和原始服务器(origin server)之间的服务器,为了从原始服务器取得内容,客户端向代理发送一个请求并指定目标(原始服务器),然后代理向原始服务器转交请求并将获得的内容返回给客户端。

# nginx反向代理

简单配置

TIP

反向代理(Reverse Proxy)方式是指以代理服务器来接受internet上的连接请求,然后将请求转发给内部网络上的服务器,并将从服务器上得到的结果返回给internet上请求连接的客户端,此时代理服务器对外就表现为一个反向代理服务器。

# 两个浏览器窗口之间通信

  1. localStorage

localstorge在一个标签页里被添加、修改或删除时,都会触发一个storage事件,通过在另一个标签页里监听storage事件,即可得到localstorge存储的值,实现不同标签页之间的通信。

  1. WebSocket

所有的WebSocket都监听同一个服务器地址,利用send发送消息,利用onmessage获取信息的变化,不仅能不同窗口,还能跨浏览器,兼容性最佳,只是需要消耗服务器资源。这种方式本质没有使用HTTP,因此也没有跨域限制

let socket = new WebSocket('ws://localhost:3000');
socket.open = function () {
  socket.send('hello');
}
socket.onmessage = function(e) {
  console.log(e.data); // 接收服务器返回的数据
}
1
2
3
4
5
6
7
  1. postMessage 借助iframewindow.open
otherWindow.postMessage(message, targetOrigin, [transfer]);
1
  • otherWindow

其他窗口的一个引用,比如iframecontentWindow属性、执行window.open返回的窗口对象、或者是命名过或数值索引的window.frames

  • message

将要发送到其他window的数据。它将会被结构化克隆算法序列化。这意味着你可以不受什么限制的将数据对象安全的传送给目标窗口而无需自己序列化。

  • targetOrigin

通过窗口的origin属性来指定哪些窗口能接收到消息事件,其值可以是字符串""(表示无限制)或者一个URI。在发送消息的时候,如果目标窗口的协议、主机地址或端口这三者的任意一项不匹配targetOrigin提供的值,那么消息就不会被发送;只有三者完全匹配,消息才会被发送。这个机制用来控制消息可以发送到哪些窗口;例如,当用postMessage传送密码时,这个参数就显得尤为重要,必须保证它的值与这条包含密码的信息的预期接受者的origin属性完全一致,来防止密码被恶意的第三方截获。如果你明确的知道消息应该发送到哪个窗口,那么请始终提供一个有确切值的targetOrigin,而不是不提供确切的目标将导致数据泄露到任何对数据感兴趣的恶意站点。

  • transfer可选

是一串和message同时传递的 Transferable 对象. 这些对象的所有权将被转移给消息的接收方,而发送一方将不再保有所有权。

TIP

  1. window.postMessage()方法可以安全地实现跨源通信。通常,对于两个不同页面的脚本,只有当执行它们的页面位于具有相同的协议(通常为 https),端口号(443https 的默认值),以及主机 (两个页面的模数 Document.domain设置为相同的值) 时,这两个脚本才能相互通信。

  2. 可以在 http 返回头 添加X-Frame-Options: SAMEORIGIN 防止被别人添加至 iframe

# 即时通信技术

  1. Comet
  2. 服务器发送事件 SSE
  3. Web Socket