00 序 动态web技术演进史
动态web技术演进史
web技术发展的历史,可以说也是整个互联网发展的历史;我们从前端和后端(服务端)两个维度,来简单的谈一下动态web技术的演进史。
初级的静态web时代
在最初的静态资源时代,主要由前端主导页面的展示,此时服务端仅仅是静态web服务器,可以理解为只是一个承载html等页面资源的容器。而我们的页面,也仅仅是像报纸一样的静态展示,内容的修改编辑都是靠对服务器上文件的修改编辑。此时的请求仅仅是对静态资源的请求,甚至谈不上是一个接口。
ajax和cgi主导下的动态web技术拉开序幕
随着ajax,局部动态刷新技术,和cgi,通用网关接口的出现,标志着web时代正式进入动态时代。
ajax可以允许js为局部页面提供请求服务,并更新浏览器的dom对象;而cgi作为web服务器和外部应用程序的通信标准,让服务器可以通过cgi执行外部程序,这两个技术,再加上到现在仍然流行的mvc设计模式,产生了无数经典的web产品。
在这个背景下,请求一个cgi程序往往是/xxx/xx.cgi的形式,这种请求才真正的算是一个接口,因为他包含了具有逻辑意义的程序。
再往后,cgi程序(请注意cgi程序和cgi是两个概念)因为低下的效率问题被时代抛弃,新的继任者是各种cgi的“升级”选项,例如php的FastCGI,python的wsgi等等。
而我们的前端技术,也藉由nodejs技术产生了质的飞跃,产生了诸如reactjs,vue等伟大的前端框架。
nodejs让前后端分离走向极致
传统的非前后端分离式开发,是由后端渲染页面,通过一个基础的html作为模板,将动态内容由后端动态生成,再整个传输到服务器,最终展示在浏览器上,因此,当你请求一个这种技术下的接口时,你会发现,接口的response里,存了整个html信息。这种开发方式虽然存在诸如方便seo,单独开发节约人员等优点,但是,也存在诸如可维护性,可复用性差,前后端混搭,开发效率低等问题。
而前后端分离的开发则解决了这些问题,尤其是nodejs和react的出现,更将前后端分离几乎做到了极致,在这开发方式下,前后端由独立的服务运行,前端负责渲染和传输数据到服务端,后端负责处理数据并返回给前端,因此,在这种技术下,当你请求一个接口时,只会看到后端返回的数据(一般是json)。
后端架构演进,从单体应用架构到微服务
当网站的流量不多的时候,我们往往只需要部署一个单应用节点,将所有功能、服务都部署在一起以节约硬件成本,这就是单一应用架构;
而随着流量的逐渐加大,提升单机硬件带来的成本越来越高,效率却越来越低,此时,我们可以将系统业务拆分成几个互不关联的系统,分别部署在各自机器上,以划清逻辑并减小压力,这就是垂直应用架构;
当我们的业务越来越多、应用系统也越来越多时,自然的,我们会发现有些功能已经不能简单划分开来或者划分不出来。此时,可以将公共业务逻辑抽离出来,将之组成独立的服务Service应用 。而原有的、新增的应用都可以与那些独立的Service应用交互,以此来完成完整的业务功能,这就是分布式服务架构;
随着敏捷开发、持续交付、DevOps理论的发展和实践,以及基于Docker等轻量级容器(LXC)部署应用和服务的成熟,微服务架构开始流行,逐渐成为应用架构的未来演进方向。通过服务的原子化拆分,以及微服务的独立打包、部署和升级,小团队敏捷缴费,应用的交付周期将缩短,运维成本也将大幅下降;这就是微服务架构。
引用文档:
Dubbo介绍前篇------单一应用框架、垂直应用框架、分布式应用框架、流动计算框架,及RPC的简介
01 HTTP协议和RESTFUL
HTTP,DNS,TCP,IP组成的一条龙式网络服务
当我们访问一个web页面的时候,比如http://www.xxxx.com/xxx,其实是这么一个过程。
首先,客户端通过DNS(域名解析系统)解析出目标网页的真实服务器(ip)地址;
其次,tcp三次握手建立连接,通过http协议针对这个地址发出请求报文;
然后,tcp协议分割请求报文,利用ip协议找到对方,把报文安全的传给目标端;
再然后,当对方利用tcp协议收到并重组成完整的报文后,会再利用http协议处理报文,解析出请求目的,交由对应的请求资源后,再把结果回传给请求端。
以上就是HTTP,DNS,TCP,IP组成的一条龙式网络服务,下面我们针对这个过程里的一些点详细分析。
HTTP的报文内容
一个请求的url包含了协议类型,目标地址,目标端口和资源位置以及有时候可能附带的参数。
简单来说,一个请求可以包括两个部分(或者更细的划分为4个部分),即请求头和请求体;在请求头中,我们重点关注以下字段:
method:主要描述了请求的请求方法,用的比较多的肯定是get和post,更多一点的还有head、delete、option、connect等等。
User-Agent:主要用于标识浏览端的信息,比如你通过chrome访问,带去的User-Agent就是chrome的信息。(为什么关注这个字段?因为某些接口限制User-Agent类型的请求)
Referer:主要标识请求来源,表示你是从哪个地方过来访问的。(为什么关注这个字段?因为某些安全设置,接口的访问会限制Referer来源)
Connection:标识是否保持连接(keep-alive)
cookie:标识用户识别标志。(因为http是无状态连接)
以上是请求头需要关注的字段,接下来我们来看看响应头里需要关注的字段:
access-control-allow-origin:标识了服务端是否接受以及接受哪些域可访问,标识为*表示接受跨域请求。
status:响应码,标识了服务端对请求的结果状态,常见的响应码分为2xx系列(成功响应),3xx系列(重定向),4xx系列(找不到请求资源),5xx系列(服务端错误)。
restful开发风格:post和get能否互换?
上面介绍了请求头里的请求方法,常见的比如post和get,都可以给服务端传递参数,那么他们有什么区别呢?post和get,能否互换?
从请求方法的客观定义上来说,post和get可能是以下区别,post支持的传输长度更长,而且不显式的传递参数,更加安全;而get直接用url传递参数,传输长度也有限制,那么如果在双方的交集里,post和get能否互换?
或者更加大的问一个问题,为什么,要有这么多的请求方法?我通过delete方法传参删除一个资源,和我通过post方法传参删除一个资源,不都可以达成目的吗?那为什么还要有这么多种的方法?
这里就需要说回http的设计者了,他设计这么多方法的本意,是想大家都通过一个统一规范的开发方式,即restful,来操作资源。
restful的开发方式,是面向资源的开发方式,什么叫面向资源的开发方式?就是以url为资源的唯一标识符,各种请求方法都只是对该资源的操作,在这样的开发方式下,可以规范url的格式,提高对于接口的理解。
因此,如果你的项目是遵循restful风格的项目,那么,post和get的互换就是不合适的。
熟悉restful并不是要求你的项目一定要遵从这个风格,但是,这对你对请求方法的原始设计用意是有帮助的。
02 抓包工具和mock server
Https和CA证书
在说抓包工具之前,我们有必要说明一下https的运行机理,因为这关系到那些使用中间人攻击方式的抓包工具的原理。
我们都知道为了安全才推广https,那么它安全在哪呢?http时代,所有传输的都是明文的,这就意味着,如果你的流量经由第三方中转,那么你的所有传输内容都是直接暴露给第三方的。
为了解决这个问题,需要对传输的内容进行加密处理,但是因为我们的web应用和客户端是一对多的关系,如果只采用同一种对称加密(即加密解密使用一个密钥)算法,那么无异于没有加密;如果对每个客户端采用不同的对称加密算法通信,又要面临对协商加密算法的过程加密的问题,否则如果你在和客户端协商加密的过程里被第三方拦截,同样会泄露加密方式。
加密协商过程就用到了非对称加密,也就是公私钥机制,私钥可以解所有公钥加密的内容,公钥只能解私钥加密的内容。这样,服务端拥有一个私钥,客户端请求服务端的公钥将协商过程加密,然后传给服务端,服务端再用私钥解密,就完成了使用非对称加密加密协商过程,但这样还有个问题,就是如果中间人拦截了公钥请求,使用自己伪造的公钥替换服务端回传的公钥,这样客户端使用伪造的公钥加密内容,中间人就可以使用伪造的公钥的私钥解密内容。
为了防止中间人对服务端公钥的拦截伪造,所以引入了第三方权威机构的ca证书概念,也就是权威第三方使用它的私钥,加密服务端的公钥,这样,如果客户端可以使用权威第三方的公钥解密出服务端的公钥,就说明这个服务端公钥不是被伪造的;为了区别第三方给不同公司的加密,所以ca上还存在有验证编码,可以算出这个ca是给谁加密的,方便客户端校验证书正确性。
回到我们的抓包工具上,很多抓包工具(例如fiddler),如果你想抓取https的内容,那么第一步你需要信任一个CA证书,结合以上https的原理,你应该知道这是为什么了吧,这个信任的ca就是为了假装自己是第三方机构,从而骗过浏览器,这就是中间人攻击类型的抓包工具的原理。
截断,伪造和mock
mock的定义,简单来说就是提供虚拟数据,在一些不好构造数据的场景里提高开发或者测试效率;在这里以fiddler为例,简述如何进行接口mock。
fiddler手动截断并替换请求/响应值
简单的完成请求截断非常简单,只要在fiddler的底栏这里点击一下All Process按钮右侧的空白格,向上的图标代表请求截断,向下的图标代表响应截断。
当拦截模式开启时,fiddler的请求列表界面会相应的把请求或者响应的通信标上一个拦截标志,这代表这条通信被截断了,此时你可以在fiddler的右侧工具栏对这条通信的信息进行修改,然后点击run to completion,就可以让你修改的内容发送给服务端或者返回给客户端。
fiddler手动构造请求发送
通过上面的教学,我们已经可以截断和伪造请求/响应了,但有时候我们只想要一个修改过的全新的请求,而不是一直靠截断来完成请求的构造,这时候我们可以利用fiddler右侧的Composer工具来完成。
只需要在通信列表里,将你想改造的请求拖进composer的界面,就拿到了这个请求的基本数据,当你改动完毕后,点击Execute就可以直接发送该请求。
fiddler自动替换响应值
之前的截断和替换,都是手动的,有没有办法可以自动完成截断和替换呢?毕竟一条一条请求手动处理太麻烦了。
这里我们用到AutoResponder,他可以根据你制定的规则,自动的替换接口的返回值。
轻量级的mock server方案:anyproxy
上面讲的是基于fiddler的接口mock,但如果想搭建一个mock服务fiddler还是有点稍显笨重,这里介绍一个更加轻量级的mock服务搭建方案:anyproxy。
anyproxy是阿里出品的一个基于nodejs环境的代理服务器,他可以跨操作系统运行,并且支持自定义mock规则,项目也是开源的,因此适合做一个轻量级的mock server。
anyproxy需要nodejs的环境(最好是v8.0版本的),装好npm后只需要执行npm install -i anyproxy即可完成安装。
anyproxy代理https请求
同fiddler一样,anyproxy代理https请求需要首先信任他的ca证书,默认的代理接口是127.0.0.1:8001,我们可以在浏览的代理设置里,将lan的代理这样设置。
设置完毕后,通过cmd执行 anyproxy -i命令,就可以在127.0.0.1:8002这个地址观察到抓取的流量信息。
使用rule.js制定mock规则
anyproxy的rule.js文件使用js语言,制定了五个节点作为mock点,分别是:
beforeSendRequest(发送请求前);
beforeSendResponse(返回响应前);
beforeDealHttpsRequest(启用https解析);
onError(请求过程发生错误时);
onConnectError(与服务器的连接产生错误)。
我们在写好rule文件后,可以在命令行通过选项 --rule 来指定规则文件,这里我们以一个小例子来说明rule的编写。
任务:在百度搜索“深圳明源怎么样”时,把结果改为“666666666666”。
以下是rule.js文件的内容:
'use strict';
module.exports = {
summary: 'the default rule for AnyProxy',
/**
*
*
* @param {object} requestDetail
* @param {string} requestDetail.protocol
* @param {object} requestDetail.requestOptions
* @param {object} requestDetail.requestData
* @param {object} requestDetail.response
* @param {number} requestDetail.response.statusCode
* @param {object} requestDetail.response.header
* @param {buffer} requestDetail.response.body
* @returns
*/
*beforeSendRequest(requestDetail) {
return null;
},
/**
*
*
* @param {object} requestDetail
* @param {object} responseDetail
*/
*beforeSendResponse(requestDetail, responseDetail) {
//只做演示,很多细节未考虑~~~
if(requestDetail.url.indexOf("https://www.baidu.com/s?")>-1)
{
const url_parmeters = requestDetail.url.split("s?")[1].split("&");
//console.log(url_parmeters);
for (var i=0;i<url_parmeters.length;i++){
//console.log(url_parmeters[i]);
if (url_parmeters[i].indexOf("wd=")>-1){
const wd = url_parmeters[i].split("=")[1];
if(wd === "%E6%B7%B1%E5%9C%B3%E6%98%8E%E6%BA%90%E6%80%8E%E4%B9%88%E6%A0%B7"){
const newResponse = responseDetail.response;
newResponse.body = '66666666666666666666';
console.log("will change~~~~~~~");
return new Promise((resolve, reject) => {
resolve({ response: newResponse});
});
};
};
//console.log("will show the wd is : ~~~~~~~~~");
//console.log(wd);
};
//console.log(requestDetail);
}else {
console.log("not baidu!!!");
};
return null;
},
/**
* default to return null
* the user MUST return a boolean when they do implement the interface in rule
*
* @param {any} requestDetail
* @returns
*/
*beforeDealHttpsRequest(requestDetail) {
return true;
},
/**
*
*
* @param {any} requestDetail
* @param {any} error
* @returns
*/
*onError(requestDetail, error) {
return null;
},
/**
*
*
* @param {any} requestDetail
* @param {any} error
* @returns
*/
*onConnectError(requestDetail, error) {
return null;
},
};
可以看到我们的规则写在beforeSendResponse方法里,主要是在服务端返回响应的时候,如果是我们规定的请求,那么就替换这个请求对应返回值的内容。
接口mock和fuzz测试
先介绍一下fuzz(模糊)测试的概念,fuzz测试主要藉由构造大量的随机或者半随机数据,对目标系统做输入,从而发现未知的bug或者漏洞。
现实中,系统可能只对程序预想的处理数据做兼容处理,而对一些异常的,不常规的值没有处理或者处理的不好,我们在构造异常数据时,如果是一个接口一个接口做,工作量太大了,因此,我们可以借由mock server,配置我们的mock规则,从而实现对接口层面的fuzz测试。
接口mock和性能测试
接口mock为何会和性能测试搭上线?实际上,由于性能测试场景的复杂,组成一个场景的接口或者接口内部,往往是存在依赖关系的,而如果我们只想得到某一部分的性能值,就不能把这种依赖关系的性能数据算进去,这时候就需要对这种依赖关系做mock,不真实等待请求结果而是直接返回构造数据,这样就避免了依赖关系的速度性能对待测部分的性能数据的污染。
接口mock的应用场景总结
通过上面的介绍,我们来总结一下接口mock的应用场景:
在前端开发的工作中,可以通过提前mock接口返回值,提高开发效率;
在某些场景例如支付中,可以mock请求和响应值,看是否存在校验漏洞导致系统数据异常;
在模糊测试中,可以通过mock server构造随机返回值,观察系统的健壮程度;
在性能测试中,可以通过mock接口设置挡板,避免性能数据被污染;
。。。。。。
网友评论