# 跨域

# Jsonp

  • jsonp是一种跨域通信手段,通过script标签的src属性实现跨域,因为浏览器的同源策略不会截断script的跨域响应。
  • 通过将前端方法名作为参数传递到服务器端,然后由服务器端注入参数之后再返回,实现服务器端向客户端通信。
  • 由于使用script标签的src属性,因此只支持get方法。
// 定义回调函数
function fn(arg) {
  // arg为服务端传来的数据
  console.log(`客户端获取数据: ${arg}`);
}

// 创建script标签
const s = document.createElement('script');
// 给script标签的src属性赋值,值为请求url,查询参数callback,需与后端对应
// fn为前端回调函数名
s.src = 'http://host:7001/test?callback=fn';
// 向html添加此标签,添加完成之后浏览器自动请求script的src对应的网址
document.getElementsByTagName('head')[0].appendChild(s);
// 等待浏览器收到响应之后,会自动执行响应内容的代码
----------------------
// 后端
// 后端获取url中的callback就是函数名
fn('test');

总流程就是,前端先注册一个函数,例如test,然后在url告诉后端,函数名是啥。

后端返回时,获取callback函数名,并执行函数,例如test('我是传给前端的内容')。

因为JS文件,返回前端时,就会执行JS文件的代码,进而调用。

# CORS

  • 跨域资源共享cors,它使用额外的HTTP头来告诉浏览器,让运行在一个origin(domain)上的Web应用被准许访问来自不同源服务器上的指定的资源。
  • 需要服务器与客户端同时支持cors跨域方式才能进行跨域请求,服务端通过设置Access-Control-Allow-Origin: *,即可开启cors允许跨域请求,使用通配符*表示允许所有不同域的源访问资源,也可单独设置指定允许的源域名。
  • 使用cors跨域时,将会在发起请求时出现2种情况:
    • 简单请求,需满足以下条件:
      • 使用get、head、post方式发起的请求。
      • Content-Type的值仅限于下列三者之一:
        • text/plain
        • multipart/form-data
        • application/x-www-form-urlencoded
      • 不满足这些条件即为预检请求
    • 预检请求
      • 需预检的请求要求必须首先使用OPTIONS方法发起一个预检请求到服务器,以获知服务器是否允许该实际请求。
      • 预检请求的使用,可以避免跨域请求对服务器的用户数据产生未预期的影响。
      • 当满足以下条件之一,将会发送预检请求:
        • 使用了下面任一HTTP方法:
          • PUT
          • DELETE
          • CONNECT
          • OPTIONS
          • TRACE
          • PATCH
        • 人为设置了对CORS安全的首部字段集合之外的其他首部字段。该集合为:
          • Accept
          • Accept-Language
          • Content-Language
          • Content-Type(需要注意额外的限制)
          • DPR
          • Downlink
          • Save-Data
          • Viewport-Width
          • Width
        • Content-Type的值不属于下列之一:
          • application/x-www-form-urlencoded
          • multipart/form-data
          • text/plain

满足以上条件之一将会发起预检请求,总共会发起2次请求,第一次为OPTIONS方式的请求,用来确定服务器是否支持跨域,如果支持,再发起第二次实际请求,否则不发送第二次请求。

# postMessage

  • postMessage可用于不同页面之间的跨域传递数据。
  • postMessage(data, origin[, source]),data为发送的数据只能发送字符串信息,origin发送目标源,指定哪些窗口能接受到消息事件,如果origin设置为*则表示无限制,source为发送消息窗口的window对象引用。
<!-- test.html -->
<iframe
    src="http://127.0.0.1:5501/postMessage.html"
    name="postIframe"
    onload="messageLoad()">
</iframe>
<script>
// 定义加载之后执行的函数,给postMessage.html发送数据
function messageLoad() {
    const url = 'http://127.0.0.1:5501/postMessage.html';
    window.postIframe.postMessage('给postMessage的数据', url);
}
// 用于监听postMessage.html的回馈,执行回调
window.addEventListener('message', event => {
    console.log(event.data);
});
</script>
----------------------------------
<!-- postMessage.html -->
<script>
// 监听test.html发来的数据,延迟1秒返回数据
window.addEventLister('message', event => {
    setTimeout(() => {
        event.source.postMessage('给test的数据', event.origin);
    }, 1000);
});
</script>
  • event对象的几个重要属性
    • data:从其他窗口发送过来的消息对象
    • type:发送消息的类型
    • source:发送消息的窗口对象
    • origin:发送消息的窗口的源

# window.name

  • 由于window.name属于全局属性,在html中的iframe加载新页面(可以是跨域),通过iframe设置的src指向的源中更改name的值,同时主页面中的name也随之更改,但是需要给iframe中的window设置为about: blank或者同源页面即可。
  • iframe使用之后应该删除,name的值只能为string类型,且数据量最大支持2MB。
<!-- test.html -->
<script>
// 封装应该用于获取数据的函数
function foo(url, func) {
    let isFirst = true;
    const ifr = document.createElement('iframe');
    loadFunc = () => {
        if (ifFirst) {
            // 设置为同源
            ifr.contentWindow.location = 'about: black';
            isFirst = false;
        } else {
            func(ifr.contentWindow.name);
            ifr.contentWindow.close();
            document.body.removeChild(ifr);
        }
    };
    ifr.src = url;
    ifr.style.display = 'none';
    document.body.appendChild(ifr);
    // 加载之后的回调
    ifr.onload = loadFunc;
}
foo(`http://127.0.0.1:5501/name.html`, data => {
    console.log(data);
});
</script>
-------------------------------------------------
<!-- name.html -->
const obj = {name: "iframe"};
// 修改name的值,必须为string类型
window.name = JSON.stringify(obj);

# document.domain

  • document.domain的值对应当前页面的域名。
  • 通过对domain设置当前域名来实现跨域,不过仅限于域名不同,但是又要属于同一个基础域名下,如http://a.baidu.comhttp://b.baidu.com这两个子域名之间才能使用domain跨域,一般用于子域名之间的跨域访问。
  • domain只能赋值为当前域名或者其基础域名,即上级域名。
<!-- test.html -->
<script>
document.domain = 'baidu.com';
const ifr = document.createElement('iframe');
ifr.src = 'a.baidu.com/test.html';
ifr.style.display = 'none';
document.body.appendChild(ifr);
ifr.onload = function() {
    const doc = ifr.contentDocument || ifr.contentWindow.document;
    // 操作domain.html的document
    ifr.onload = null;
};
</script>
--------------------------------------------------
<!-- domain.html -->
<script>
// domain.html下设置与test.html中的domain一致
document.domain = 'baidu.com';
</script>

通过设置为同源域名(只能为其基础域名),通过iframe操作另一个页面的内容。

# nginx反向代理

  • nginx反向代理,代理从客户端来的请求,转发到其代理源。
  • 配置nginx的配置文件实现代理到不同源。
// nginx.conf配置
server {
  listen 80; // 监听端口
  server_name www.baidu.com; // 匹配来源
  location / { // 匹配路径
    // 反向代理到http://127.0.0.1:3000
    proxy_pass http://127.0.0.1:3000
    // 默认入口文件
    index index.html index.hml index.jsp;
}

nginx反向代理还能实现负载均衡。

最近更新时间: 2020/7/24 17:32:29