web接口測試微百科

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的簡介

動態(tài)web技術(二) --- CGI

【Web開發(fā)原理】web發(fā)展歷史

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ù)據被污染;

。。。。。。

參考資料:

在 anyproxy 上做 mock 和 fuzz 測試

https原理通俗了解

anyproxy官網

淺談HTTPS以及Fiddler抓取HTTPS協(xié)議

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
【社區(qū)內容提示】社區(qū)部分內容疑似由AI輔助生成,瀏覽時請結合常識與多方信息審慎甄別。
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發(fā)布,文章內容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

相關閱讀更多精彩內容

友情鏈接更多精彩內容