【轉載】前端工程精粹(三):本地調試與數據模擬

自Web項目誕生以來,程序員們漸漸把整個項目的開發拆分爲了前端與後端兩個部分。隨着項目複雜度的不斷增長,同時爲了快速迭代出更多Web項目,IT界就把兩個部分的工做分紅了兩個工種來完成--前端開發工程師以及後端XX語言開發工程師(咱們在此簡稱爲FE與RD)。因此,在一個Web項目開發過程當中,就出現了先後端定義數據接口、參數等等工做,同時產生了一個巨大的耦合問題--前端工程師徹底須要依賴後端工程師的數據接口以及後端聯調環境。當一個FE快速完成了頁面的搭建,須要後端數據來完成頁面交互等工做時,他惟一能作的就是等待RD完成他的工做,有時甚至還須要RD來搭建一個聯調環境等等。php

也許更多時候狀況比我上述所說的更復雜,須要項目管理者協調好項目進度、先後端協同開發等等問題。固然更多時候FE但願本身能解決這些問題,靠別人不如靠本身,對吧?這時候爲了避免依賴後端工程師,能夠本身搞定後端環境及數據,都須要準備什麼呢?下面我來舉例:前端

  • 完整的server支持,能夠真實運行模板、後端程序等,同時最好知足能夠跨平臺運行、支持經常使用後端語言(java、php)等
  • 模擬url請求,可以有控制url請求的能力,不是單純的直接訪問返回內容的server,經過控制url來解決ajax請求模擬返回的功能
  • 數據模擬,有本地數據mock的能力,分離模板與對後端數據的依賴,使得fe能獨立於rd進行項目開發

若是能夠達到以上要求,你便能擁有一套完整的本地開發環境,擁有獨立開發前端項目的環境以及解決與後端開發的耦合問題。使用過FIS2.0的用戶也許會發現,FIS2.0能夠完整解決以上的問題,在任何地方、任何環境均可以獨立開發,接下來我爲你們介紹下FIS本地開發環境是如何作到這些要求的。java

一個輕巧獨立的服務器

咱們須要一個什麼樣的Server?程序員

  • 能夠監聽請求,負責對頁面請求進行響應
  • 後端語言的解析能力,好比可運行java、php等,不是簡單的靜態Web服務器
  • 易搭建,沒有繁瑣的安裝過程,儘量的不依賴其餘複雜環境
  • 性能與可伸縮性,可快速的響應請求,同時穩定響應必定級別的併發數
  • 平臺需求,可跨平臺運行,解決使用不一樣平臺開發的用戶需求
  • 可靠性,可穩定長期運行,有服務器異常處理機制等

以上這些就是咱們總結出一個合理可靠輕巧的服務器須要達到的要求,它是本地開發環境的基石。面對需求,咱們須要的就是解決這些需求,固然從最開始用很挫的方式一步一步嘗試,到最後發現須要怎麼實現這個server,經歷的痛苦也是不言而喻的。最後咱們發現須要實現如下內容以及找到了一些匹配的「輪子」來作這些事:ajax

  • 爲了可跨平臺部署及易搭建,咱們選擇了用JAVA開發。
  • 爲了知足性能需求,咱們須要多個CGI進程來處理併發請求,同時須要一個隊列來保證接受請求不會丟失
  • 爲了能夠運行PHP程序,咱們選擇使用PHP-CGI進行解析以及經過Fastcgi協議與其通訊
  • 爲了可擴展不一樣的Web服務,咱們須要一個可靈活擴展的Servlet容器,可處理不一樣的Web應用
  • 爲了保證服務器的穩定性,咱們須要一個服務器守護進程,保證服務器是正常運行的

咱們須要一個強大的HTTP SERVER來監聽請求、分析HTTP協議、建立socket通信,同時還得須要一個Servlet容器來擴展不一樣的Servlet服務,進行不一樣Web應用的解析,所以咱們選擇了JETTY來做爲SERVER的Web服務器。JETTY可作做爲Web服務器嵌入到JAVA程序中,並且是輕量級、性能極高的,同時提供靈活可擴展的Servlet容器,知足開發針對各種Web應用的業務Servlet。apache

選擇了JETTY,就意味着sever天生就能夠運行JSP/Servlet,剩下咱們須要解決的就是如何運行PHP了。爲了跨平臺且知足高效的性能,咱們選擇了使用FASTCGI開發擴展與PHP-CGI進行通訊,server將CGI環境變量和標準輸入發送到FastCGI子進程php-cgi,子進程完成處理後將標準輸出和錯誤信息從同一鏈接返回Server。咱們爲了能夠解決併發請求必須啓動多個PHP-CGI進程,保持能夠多線程處理請求。同時須要一個進程管理器對PHP-CGI進行管理,好比當PHP-CGI處理了幾千個請求有內存溢出現象時,須要KILL掉從新啓動新的PHP-CGI進程,以及當PHP-CGI進程出現異常狀況掛掉後,會及時發現且啓動新的進程保持可用的進程數。後端

固然你會發現作這些事是十分困難的,須要將HTTP SERVER與PHP-CGI創建連接、經過FASTCGI協議封裝請求與PHP-CGI進行通訊等等。咱們最開始也嘗試過且成功的運行了服務器,只是發現並非那麼完美,特別是在進程管理方面。最後咱們發現了一個牛逼且輕巧的東西——php-java-bridge,利用其對PHP-CGI封裝的FastCGIServlet,與JETTY進行完美對接,可在多平臺且高性能的運行PHP。其實php-java-bridge的做用不止於此,它最厲害的地方是能夠在PHP中運行JAVA程序,這就是另一個擴展點了。服務器

有了以上這些開源的東西,咱們要作的就是把這個服務器如何搭建起來,創建對應的Web應用。JETTY做爲內嵌Web服務器,須要一個基礎的Web.xml初始化參數,同時創建WebAppContext來負責處理Web應用請求。在Jetty中,有幾個比較重要的模塊:前端工程師

  • Connector負責解析服務器請求併產生應答,不一樣的Connector用於處理不一樣協議的請求。
  • Handler用於處理通過Connector解析的請求併產生應答內容,一樣能夠經過配置不一樣的Handler來負責處理不一樣的請求。
  • TheadPool:管理和調度多個線程,用於服務於多個鏈接請求。
  • Server表明一個Jetty服務器對象,主要做用是協同Connector、Handler和TheadPool的工做。

JETTY啓動須要作的事,啓動ThreadPool線程池,啓動設置到 Server 的 Handler,一般這個 Handler 會有不少子 Handler,這些 Handler 將組成一個 Handler 鏈。最後會啓動 Connector,打開端口,接受客戶端請求。在啓動handler時,會啓動Handler鏈上的子Handler,好比咱們針對運行一個Web應用程序建立一個WebAppContext,同時在WebAppContext初始化時設置處理請求時對應的Servlet,這樣配置的請求都會傳送到這個WebAppContext進行處理。多線程

下面一段僞代碼說明如何啓動Jetty及配置可處理PHP程序的Web應用程序:

//context
HandlerCollection hc = new HandlerCollection();
WebAppContext context = new WebAppContext(root, "/");

//set default descriptor
String descriptor = Thread.currentThread().getClass().getResource("/jetty/Web.xml").toString();
context.setDefaultsDescriptor(descriptor);

//Servlet
Iterator<Entry<String, String>> iter = map.entrySet().iterator();
while(iter.hasNext()){
	Entry<String, String> entry = iter.next();
	String key = entry.getKey().toLowerCase();
	String value = entry.getValue();
	System.setProperty("php.java.bridge." + key, value);
}
context.addServlet(FastCGIServlet.class, "*.php");
context.addEventListener(new ContextLoaderListener());

hc.addHandler(context);
Server server = new Server(port);
server.setHandler(hc);
try {
	server.start();
} catch(Exception e){
	System.out.print("fail");
}

啓動Server時,添加一個WebAppContext處理請求php的文件,同時使用php-java-bridge中的FastCGIServlet來解析php程序,這樣一個PHP服務器就打造出來了。同理,能夠利用這樣的原理處理其餘支持CGI的後端語言程序。

從啓動Server的代碼中,你們能夠發現咱們會設置一個路由頁面路徑或者一個工程目錄,這至關於Tomcat、Apache設置Web工程目錄,是服務器訪問文件的根目錄。還有一個須要注意的問題是從Server訪問的文件都是能夠正常上線的代碼,而非還須要被「處理」的源碼。

源碼!=上線代碼,這個關係開發者應該都是瞭解。隨着不少自動化工具的誕生,咱們開發的代碼都是會通過工具處理後纔會發佈到線上機器,這個過程就是所謂的編譯過程。在本地開發時,咱們依然須要將源碼編譯發佈到一個本地臨時預覽環境中,經過Server去訪問Web工程。這樣的目的就是讓Sever是個徹底獨立的東西,只是接受HTTP請求、分析URL、解析代碼就夠了,至於代碼編譯發佈的工做就不須要交給Server去作了。並且,也不必同時再Server中實現一遍編譯工做,Server和編譯上線是沒有關係的,它的責任就是負責本地服務器的做用。

一個可模擬的環境

有了本地運行服務器做爲基石,接下來咱們就能夠打造屬於FE本身的開發環境。也許你會發現經過Server你能夠正常訪問到你工程中的頁面,可是與後端對接的數據接口怎麼辦?異步請求怎麼處理?忽然發現還有不少請求問題沒有獲得完美的解決。在聯調環境中,也許RD會爲項目配置好服務轉發規則,不管是使用的是Aapache、Tomcat,仍是lighttpd。固然做爲本地Server,你依然能夠解決這個問題。

  1. 制定一套轉發配置的規則
  2. 打造一個Rewrite模塊,能夠與Server配合使用

固然你會想到既然咱們使用了Jetty,就可使用其rewrite配置,但這並不能達到咱們的要求並且過於複雜。好比咱們要運行PHP項目時,咱們可能須要在FastCGIServlet處理URL轉發的問題,至關麻煩,並且使Server不夠獨立、過於複雜。因此咱們是這樣作的:

  1. 一個conf文件配置URL轉發規則
  2. 一個PHP文件充當一個Route模塊

因此咱們的Server就須要有兩種狀態,一種是不轉發狀態,一種是所有URL轉發到Router文件中。所以咱們的Server須要這樣改造:

//context
HandlerCollection hc = new HandlerCollection();
WebAppContext context;
if(rewrite){
	context = new FISWebAppContext(root, "/" + script);
} else {
	context = new WebAppContext(root, "/");
}

//set default descriptor
String descriptor = Thread.currentThread().getClass().getResource
("/jetty/Web.xml").toString();
context.setDefaultsDescriptor(descriptor);

//Servlet
if(hasCGI){
	Iterator<Entry<String, String>> iter = map.entrySet().iterator();
	while(iter.hasNext()){
		Entry<String, String> entry = iter.next();
		String key = entry.getKey().toLowerCase();
		String value = entry.getValue();
		System.setProperty("php.java.bridge." + key, value);
	}
	context.addServlet(FastCGIServlet.class, "*.php");
	context.addEventListener(new ContextLoaderListener());
}
hc.addHandler(context);
Server server = new Server(port);
server.setHandler(hc);
try {
	server.start();
} catch(Exception e){
	System.out.print("fail");
}

同時咱們須要構造FISWebAppContext繼承WebAppContext類重寫doScope方法

class FISWebAppContext extends WebAppContext {
		
	private String filename = "/index.php";
	
	public FISWebAppContext(String root, String input) {
		super(root, "/");
		filename = input;
	}

	@Override
	public void doScope(String target, Request baseRequest,
			HttpServletRequest request, HttpServletResponse response)
			throws IOException, ServletException {
		super.doScope(filename, baseRequest, request, response);
	}
}

當咱們啓動Server時,若是添加了rewrite參數,便將全部的請求轉發到路由頁面中,默認是index.php,路由頁面將根據轉發配置規則進行URL處理。咱們可將各種URL都由路由頁面轉發不一樣的文件中進行處理,好比一個異步請求須要返回JSON數據,一個模擬線上URL顯示模板頁面等等。

在rewrite模式下,咱們會將全部的請求都轉發到index.php中,好比下面的狀況:

當咱們把路由頁面寫成一個固定內容時,任何請求都是返回index.php裏的內容,因此咱們須要根據不一樣的URI來作不一樣的處理,好比:

這時咱們對URI爲"/a.js"的請求作了特殊處理,返回一個JS內容。其實路由頁面的做用就是要針對URI作不一樣的處理,就像lighttpd同樣須要一個配置文件,將線上URI轉發對應上服務器的各個文件,所以咱們也須要相似一個Rewrite庫以及配置文件:

咱們根據server.conf的配置來控制URI找到對應的文件,固然這是最基本的要求。同時咱們也能夠模擬一些Ajax請求,來知足異步數據的獲取:

你會發現如今解決了不少問題,靜態資源能夠訪問了、異步數據也能夠獲取了,固然根據不一樣類型的項目,咱們還須要模板。爲了達到需求,咱們爲Rewrite模塊添加一類轉發類型

Rewrite模塊提供靈活的添加轉發規則的機制,可以讓咱們在不重啓Server的狀況下動態對轉發配置進行修改。根據項目狀況能夠具體打造路由頁面、Rewrite模塊以及Server.conf,來合理的模擬開發環境。

你們可能會發現一個問題,咱們的路由頁面和轉發配置放在哪?其實咱們的源碼會通過編譯後發佈到本地預覽環境中,在沒有rewrite狀態時,Server會根據URL請求直接讀取到預覽環境中的文件。當啓動rewrite模式時,Server會將請求都轉發到路由頁面,同時路由頁面還須要找到轉發配置文件進行分析,找到被轉發的文件進行渲染。當你發現配置文件、路由頁面和你的項目文件都有路徑關係時,那就表明其實這三樣都應該發佈在預覽環境中的,同時轉發配置文件應該跟隨源碼進行維護。

咱們須要的數據

經過以上努力,咱們已經能夠正常在本地運行頁面了,固然咱們還缺數據。一般頁面所須要的數據基本分爲兩種,一種是頁面渲染時由後端傳入的數據,一種是經過Ajax請求獲取的數據。經過請求獲取的數據,咱們能夠經過控制URL的方式來獲取,那由後端傳入的數據咱們該怎麼作呢?

經過以上的路由頁面的處理流程能夠發現,處理模板有個獨立的過程:

在開發的過程當中,咱們能夠創建模板與測試數據的對應關係,將測試數據與源碼工程一塊兒維護。當源碼工程進行編譯發佈到預覽環境中,瀏覽預覽環境中的模板時,能夠獲取測試數據進行渲染,同時也可對測試數據進行在線編輯。

對於測試數據的管理,一方面咱們能夠根據與後端的數據接口建立測試數據,一方面咱們能夠經過某種方式獲取線上或沙盒環境的真實數據進行調試。

FIS的本地開發環境

從上文的介紹中,咱們瞭解到創建一個本地開發環境都須要哪些必須的條件。下面我簡單的爲你們介紹下FIS的本地開發環境是怎麼樣的。

  • 獨立、跨平臺、高性能的Server
  • 路由頁面
  • Rewrite模塊
  • 測試數據模塊

以上四樣法寶構成了一個本地開發環境,開發者將開發代碼通過FIS編譯後發佈到本地的臨時預覽目錄,經過Server可預覽頁面,具體的你們能夠動手嘗試下[Fis官網](http://fis.baidu.com)提供的Demo。

縱觀以上流程,你會發現fis-plus提供的整個本地調試預覽都是依賴本文最開始提出的三個要求來進行打造的,你能夠根據項目以及環境的實際狀況打造屬於大家團隊本身的調試平臺。一個好的易用的本地調試輔助開發工具,能夠有效的提升開發聯調效率同時減小一些繁瑣重複甚至是煩人的環節。程序員們固然但願擁有更多的時間去面對寫碼,而不是浪費太多時間以及精力在搭環境、催單子、找接口人等環節。FIS本地輔助開發工具就是秉着此原則和要求不斷挖掘以及通用化FE的本地開發需求,但願能夠給你們帶來更多的幫助和想法。

做者簡介:袁方,百度Web前端研發部前端集成解決方案(FIS)小組核心成員,負責FIS本地開發環境的設計與開發、參與實現PC端解決方案等項目,全面負責FIS與百度各產品線前端團隊合做落地。

相關文章
相關標籤/搜索