#什么是跨域?

回顾一下url的组成:
004.png
浏览器遵循同源策略(scheme(协议),host(主机),path(路径)),这三者都相同则为同源,那么非同源会有以下限制:

  • 不能读取和修改对方的 DOM
  • 不读访问对方的 Cookie、IndexDB 和 LocalStorage
  • 限制 XMLHttpRequest 请求。(后面的话题着重围绕这个)

当浏览器发送Ajax请求时,目标URL和当前的URL不同源,那么则会产生跨域的需求(跨域请求)
跨域请求一般都会被浏览器给拦截,那这个拦截是如何发生呢?
首先要知道的是,浏览器是多进程的,以 Chrome 为例,进程组成如下:
007.jpg
008.jpg
详情:http://47.98.159.95/my_blog/http/014.html#%E4%BB%80%E4%B9%88%E6%98%AF%E8%B7%A8%E5%9F%9F

#跨域的几种解决方案

# JSONP

jsonp的原理:虽然XMLHttpRequest对象遵循同源策略,但script标签不一样,src属性可以填上目标地址,从而发送get请求,实现跨域请求并拿到响应,这也就是 JSONP 的原理.接下来我们封装一个jsonp
jsonp:

  1. // 1. script 标签可以跨域
  2. // 2. script 标签 如果请求失败之后,如果跨域的话,没有具体的错误信息
  3. // 为什么?
  4. // 和安全有关
  5. function jsonp(req) {
  6. const {
  7. params,
  8. url
  9. } = req
  10. const callbackName = 'laizhenhang'
  11. const generateUrl = (url, params, callbackName) => {
  12. //localhost:9000/api/user?id=1&age=18&jsonpCallback=laizhenhang
  13. let paramsStr = '?'
  14. for (const key in params) {
  15. paramsStr += `${key}=${params[key]}&`
  16. }
  17. paramsStr += `jsonpCallback=${callbackName}`
  18. const fullURL = url + paramsStr
  19. return fullURL
  20. }
  21. // 1. 创建 script 元素,将其 src 指向 要请求的url, 携带 callback的名字,让服务端返回该脚本,让前端执行
  22. const scriptEL = document.createElement('script')
  23. scriptEL.src = generateUrl(url, params, callbackName)
  24. return new Promise((resolve, reject) => {
  25. // 2. append script元素,让浏览器去发起script的get请求
  26. document.body.appendChild(scriptEL)
  27. window[callbackName] = (data) => {
  28. // 3. 执行回调之后,执行用户的回调或者让用户知道请求成功或失败
  29. resolve(data)
  30. // 4. 最后,收尾. 移除我们添加的 scrip 元素, 以及 删除 我们在 window 上添加的函数
  31. document.body.removeChild(scriptEL)
  32. delete window[callbackName]
  33. }
  34. // 超时的话,reject
  35. })
  36. }
  37. jsonp({
  38. url: 'http://localhost:9000/user',
  39. params: {
  40. id: 1,
  41. age: 18
  42. }
  43. }).then(data => {
  44. console.log(data)
  45. })
  46. // jsonp的兼容性
  47. // jsonp的安全性
  48. // jsonp的局限性

相应服务端的代码

  1. const {
  2. json
  3. } = require('body-parser')
  4. const express = require('express')
  5. const app = express()
  6. const port = 9000
  7. app.get('/user', (req, res) => {
  8. const {
  9. age,
  10. id,
  11. jsonpCallback
  12. } = req.query
  13. console.log(jsonpCallback)
  14. // 通过 传过来的参数,查数据库,返回数据库的结果
  15. // code
  16. // const result = db.query(`select age=${age},id=${id}`)
  17. res.send(`${jsonpCallback}('woshibendan')`)
  18. // laizhenhang()
  19. })
  20. app.listen(port, () => {
  21. console.log(`app is running at ${port}!`)
  22. })

JSONP 最大的优势在于兼容性好,IE 低版本不能使用 CORS 但可以使用 JSONP,缺点也很明显,请求方法单一,只支持 GET 请求。
JSONP 的安全性

  • 因为jsonp只能发送get请求,而给请求的url都会在浏览器的输入url显示,那么传给服务端的参数就会被看到
  • 防止callback参数恶意添加标签(如script),造成XSS漏洞
  • 防止callback参数意外截断js代码,特殊字符单引号双引号,换行符均存在风险
  • 防止跨域请求滥用,阻止非法站点恶意调用

# Nginx

Nginx 是一种高性能的反向代理服务器,可以用来轻松解决跨域问题。
009.jpg
正向代理帮助客户端访问客户端自己访问不到的服务器,然后将结果返回给客户端。
反向代理拿到客户端的请求,将请求转发给其他的服务器,主要的场景是维持服务器集群的负载均衡,换句话说,反向代理帮其它的服务器拿到请求,然后选择一个合适的服务器,将请求转交给它。
因此,两者的区别就很明显了,正向代理服务器是帮客户端做事情,而反向代理服务器是帮其它的服务器做事情。
好了,那 Nginx 是如何来解决跨域的呢?
比如说现在客户端的域名为client.com,服务器的域名为server.com,客户端向服务器发送 Ajax 请求,当然会跨域了,那这个时候让 Nginx 登场了,通过下面这个配置:

  1. server {
  2. listen 80;
  3. server_name client.com;
  4. location /api {
  5. proxy_pass server.com;
  6. }
  7. }

Nginx 相当于起了一个跳板机,这个跳板机的域名也是client.com,让客户端首先访问 client.com/api,这当然没有跨域,然后 Nginx 服务器作为反向代理,将请求转发给server.com,当响应返回时又将响应给到客户端,这就完成整个跨域请求的过程。