Web API即使通过网络进行调用的API接口,与具体的编程语言无关。现在常见的是通过标准的HTTP GET/POST请求,从服务器获取响应的资源或服务,服务器返回调用的结果内容,一般为xml格式或者json格式的数据(现在使用json的更多)。
在开发App的时候,一般原型设计好(如使用just in mind之类的工具)之后,我们会设计出与服务器交互的接口文档。一般情况下,App的开发进度(尤其原型)要快于服务器的开发进度。在App静态原型开发完到服务器实现所有的交互接口这段期间内,我们当然不能闲着。这时,我们可以本地模拟一个HTTP服务器,从而可以继续App的”动态化”开发。
由于对javascript比较熟悉,简单看了一下node js之后,就使用它来开发本地的HTTP服务器并提供各种交互的接口。这里记录一下是怎么一步一步实现的。
一步一步实现HTTP服务器
示例起见,杜撰了3个接口(无论多少个,原理都一样),如下:
焦点图
/sample_app/focus_pic
文章列表
/sample_app/article_list
文章详情
/sample_app/article_detail
我们可以将服务器划分为以下几个模块:
- 入口 - app.js,总体管理服务器。一般是启动服务器
- server模块 - server.js,负责服务器的配置与请求的转发。如服务器监听的端口,请求日志的记录,请求转发至具体的处理函数等
- router模块 - router.js,顾名思义,负责请求的路由功能。例如,我们在这里可以将不同的请求地址转发至不同的函数处理。
- request handlers模块 - request_handler.js,针对每个请求进行处理的函数都定义在这个模块里边。
- response template模块,由于我们只是快速模拟提供Web API服务的HTTP服务器,所以真正返回的内容写在模板里边即可。
其实3个接口的路由(将不同的请求转到对应的处理函数)按照道理应该在router模块中进行处理,但是因为针对每个接口的处理逻辑都是相同的,只是返回的内容不同,我就把路由逻辑转到request handlers模块中去了。具体如何写,可以根据实际的情况进行调整,这里边只是提供一个思路而已。
单看文字还是比较晦涩的,我们来看一下具体的代码:
server.js
/**
* Created by FIMH on 2016/05/05.
*/
var http = require(‘http‘);
var url = require(‘url‘);
function start(route, handle) {
function onRequest(request, response) {
// 获取请求路径
var parsedUrl = url.parse(request.url);
var pathname = parsedUrl.pathname;
console.log(‘Request for ‘ + pathname + ‘ received.‘);
route(handle, parsedUrl, request, response);
}
http.createServer(onRequest).listen(9999);
console.log(‘Server has started.‘);
}
exports.start = start;
可以看到,主要配置了HTTP服务器监听的端口 - 9999,以及打印了一条log信息,然后将请求转至router模块进行处理
router.js
/**
* Created by FIMH on 2016/05/05.
*/
// 针对不同的请求,做出不同的相应
function route(handler, parsedUrl, request, response) {
var pathname = parsedUrl.pathname;
console.log(‘About to route a request for ‘ + pathname);
// 禁止访问favicon.ico
if (!pathname.indexOf(‘/favicon.ico‘)) {
return;
}
// 这里不用检查请求路径是否正确,将路由放到handle对应的函数中去
handler(parsedUrl, request, response);
}
exports.route = route;
这里我们主要拦截了对favicon.ico文件的访问,关于这个文件是什么,大家可以自行搜索。
前面也提到了,由于这个sample里边,每个接口的处理逻辑都是相同的,只是返回的内容不同,我就把路由逻辑转到request handlers模块中去了。
真正的处理逻辑都在下面这个模块中
requests_handlers.js
/**
* 请求处理入口。
*/
function handleRequests(parsedUrl, request, response) {
// 解码并解析querystring
//var queryStringUtil = require(‘querystring‘);
//var queryString = parsedUrl.query;
//var queryStringResultObject = queryStringUtil.parse(queryString);
var pathname = parsedUrl.pathname;
// 在这里进行处理
}
exports.handleRequests = handleRequests;
这里,我只贴了一个请求处理的入口函数。
在这个地方我重构了一次,最初的处理逻辑大致如下:
var templateName;
var innerHtml;
if (pathname == ‘/sample_app/focus_pic‘) {
templateName = ‘focus_pic‘;
} else if (pathname == ‘/sample_app/article_list‘) {
templateName = ‘article_list‘;
} else if (pathname == ‘/sample_app/article_detail‘) {
templateName = ‘article_detail‘;
innerHtml = ‘article‘;
}
if (templateName) {
handleValidRequest(request, response, templateName, innerHtml);
} else {
handleErrorOutput(request, response, 400, ‘Invalid request url!‘);
}
因为这里只是3个请求,看着还不明显,如果比较多了,那么if…else写起来就太烦了,这时候我想起来好多js语言的项目(如cocos 2d-js,egret)都会使用json文件作为项目的配置文件,依次来简化代码并提高灵活性。
这时,我们可以定义一个项目的配置文件,我这里取名叫appProperties.json
,内容如下
{
"route": {
"/sample_app/focus_pic": {
"template": "focus_pic"
},
"/sample_app/article_list": {
"template": "article_list"
},
"/sample_app/article_detail": {
"template": "article_detail",
"inner_html": "article"
}
}
}
然后我们修改前面提到的模块 - requests_handlers.js
先定义一个全局变量var routeObj;
然后在函数handleRequests
里这样处理:
// 解析route配置信息
if (!routeObj) {
var fs = require(‘fs‘);
var propertiesPath = ‘./appProperties.json‘;
var propertiesData = fs.readFileSync(propertiesPath, ‘utf-8‘);
routeObj = JSON.parse(propertiesData);
}
var templateObj = routeObj[‘route‘][pathname];
if (templateObj) {
handleValidRequest(request, response, templateObj[‘template‘], templateObj[‘inner_html‘]);
} else {
handleErrorOutput(request, response, 400, ‘Invalid request url!‘);
}
重构之后,无论接口有多少个,处理的代码依然是这几行;而重构前的方法,每添加一个接口,都需要增加一个 else if
。
拿算法复杂度的概念来比对,重构前就是O(n),而重构后则为O(1)。
关于response template模块的处理,这里就不贴代码了。主要是使用node js同步或者异步读取模板文件,还有对json对象的序列化,编辑与反序列化。大家有兴趣的话可以看整个sample的源代码,见文章底部。
总结与源代码
如果使用传统的方式,你需要安装一个http服务器 - 如apache,还有一个语言处理模块 - 如php。
而使用了node js之后,你只需要安装一个node运行时,剩下的http服务器,请求解析,处理,返回等全部使用js来进行编写即可,而且书写的代码量也很小。
整个项目的代码我放到github上了,详见nodejs_sample_app