浏览器同源策略与ajax跨域方法汇总

2019-04-05 08:04栏目:ca888圈内

二、核心问题

上面提出了在业务中看到的常见的三种模式,问题的核心就是数据交给谁去处理。数据交给后台处理,这是模式一,数据交给前端处理,这是模式二,数据交给前端分层处理,这是模式三。三种模式没有优劣之分,其使用还是得看具体场景。

既然都是数据的问题,数据从哪里来?这个问题又回到了接口。

  • 接口文档由谁来撰写和维护?
  • 接口信息的改动如何向前后端传递?
  • 如何根据接口规范拿到前后端可用的测试数据?
  • 使用哪种接口?JSON,JSONP?
  • JSONP 的安全性问题如何处理?

这一系列的问题一直困扰着奋战在前线的前端工程师和后端开发者。淘宝团队做了两套接口文档的维护工具,IMS以及DIP,不知道有没有对外开放,两个东西都是基于 JSON Schema 的一个尝试,各有优劣。JSON Schema 是对 JSON 的一个规范,类似我们在数据库中创建表一样,对每个字段做一些限制,这里也是一样的原理,可以对字段进行描述,设置类型,限制字段属性等。

接口文档这个事情,使用 JSON Schema 可以自动化生产,所以只需编写 JSON Schema 而不存在维护问题,在写好的 Schema 中多加些限制性的参数,我们就可以直接根据 Schema 生成 mock(测试) 数据。

mock 数据的外部调用,这倒是很好处理:

JavaScript

typeof callback === "function" && callback({ json: "jsonContent" })

1
2
3
typeof callback === "function" && callback({
   json: "jsonContent"
})

在请求的参数中加入 callback 参数,如 /mock/hashString?cb=callback,一般的 io(ajax) 库都对异步数据获取做了封装,我们在测试的时候使用 jsonp,回头上线,将 dataType 改成 json 就行了。

JavaScript

IO({ url: "", dataType: "jsonp", //json success: function(){} })

1
2
3
4
5
IO({
  url: "http://barretlee.com",
  dataType: "jsonp", //json
  success: function(){}
})

这里略微麻烦的是 POST 方法,jsonp 只能使用 get 方式插入 script 节点去请求数据,但是 POST,只能呵呵了。

这里的处理也有多重方式可以参考:

  • 修改 Hosts,让 mock 的域名指向开发域名
  • mock 设置 header 响应头,Access-Allow-Origin-Control

对于如何拿到跨域的接口信息,我也给出几个参考方案:

  • fiddler 替换包,好像是支持正则的,感兴趣的可以研究下(求分享研究结果,因为我没找到正则的设置位置)
  • 使用 HTTPX 或者其他代理工具,原理和 fiddler 类似,不过可视化效果(体验)要好很多,毕竟人家是专门做代理用的。
  • 自己写一段脚本代理,也就是本地开一个代理服务器,这里需要考虑端口的占用问题。其实我不推荐监听端口,一个比较不错的方案是本地请求全部指向一个脚本文件,然后脚本转发URL,如:

JavaScript

原始请求: 在ajax请求的时候: $.ajax({ url: "" });

1
2
3
4
5
原始请求:http://barretlee.com/api/test.json
在ajax请求的时候:
$.ajax({
  url: "http://<local>/api.php?path=/api/text.json"
});
  • php中处理就比较简单啦:

JavaScript

if(!isset($_GET["page"])){ echo 0; exit(); } echo file_get_contents($_GET["path"]);

1
2
3
4
5
if(!isset($_GET["page"])){
  echo 0;
  exit();
}
echo file_get_contents($_GET["path"]);
  • Ctrl S,保存把线上的接口数据到本地的api文件夹吧-_-||

题目2: 前后端开发联调需要注意哪些事情?后端接口完成前如何 mock 数据?

注意事项:大的方面我需要什么,我给你什么.具体来讲:
1.约定后端发回的数据格式.数组.JSON.文本.二进制文件
2.约定请求方式:post或者get
3.约定接口名字/路径
4.约定发送的参数
mock数据
要完整运行前端代码,通常并不需要完整的后端环境,我们只要在mock server中实现以下几点就行了:

  • 能渲染模板
  • 实现请求路由映射
  • 数据接口代理到生产或者
![](https://upload-images.jianshu.io/upload_images/5927991-9f59e15fb04d32f8.png)

image.png



测试环境

参考

题目1: ajax 是什么?有什么作用?

  • ajax 是什么
    AJAX全称为“Asynchronous JavaScript and XML”(异步JavaScript和XML)
    ajax是一种在无需重新加载整个网页的情况下,能够更新部分网页的技术
    ajax是一种用于创建快速动态网页的技术。通过在后台与服务器进行少量数据交换。
    ajax可以使网页实现异步更新。这意味着可以在不重新加载整个网页的情况下,对网页的某部分进行更新。
    而传统的网页(不使用ajax)如果需要更新内容,必须重载整个网页面。
  • ajax的作用:
    1、最大的一点是页面无刷新,用户的体验非常好。
    2、使用异步方式与服务器通信,具有更加迅速的响应能力。。
    3、可以把以前一些服务器负担的工作转嫁到客户端,利用客户端闲置的能力来处理,减轻服务器和带宽的负担,节约空间和宽带租用成本。并且减轻服务器的负担,ajax的原则是“按需取数据”,可以最大程度的减少冗余请求,和响应对服务器造成的负担。
    4、基于标准化的并被广泛支持的技术,不需要下载插件或者小程序。

使用代理(proxy)

这种方法本质上仍然遵循了同源政策,只是换了一个请求的思路,将请求移至了后端。

我们知道,同源政策是浏览器层面的限制。那么,如果我们不在前端跨域,而将“跨域”的任务交给后端服务,是否就规避了同源政策呢?是的。

这就是“代理”。这个代理可以将我们的请求转发,而后端并不会有所谓的同源政策限制。这个“代理”也可以理解为一个同域的后端服务。

由于我们的myweb是一个完整的web项目(包括前端部分和后端服务部分),因此,我们可以在myweb项目的后端添加一个proxy接口,专门处理跨域ajax请求的转发。

const express = require('express');
const router = express.Router();
const request = require('request');

router.get('*', (req, res, next) => {
    let path = req.path.replace(/^/proxy/, '');
    request.get(`http://127.0.0.1:3000${path}`, (err, response) => {
        res.json(JSON.parse(response.body));
    });
});

module.exports = router;

这样,我们在前端访问/proxy/info/normal后,就会自动转发到http://127.0.0.1:3000/proxy/info/normal

前端ajax部分如下:

document.getElementById('btn-1').addEventListener('click', function() {
    var xhr = new XMLHttpRequest();
    xhr.onreadystatechange = function() {
        if (xhr.readyState === 4 && xhr.status === 200) {
            alert(xhr.responseText);
        }
    }
    xhr.open('get', '/proxy/info/normal');
    xhr.send(null);
});

该方法的优点很明显:不需要第三方服务http://127.0.0.1:3000/info/normal进行任何改造。

当然,该方法也有一些缺点:

  • 首先,需要你有一个自己的后端服务能够接收并转发请求。如果你进行本地的纯静态页面开发,则需要一些浏览器插件或自动化工具中集成的本地服务器来实现。
  • 此外,如果请求包含一些特殊的请求头(例如cookie等等),需要在转发时特殊处理。

下面两种方法则需要第三方服务端或多或少进行配合改造。

三、小结

本文只是对前后端协作存在的问题和现有的几种常见模式做了简要的列举,JSON Schema 具体如何去运用,还有接口的维护问题、接口信息的获取问题没有具体阐述,这个后续有时间会整理下我对他的理解。

赞 2 收藏 1 评论

ca888会员登录 1

题目1: ajax 是什么?有什么作用?

ajax(Asynchronous JavaScript and XML 异步的JavaScript与XML技术),他利用HTML.CSS.Javascript.XML以及最最最重要的XMLHttpResponse接口向后端发送http请求实现不刷新页面的情况下更新部分页面内容.
步骤:
1.构建ajax, xhr = new XMLHttpResponse
2.设置发送方式.接口名字,参数. xhr.open('get','/loadMore?index=' pageIndex 'length=5',true)
3.设置header,文件格式等参数
4.发送HTTP请求,xhr.send()
5.接受数据,对数据进行操作
6.更新页面相关内容
作用:不刷新页面的情况下,更新部分页面内容,不耽误用户其他操作,提升用户体验.

题目4:实现加载更多的功能,效果范例380,后端在本地使用server-mock来模拟数据

github代码

为什么实际开发中会有跨域ajax请求

根据上文的内容我们可以知道,由于浏览器同源政策的影响,跨域的ajax请求是不被允许。那么在实际的开发、应用中,是否有跨域ajax的场景呢?

答案是肯定的。

那么有哪些场景会有跨域ajax的需求呢?

  1. 当你调用一个现有的API或公开API:想象一下,你接到了一个新需求,需要在当前开发的新闻详细页http://www.yournews.com/p/123展示该新闻的相关推荐。令人欣慰的是,推荐的接口已经在你们公司的其他产品线里实现了,你只需要给该接口一个query即可:http://www.mynews.com/recommend?query=123。然而问题来了——你发起了一个跨域请求。

  2. 前后端分离的开发模式下,在本地进行接口联调时:也许在你的项目里,你想尝试前后端分离的开发模式。你在本地开发时,mock了一些假数据来帮助自己本地开发。而有一天,你希望在本地和后端同学进行联调。此时,后端rd的接口地址和你发生了跨域问题。这阻止了你们的联调,你只能继续使用你mock的假数据。

上面只是列举了存在跨域的两个最为常见的场景,这足以说明跨域请求在实际开发中确实经常出现。

谈谈前后端的分工协作

2015/05/15 · HTML5 · 1 评论 · Web开发

原文出处: 小胡子哥的博客(@Barret李靖)   

前后端分工协作是一个老生常谈的大话题,很多公司都在尝试用工程化的方式去提升前后端之间交流的效率,降低沟通成本,并且也开发了大量的工具。但是几乎没有一种方式是令双方都很满意的。事实上,也不可能让所有人都满意。根本原因还是前后端之间的交集不够大,交流的核心往往只限于接口及接口往外扩散的一部分。这也是为什么很多公司在招聘的时候希望前端人员熟练掌握一门后台语言,后端同学了解前端的相关知识。

题目4:实现加载更多的功能,效果范例338,后端在本地使用server-mock来模拟数据

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-6">
    <title>load-more</title>

    <style>
        a{
            text-decoration: none;
        }
        .ct {
            margin: 0;
            padding: 0;
            vertical-align: middle;
            text-align: center;
        }
        .ct li{
            list-style: none;
            border: 1px solid red;
            padding: 10px;
            margin: 10px 20px;
            color: blue;
            cursor: pointer;
            border-radius: 4px;
        }
        .ct li:hover {
            background-color: green;
            color: azure;
        }
        .btn-ct {
            text-align: center;
        }
        .btn {
            display: inline-block;
            margin: 20px auto;
            padding: 10px;
            background: yellowgreen;
            font-size: 18px;
            color: red;
            border-radius: 5px;

        }
        .btn:hover {
            background-color: deepskyblue;
            color: firebrick;
        }
    </style>
</head>
<body>
    <ul class="ct">
        <li>新闻0</li>
    </ul>
    <div class="btn-ct"><a  href="##" class="btn">加载更多</a></div>
</body>
<script>
    var ct = document.querySelector('.ct')
    var btn = document.querySelector('.btn')
    var pageIndex = 1
    var dataArrive = true//状态锁,防止重复点击
    function loadMore(){
        if(dataArrive === false){//用来判断是否为重复无效点击
            return
        }
        dataArrive = false
        var xhr = new XMLHttpRequest()
        xhr.onreadystatechange = function(){
            if (xhr.readyState === 4){
                if (xhr.status === 200 || xhr.status === 304){
                    console.log(xhr.responseText)
                    var results = JSON.parse(xhr.responseText)
                    console.log(results.length)
                    var fragment = document.createDocumentFragment()
                    for(var i = 0;i < results.length; i  ){
                        console.log(i)
                        var node = document.createElement('li')
                        node.innerText = results[i]
                        fragment.appendChild(node)
                        pageIndex  = 1;
                    }
                    ct.appendChild(fragment)
                }else{
                    console.log('error')
                }
                dataArrive = true
            }
        }
        xhr.open('get','/loadMore?index=' pageIndex '&length=5',true)
        xhr.send()
    }
    btn.addEventListener('click',loadMore)
</script>
</html>

// 服务端 router.js


app.get('/loadMore', function(req, res) {

  var curIdx = req.query.index
  var len = req.query.length
  var data = []

  for(var i = 0; i < len; i  ) {
    data.push('新闻'   (parseInt(curIdx)   i))
  }

  setTimeout(function(){
    res.send(data);
  },3000)

});

题目2:前后端开发联调需要注意哪些事情?后端接口完成前如何 mock 数据?

  • 前后端联调是一种 真实业务数据 和 本地mock数据 之间来回切换以达到 前后端分离架构 下的不同开发速度时 数据交换 的一种方式方法。

  • 注意事项:
    1.确定要传输的数据以及数据类型。
    2.确定接口名称、请求和响应的类型格式(get或是post)
    3.请求的数据中参数的名称

    如: { index:3
        length:5  }
    

    4.响应的数据的格式。如JSON格式的字符串

  • 后端接口完成前如何 mock 数据
    mock数据:当后端接口没有完成前,前端需要模仿后台数据,以测试处理前端的请求。
    1.使用nodejs搭建一个web服务器,返回我们想要的数据
    2.安装server-mock,在当前的文件夹下创建 router.js,接受处理请求数据

跨域的一些方案

了解了上面的内容后,下面就来介绍一下在实践中常用的三种ajax跨域方案。这部分的实例代码可以在这里看到:cross-domain-demo

假设这样一个跨域场景:目前有两个项目

  • myweb,这个就是我们目前开发的项目,是一个独立的站点。
  • thirdparty,表示我们需要调用到的第三方(third-party)后端服务,myweb项目就是需要调用它的接口。

为了简化不必要的代码编写过程,示例使用express-generator来快速生成myweb与thirdparty这两个应用,其中thirdparty我们只需要用到后端接口部分。

npm install express-generator -g
express --view=pug myweb
express --view=pug thirdparty

在myweb中,index页面 http://127.0.0.1:8085需要跨域访问server中的http://127.0.0.1:3000/info/normal这个接口的信息。前端操作是:当点击buttonca888会员登录,时就会去获取info,并alert出来。
跨域访问的接口http://127.0.0.1:3000/info/normal代码如下:

const express = require('express');
const router = express.Router();

const data = {
    name: 'alienzhou',
    desc: 'a developer'
};

router.get('/normal', (req, res, next) => {
    res.json(data);
});

然后是http://127.0.0.1:8085index页面的部分的javascript

// http://127.0.0.1:8085  -- index.js
document.getElementById('btn-1').addEventListener('click', function() {
    var xhr = new XMLHttpRequest();
    xhr.onreadystatechange = function() {
        if (xhr.readyState === 4 && xhr.status === 200) {
            alert(xhr.responseText);
        }
    }
    xhr.open('get', 'http://127.0.0.1:3000/info/normal');
    xhr.send(null);
});

点击btn-1,在控制台中就会出现如下错误,这个跨域ajax请求受到了同源策略的限制。

[Error] Origin http://127.0.0.1:8085 is not allowed by Access-Control-Allow-Origin.
[Error] Failed to load resource: Origin http://127.0.0.1:8085 is not allowed by Access-Control-Allow-Origin. (normal, line 0)
[Error] XMLHttpRequest cannot load http://127.0.0.1:3000/info/normal due to access control checks.

下面来讲具体的三种解决方案:

版权声明:本文由ca888发布于ca888圈内,转载请注明出处:浏览器同源策略与ajax跨域方法汇总