Nodejs实现的一个网络请求模块化基础案例

本篇文章是基于我前面写过的 《Nodejs 结构和创建》 的进一步实践。在我们面对复杂的业务逻辑的时候就需要一个更好的模块和分职责的结构,今天我们通过一个简单的案例来看看如何创建一个路由 Route 并在 Nodejs 中分离业务逻辑。

Nodejs 网络请求

前面提到过使用 Nodejs 启动一个服务很简单,例如我们启动一个默认端口是 3000 的服务:

var http = require('http');

http.createServer(function(req, res){
    res.writeHead(200, {'Content-Type' : 'text/html'});
    res.write('<h1>Node.js</h1>');
    res.end('<p>Hello World</p>');
}).listen(3000);

console.log('HTTP server is listening at prot 3000.');

这些代码没啥特别的地方,可能这里大家比较好奇的是这个 http 模块到底是个什么,这个是 Nodejs 的一个内置的核心模块(模块和包的概念前面已经提到过了),Nodejs 内置核心模块清单如下:

模块名说明
http提供HTTP服务器功能。
url解析URL。
fs与文件系统交互。
querystring解析URL的查询字符串。
child_process新建子进程。
util提供一系列实用小工具。
path处理文件路径。
crypto提供加密和解密功能,基本上是对OpenSSL的包装。

上面这些核心模块,源码都在 Nodejs 的 lib 子目录中。为了提高运行速度,它们安装时都会被编译成二进制文件。核心模块总是最优先加载的。如果你自己写了一个 HTTP 模块, require('http') 加载的还是核心模块。

上面的 http.createServer([options][, requestlistener]) 返回的是一个 http.Server 实例对象。然后通过它的 server.listen() 启动 HTTP 服务器监听连接。requestListener 是一个自动添加到 ‘request’ 事件的函数。我们通过操作和是想服务器返回。设置 http.ServerResponse 对象实现服务器返回。

response.writeHead(statusCode[, statusMessage][, headers]) 向请求发送响应头。状态码是一个 3 位的 HTTP 状态码,如 404。 最后一个参数 headers 是响应头。 可以可选地将用户可读的 statusMessage 作为第二个参数。

response
  .writeHead(200, {
    'Content-Length': Buffer.byteLength(body),
    'Content-Type': 'text/plain'
  })

此方法只能在消息上调用一次,并且必须在调用 response.end() 之前调用。其余方法和更多用法请参考文档:http://nodejs.cn/api/http.html

如何结束一个 Node 服务

在 win10 上面要结束一个 Node 服务可以通过下面方式:

先查找PID(注意后面是你启动服务的端口号):

netstat -aon|findstr 3000

终止进程(后面的 15956 改成你所查到的 PID):

taskkill -f -pid 15956

封装一个请求服务模块

创建一个文件 server.js:

var http = require("http");

function start(){
    function onRequest(req, res){
        res.writeHead(200, {'Content-Type' : 'text/html'});
        res.write('<h1>Node.js</h1>');
        res.end('<p>Hello World</p>');
    }

    http.createServer(onRequest).listen(3000);
}

exports.start = start;

然后在 index.js 中引入该模块,并执行 start().

var server = require("./server");
server.start();

现在我们的请求结构如下图所示:

服务器和浏览器关系示意图

封装一个路由

如果我们根据不同的请求地址来回复不同的内容,修改上面代码如下:

var http = require("http");
var url = require("url");

function start(){
    function onRequest(req, res){
        var pathname = url.parse(req.url).pathname;
        res.writeHead(200, {'Content-Type' : 'text/html'});
        if(pathname === "/"){
            res.write('<h1>Node.js</h1>');
        }else if(pathname === "/android"){
            res.write('<h1>Android</h1>');
        }else{
            res.write('<h1>Unkown Path</h1>')
        }
        res.end('<p>Hello World</p>');
    }

    http.createServer(onRequest).listen(3000);
}

exports.start = start;

上面添加了一个 url 内置模块,并且使用 url.parse() 方法获取到了请求路径,并拿出 pathname 进行判断分别返回不同的 <h1> 标签内容。

例如我们访问:localhost:3000 则返回 Node.js, 如果我们访问 localhost:3000\android 则返回 Android,否则返回的是 Unkown Path.

接下来我们新建一个 route.js 来抽取出路由功能。

function route(pathname, res){
    res.writeHead(200, {'Content-Type' : 'text/html'});
    if(pathname === "/"){
        res.write('<h1>Node.js</h1>');
    }else if(pathname === "/android"){
        res.write('<h1>Android</h1>');
    }else{
        res.write('<h1>Unkown Path</h1>')
    }
    res.end('<p>Hello World</p>');
}

exports.route = route;

抽取后的 server.jsindex.js 内容如下:

server.js 文件:

var http = require("http");
var url = require("url");

function start(route){
    function onRequest(req, res){
        var pathname = url.parse(req.url).pathname;
        route(pathname, res);
    }

    http.createServer(onRequest).listen(3000);
}

exports.start = start;

index.js 文件:

var server = require("./server");
var router = require("./route");

server.start(router.route);

现在的结构如下图所示,将业务逻辑和服务启动器分类了。

分离路由和服务逻辑示意图

分离业务逻辑

上面我们分类了一个路由功能出来,你可以试想一下,如果我们的业务逻辑很多的时候是不是这个路由类 route.js 内容是不是会很长,所以我们将里面的逻辑分离出来,下面简单示例一下这个过程。本文出自水寒的个人博客,转载请说明出处:https://dp2px.com

新建一个 requestHandler.js 文件:

function doHome(res){
    res.writeHead(200, {'Content-Type' : 'text/html'});
    res.write('<h1>Node.js</h1>');
    res.end('<p>Hello World</p>');
}

function doAndroid(res){
    res.writeHead(200, {'Content-Type' : 'text/html'});
    res.write('<h1>Android</h1>');
    res.end('<p>Hello World</p>');
}

function doOther(res){
    res.writeHead(200, {'Content-Type' : 'text/html'});
    res.write('<h1>Unkown Path</h1>')
    res.end('<p>Hello World</p>');
}

exports.home = doHome;
exports.android = doAndroid;
exports.other = doOther;

这个 handle 文件中处理了我们上面的三个业务逻辑,然后我们的路由就可以简化如下:

function route(pathname, res, handle){
    if(pathname === "/"){
        handle.home(res);
    }else if(pathname === "/android"){
        handle.android(res);
    }else{
        handle.other(res);
    }
}

exports.route = route;

index.js 中将 handle 对象传递给 server.start() 函数,然后通过 server 将 handle 和 route 绑定。

分离路由和业务逻辑示意图

本系列源码在 GitHub 仓库:https://github.com/lxqxsyu/NodejsStudy