00 序 動態(tài)web技術演進史
動態(tài)web技術演進史
web技術發(fā)展的歷史,可以說也是整個互聯(lián)網發(fā)展的歷史;我們從前端和后端(服務端)兩個維度,來簡單的談一下動態(tài)web技術的演進史。
初級的靜態(tài)web時代
在最初的靜態(tài)資源時代,主要由前端主導頁面的展示,此時服務端僅僅是靜態(tài)web服務器,可以理解為只是一個承載html等頁面資源的容器。而我們的頁面,也僅僅是像報紙一樣的靜態(tài)展示,內容的修改編輯都是靠對服務器上文件的修改編輯。此時的請求僅僅是對靜態(tài)資源的請求,甚至談不上是一個接口。
ajax和cgi主導下的動態(tài)web技術拉開序幕
隨著ajax,局部動態(tài)刷新技術,和cgi,通用網關接口的出現(xiàn),標志著web時代正式進入動態(tài)時代。
ajax可以允許js為局部頁面提供請求服務,并更新瀏覽器的dom對象;而cgi作為web服務器和外部應用程序的通信標準,讓服務器可以通過cgi執(zhí)行外部程序,這兩個技術,再加上到現(xiàn)在仍然流行的mvc設計模式,產生了無數(shù)經典的web產品。
在這個背景下,請求一個cgi程序往往是/xxx/xx.cgi的形式,這種請求才真正的算是一個接口,因為他包含了具有邏輯意義的程序。
再往后,cgi程序(請注意cgi程序和cgi是兩個概念)因為低下的效率問題被時代拋棄,新的繼任者是各種cgi的“升級”選項,例如php的FastCGI,python的wsgi等等。
而我們的前端技術,也藉由nodejs技術產生了質的飛躍,產生了諸如reactjs,vue等偉大的前端框架。
nodejs讓前后端分離走向極致
傳統(tǒng)的非前后端分離式開發(fā),是由后端渲染頁面,通過一個基礎的html作為模板,將動態(tài)內容由后端動態(tài)生成,再整個傳輸?shù)椒掌?,最終展示在瀏覽器上,因此,當你請求一個這種技術下的接口時,你會發(fā)現(xiàn),接口的response里,存了整個html信息。這種開發(fā)方式雖然存在諸如方便seo,單獨開發(fā)節(jié)約人員等優(yōu)點,但是,也存在諸如可維護性,可復用性差,前后端混搭,開發(fā)效率低等問題。
而前后端分離的開發(fā)則解決了這些問題,尤其是nodejs和react的出現(xiàn),更將前后端分離幾乎做到了極致,在這開發(fā)方式下,前后端由獨立的服務運行,前端負責渲染和傳輸數(shù)據到服務端,后端負責處理數(shù)據并返回給前端,因此,在這種技術下,當你請求一個接口時,只會看到后端返回的數(shù)據(一般是json)。
后端架構演進,從單體應用架構到微服務
當網站的流量不多的時候,我們往往只需要部署一個單應用節(jié)點,將所有功能、服務都部署在一起以節(jié)約硬件成本,這就是單一應用架構;
而隨著流量的逐漸加大,提升單機硬件帶來的成本越來越高,效率卻越來越低,此時,我們可以將系統(tǒng)業(yè)務拆分成幾個互不關聯(lián)的系統(tǒng),分別部署在各自機器上,以劃清邏輯并減小壓力,這就是垂直應用架構;
當我們的業(yè)務越來越多、應用系統(tǒng)也越來越多時,自然的,我們會發(fā)現(xiàn)有些功能已經不能簡單劃分開來或者劃分不出來。此時,可以將公共業(yè)務邏輯抽離出來,將之組成獨立的服務Service應用 。而原有的、新增的應用都可以與那些獨立的Service應用交互,以此來完成完整的業(yè)務功能,這就是分布式服務架構;
隨著敏捷開發(fā)、持續(xù)交付、DevOps理論的發(fā)展和實踐,以及基于Docker等輕量級容器(LXC)部署應用和服務的成熟,微服務架構開始流行,逐漸成為應用架構的未來演進方向。通過服務的原子化拆分,以及微服務的獨立打包、部署和升級,小團隊敏捷繳費,應用的交付周期將縮短,運維成本也將大幅下降;這就是微服務架構。
引用文檔:
Dubbo介紹前篇------單一應用框架、垂直應用框架、分布式應用框架、流動計算框架,及RPC的簡介
01 HTTP協(xié)議和RESTFUL
HTTP,DNS,TCP,IP組成的一條龍式網絡服務
當我們訪問一個web頁面的時候,比如http://www.xxxx.com/xxx,其實是這么一個過程。
首先,客戶端通過DNS(域名解析系統(tǒng))解析出目標網頁的真實服務器(ip)地址;
其次,tcp三次握手建立連接,通過http協(xié)議針對這個地址發(fā)出請求報文;
然后,tcp協(xié)議分割請求報文,利用ip協(xié)議找到對方,把報文安全的傳給目標端;
再然后,當對方利用tcp協(xié)議收到并重組成完整的報文后,會再利用http協(xié)議處理報文,解析出請求目的,交由對應的請求資源后,再把結果回傳給請求端。
以上就是HTTP,DNS,TCP,IP組成的一條龍式網絡服務,下面我們針對這個過程里的一些點詳細分析。
HTTP的報文內容
一個請求的url包含了協(xié)議類型,目標地址,目標端口和資源位置以及有時候可能附帶的參數(shù)。
簡單來說,一個請求可以包括兩個部分(或者更細的劃分為4個部分),即請求頭和請求體;在請求頭中,我們重點關注以下字段:
method:主要描述了請求的請求方法,用的比較多的肯定是get和post,更多一點的還有head、delete、option、connect等等。
User-Agent:主要用于標識瀏覽端的信息,比如你通過chrome訪問,帶去的User-Agent就是chrome的信息。(為什么關注這個字段?因為某些接口限制User-Agent類型的請求)
Referer:主要標識請求來源,表示你是從哪個地方過來訪問的。(為什么關注這個字段?因為某些安全設置,接口的訪問會限制Referer來源)
Connection:標識是否保持連接(keep-alive)
cookie:標識用戶識別標志。(因為http是無狀態(tài)連接)
以上是請求頭需要關注的字段,接下來我們來看看響應頭里需要關注的字段:
access-control-allow-origin:標識了服務端是否接受以及接受哪些域可訪問,標識為*表示接受跨域請求。
status:響應碼,標識了服務端對請求的結果狀態(tài),常見的響應碼分為2xx系列(成功響應),3xx系列(重定向),4xx系列(找不到請求資源),5xx系列(服務端錯誤)。
restful開發(fā)風格:post和get能否互換?
上面介紹了請求頭里的請求方法,常見的比如post和get,都可以給服務端傳遞參數(shù),那么他們有什么區(qū)別呢?post和get,能否互換?
從請求方法的客觀定義上來說,post和get可能是以下區(qū)別,post支持的傳輸長度更長,而且不顯式的傳遞參數(shù),更加安全;而get直接用url傳遞參數(shù),傳輸長度也有限制,那么如果在雙方的交集里,post和get能否互換?
或者更加大的問一個問題,為什么,要有這么多的請求方法?我通過delete方法傳參刪除一個資源,和我通過post方法傳參刪除一個資源,不都可以達成目的嗎?那為什么還要有這么多種的方法?
這里就需要說回http的設計者了,他設計這么多方法的本意,是想大家都通過一個統(tǒng)一規(guī)范的開發(fā)方式,即restful,來操作資源。
restful的開發(fā)方式,是面向資源的開發(fā)方式,什么叫面向資源的開發(fā)方式?就是以url為資源的唯一標識符,各種請求方法都只是對該資源的操作,在這樣的開發(fā)方式下,可以規(guī)范url的格式,提高對于接口的理解。
因此,如果你的項目是遵循restful風格的項目,那么,post和get的互換就是不合適的。
熟悉restful并不是要求你的項目一定要遵從這個風格,但是,這對你對請求方法的原始設計用意是有幫助的。
02 抓包工具和mock server
Https和CA證書
在說抓包工具之前,我們有必要說明一下https的運行機理,因為這關系到那些使用中間人攻擊方式的抓包工具的原理。
我們都知道為了安全才推廣https,那么它安全在哪呢?http時代,所有傳輸?shù)亩际敲魑牡?,這就意味著,如果你的流量經由第三方中轉,那么你的所有傳輸內容都是直接暴露給第三方的。
為了解決這個問題,需要對傳輸?shù)膬热葸M行加密處理,但是因為我們的web應用和客戶端是一對多的關系,如果只采用同一種對稱加密(即加密解密使用一個密鑰)算法,那么無異于沒有加密;如果對每個客戶端采用不同的對稱加密算法通信,又要面臨對協(xié)商加密算法的過程加密的問題,否則如果你在和客戶端協(xié)商加密的過程里被第三方攔截,同樣會泄露加密方式。
加密協(xié)商過程就用到了非對稱加密,也就是公私鑰機制,私鑰可以解所有公鑰加密的內容,公鑰只能解私鑰加密的內容。這樣,服務端擁有一個私鑰,客戶端請求服務端的公鑰將協(xié)商過程加密,然后傳給服務端,服務端再用私鑰解密,就完成了使用非對稱加密加密協(xié)商過程,但這樣還有個問題,就是如果中間人攔截了公鑰請求,使用自己偽造的公鑰替換服務端回傳的公鑰,這樣客戶端使用偽造的公鑰加密內容,中間人就可以使用偽造的公鑰的私鑰解密內容。
為了防止中間人對服務端公鑰的攔截偽造,所以引入了第三方權威機構的ca證書概念,也就是權威第三方使用它的私鑰,加密服務端的公鑰,這樣,如果客戶端可以使用權威第三方的公鑰解密出服務端的公鑰,就說明這個服務端公鑰不是被偽造的;為了區(qū)別第三方給不同公司的加密,所以ca上還存在有驗證編碼,可以算出這個ca是給誰加密的,方便客戶端校驗證書正確性。
回到我們的抓包工具上,很多抓包工具(例如fiddler),如果你想抓取https的內容,那么第一步你需要信任一個CA證書,結合以上https的原理,你應該知道這是為什么了吧,這個信任的ca就是為了假裝自己是第三方機構,從而騙過瀏覽器,這就是中間人攻擊類型的抓包工具的原理。
截斷,偽造和mock
mock的定義,簡單來說就是提供虛擬數(shù)據,在一些不好構造數(shù)據的場景里提高開發(fā)或者測試效率;在這里以fiddler為例,簡述如何進行接口mock。
fiddler手動截斷并替換請求/響應值
簡單的完成請求截斷非常簡單,只要在fiddler的底欄這里點擊一下All Process按鈕右側的空白格,向上的圖標代表請求截斷,向下的圖標代表響應截斷。
當攔截模式開啟時,fiddler的請求列表界面會相應的把請求或者響應的通信標上一個攔截標志,這代表這條通信被截斷了,此時你可以在fiddler的右側工具欄對這條通信的信息進行修改,然后點擊run to completion,就可以讓你修改的內容發(fā)送給服務端或者返回給客戶端。
fiddler手動構造請求發(fā)送
通過上面的教學,我們已經可以截斷和偽造請求/響應了,但有時候我們只想要一個修改過的全新的請求,而不是一直靠截斷來完成請求的構造,這時候我們可以利用fiddler右側的Composer工具來完成。
只需要在通信列表里,將你想改造的請求拖進composer的界面,就拿到了這個請求的基本數(shù)據,當你改動完畢后,點擊Execute就可以直接發(fā)送該請求。
fiddler自動替換響應值
之前的截斷和替換,都是手動的,有沒有辦法可以自動完成截斷和替換呢?畢竟一條一條請求手動處理太麻煩了。
這里我們用到AutoResponder,他可以根據你制定的規(guī)則,自動的替換接口的返回值。
輕量級的mock server方案:anyproxy
上面講的是基于fiddler的接口mock,但如果想搭建一個mock服務fiddler還是有點稍顯笨重,這里介紹一個更加輕量級的mock服務搭建方案:anyproxy。
anyproxy是阿里出品的一個基于nodejs環(huán)境的代理服務器,他可以跨操作系統(tǒng)運行,并且支持自定義mock規(guī)則,項目也是開源的,因此適合做一個輕量級的mock server。
anyproxy需要nodejs的環(huán)境(最好是v8.0版本的),裝好npm后只需要執(zhí)行npm install -i anyproxy即可完成安裝。
anyproxy代理https請求
同fiddler一樣,anyproxy代理https請求需要首先信任他的ca證書,默認的代理接口是127.0.0.1:8001,我們可以在瀏覽的代理設置里,將lan的代理這樣設置。
設置完畢后,通過cmd執(zhí)行 anyproxy -i命令,就可以在127.0.0.1:8002這個地址觀察到抓取的流量信息。
使用rule.js制定mock規(guī)則
anyproxy的rule.js文件使用js語言,制定了五個節(jié)點作為mock點,分別是:
beforeSendRequest(發(fā)送請求前);
beforeSendResponse(返回響應前);
beforeDealHttpsRequest(啟用https解析);
onError(請求過程發(fā)生錯誤時);
onConnectError(與服務器的連接產生錯誤)。
我們在寫好rule文件后,可以在命令行通過選項 --rule 來指定規(guī)則文件,這里我們以一個小例子來說明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) {
//只做演示,很多細節(jié)未考慮~~~
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;
},
};
可以看到我們的規(guī)則寫在beforeSendResponse方法里,主要是在服務端返回響應的時候,如果是我們規(guī)定的請求,那么就替換這個請求對應返回值的內容。
接口mock和fuzz測試
先介紹一下fuzz(模糊)測試的概念,fuzz測試主要藉由構造大量的隨機或者半隨機數(shù)據,對目標系統(tǒng)做輸入,從而發(fā)現(xiàn)未知的bug或者漏洞。
現(xiàn)實中,系統(tǒng)可能只對程序預想的處理數(shù)據做兼容處理,而對一些異常的,不常規(guī)的值沒有處理或者處理的不好,我們在構造異常數(shù)據時,如果是一個接口一個接口做,工作量太大了,因此,我們可以借由mock server,配置我們的mock規(guī)則,從而實現(xiàn)對接口層面的fuzz測試。
接口mock和性能測試
接口mock為何會和性能測試搭上線?實際上,由于性能測試場景的復雜,組成一個場景的接口或者接口內部,往往是存在依賴關系的,而如果我們只想得到某一部分的性能值,就不能把這種依賴關系的性能數(shù)據算進去,這時候就需要對這種依賴關系做mock,不真實等待請求結果而是直接返回構造數(shù)據,這樣就避免了依賴關系的速度性能對待測部分的性能數(shù)據的污染。
接口mock的應用場景總結
通過上面的介紹,我們來總結一下接口mock的應用場景:
在前端開發(fā)的工作中,可以通過提前mock接口返回值,提高開發(fā)效率;
在某些場景例如支付中,可以mock請求和響應值,看是否存在校驗漏洞導致系統(tǒng)數(shù)據異常;
在模糊測試中,可以通過mock server構造隨機返回值,觀察系統(tǒng)的健壯程度;
在性能測試中,可以通過mock接口設置擋板,避免性能數(shù)據被污染;
。。。。。。