BT

如何利用碎片时间提升技术认知与能力? 点击获取答案

前端工程精粹(三):本地调试与数据模拟

| 作者 袁方 关注 0 他的粉丝 发布于 2013年12月25日. 估计阅读时间: 23 分钟 | GMTC大前端的下一站,PWA、Web框架、Node等最新最热的大前端话题邀你一起共同探讨。

自Web项目诞生以来,程序员们渐渐把整个项目的开发拆分为了前端与后端两个部分。随着项目复杂度的不断增加,同时为了快速迭代出更多Web项目,IT界就把两个部分的工作分成了两个工种来完成--前端开发工程师以及后端XX语言开发工程师(我们在此简称为FE与RD)。所以,在一个Web项目开发过程中,就出现了前后端定义数据接口、参数等等工作,同时产生了一个巨大的耦合问题--前端工程师完全需要依赖后端工程师的数据接口以及后端联调环境。当一个FE快速完成了页面的搭建,需要后端数据来完成页面交互等工作时,他唯一能做的就是等待RD完成他的工作,有时甚至还需要RD来搭建一个联调环境等等。

也许更多时候情况比我上述所说的更复杂,需要项目管理者协调好项目进度、前后端协同开发等等问题。当然更多时候FE希望自己能解决这些问题,靠别人不如靠自己,对吧?这时候为了不依赖后端工程师,可以自己搞定后端环境及数据,都需要准备什么呢?下面我来举例:

  • 完整的server支持,可以真实运行模板、后端程序等,同时最好满足可以跨平台运行、支持常用后端语言(java、php)等
  • 模拟url请求,能够有控制url请求的能力,不是单纯的直接访问返回内容的server,通过控制url来解决ajax请求模拟返回的功能
  • 数据模拟,有本地数据mock的能力,分离模板与对后端数据的依赖,使得fe能独立于rd进行项目开发

如果可以达到以上要求,你便能拥有一套完整的本地开发环境,拥有独立开发前端项目的环境以及解决与后端开发的耦合问题。使用过FIS2.0的用户也许会发现,FIS2.0可以完整解决以上的问题,在任何地方、任何环境都可以独立开发,接下来我为大家介绍下FIS本地开发环境是如何做到这些要求的。

一个轻巧独立的服务器

我们需要一个什么样的Server?

  • 可以监听请求,负责对页面请求进行响应
  • 后端语言的解析能力,比如可运行java、php等,不是简单的静态Web服务器
  • 易搭建,没有繁琐的安装过程,尽可能的不依赖其他复杂环境
  • 性能与可伸缩性,可快速的响应请求,同时稳定响应一定级别的并发数
  • 平台需求,可跨平台运行,解决使用不同平台开发的用户需求
  • 可靠性,可稳定长期运行,有服务器异常处理机制等

以上这些就是我们总结出一个合理可靠轻巧的服务器需要达到的要求,它是本地开发环境的基石。面对需求,我们需要的就是解决这些需求,当然从最开始用很挫的方式一步一步尝试,到最后发现需要怎么实现这个server,经历的痛苦也是不言而喻的。最后我们发现需要实现以下内容以及找到了一些匹配的“轮子”来做这些事:

  • 为了可跨平台部署及易搭建,我们选择了用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。

选择了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与百度各产品线前端团队合作落地。


感谢崔康对本文的审校。

给InfoQ中文站投稿或者参与内容翻译工作,请邮件至editors@cn.infoq.com。也欢迎大家通过新浪微博(@InfoQ)或者腾讯微博(@InfoQ)关注我们,并与我们的编辑和其他读者朋友交流。

评价本文

专业度
风格

您好,朋友!

您需要 注册一个InfoQ账号 或者 才能进行评论。在您完成注册后还需要进行一些设置。

获得来自InfoQ的更多体验。

告诉我们您的想法

允许的HTML标签: a,b,br,blockquote,i,li,pre,u,ul,p

当有人回复此评论时请E-mail通知我

企业内部搭建一个简单的server提供给前端的同学用,需要模拟什么数据直接去填充,绑定环境也挺方便的。 by Fu Gavin

企业内部搭建一个简单的server提供给前端的同学用,需要模拟什么数据直接去填充,绑定环境也挺方便的。

允许的HTML标签: a,b,br,blockquote,i,li,pre,u,ul,p

当有人回复此评论时请E-mail通知我

允许的HTML标签: a,b,br,blockquote,i,li,pre,u,ul,p

当有人回复此评论时请E-mail通知我

1 讨论

登陆InfoQ,与你最关心的话题互动。


找回密码....

Follow

关注你最喜爱的话题和作者

快速浏览网站内你所感兴趣话题的精选内容。

Like

内容自由定制

选择想要阅读的主题和喜爱的作者定制自己的新闻源。

Notifications

获取更新

设置通知机制以获取内容更新对您而言是否重要

BT