Nodejs实现一个简单的服务器

之前使用 nodejs 完成了简陋版的静态服务器,了解了服务器的运行机制:

  • 1、创建服务器对象并监听用户请求
  • 2、设置路由来根据用户请求返回不同的响应结果

但这里仅仅是返回了静态的结果,而没有动态的数据。如果需要有动态的数据,整个服务器的运行机制是怎么样的?

  • 1、同样是创建服务器对象并监听用户请求
  • 2、用户请求分为静态文件请求和获取动态页面。比如
    get 127.0.0.1:3000/index.htmlget 127.0.0.1:3000/index的结果是不同的。
  • 3、当请求127.0.0.1:3000/index时,触发render()函数,传入index.html作为模板,传入{name: 'ltaoo'}作为页面数据。render()函数获取index.html文件内容,遇到{name}表示需要替换为ltaoo

这里可以使用模板引擎来替代自己解析html并插入数据。

简单实现

尝试实现最简陋版本的服务端程序,只有index页面并显示固定数据,不从数据库中获取数据、没有静态服务器(意味着没有样式与js脚本)。

var http = require('http')
var url = require('url')
var fs = require('fs')
var path = require('path')

http.createServer(function (req, res){
  //解析请求
  var pathname = url.parse(req.url).pathname
  if(pathname === '/index') {
    //只处理请求 index 这一种情况
    fs.readFile(path.join(__dirname, './index.html'), 'utf-8', function (err, file){
      if(err) console.log(err)
      //假数据
      var data = {
        "title": "MVC project",
        "author": "ltaoo",
        "content": "a project content"
      }
      //获取html 文件并将其中的 {{xx}} 和 data 数据进行匹配。
      var pattern = /({{)[a-z]+(}})/g;
      var ary = file.match(pattern);
      for (var i = ary.length - 1; i >= 0; i--) {
        ary[i] = ary[i].replace(/\W/g, ')
      }
      //console.log(ary)
      // 将 html 文件内的变量替换
      for (var j = ary.length - 1; j >= 0; j--) {
        file = file.replace('{{' + ary[j] + '}}', data[ary[j]])
      }
      // 渲染页面
      res.writeHead(200, {"Content-Type": "text/html"})
      res.write(file)
      res.end()
    }) 
  }
}).listen(3000)

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

对应的模板文件index.html

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title>Nodejs study</title>
</head>
<body>
  <article>
    <h1>{{title}}</h1>
    <p>{{author}}</p>
    <p>{{content}}</p>
  </article>
</body>
</html>

运行后页面成功显示预期的数据。


如何从该程序拓展成一个“比较”通用的服务器程序?

拓展

服务器的工作原理即接收请求,返回数据。查看express官方文档:

app.get('/', function (req, res) {
    res.render('index', {title: "Express", content: "this is a express project"});
});

这里等同于

if(pathname === '/index') {
    //coding
}

可以从上面的简陋版程序中看到,这一段代码属于响应请求的核心部分,代码量比较多且逻辑相同。所以抽象为函数。即 render() 函数。
该函数接收模板、数据作为参数,并在函数内渲染页面。

render 函数

根据 pathname 来调用 template。比如

  • get index, index.html
  • get articles, articles.html
  • get article/2016/07/26/first, 调用article.html
function render(template, data) {
    // 这个地方的代码和简陋版的原理差不多,同样是先获取到模板文件,可以是 jade,然后将数据替换掉模板中的预留位置。最后渲染页面。这个地方是请求的终点。
}

这里涉及到模板引擎,如果使用 express 内置引擎,需要现在项目开始位置配置 views 的路径,则 render 函数将会在 views 目录下查找模板。

MVC 架构

OK,上面讲到了渲染页面,但是问题在于数据是怎么得到的,又是怎么传递给 render 函数的。如果 render 函数属于 V,则 M 和 C 都暂时还没有出现。

MVC 模式是软件工程的一种软件架构模式。...专业人员可以通过自身的专长分组:

  • 控制器(Controller)-负责转发请求,对请求进行处理。
  • 视图(View)-界面设计人员进行图形界面设计。
  • 模型(Model)-程序员编写程序应有功能(实现算法等)、数据库专家进行数据管理和数据库设计(可以实现具体的功能)。

OK,按照维基百科的说明,MVC架构能够让专业人员分组进行工作,各自负责自己擅长的方面。是否能理解成这是属于三个方面,通过函数调用来实现联系。

比如说,用户请求 /index ,这个请求将交给 Controller 来处理,Controller 知道了用户在请求 /index,就让 Model 从数据库中调取数据,调取完成后将数据交给 Controller,Controller拿到这个数据后,把数据和 View 进行组合,最后返回页面给用户。即

V -> C -> M -> C -> V

所以 View 应该仅仅是模板,没有任何的逻辑。上面代码中, render 函数应该是属于 Controller,因为它接受到了请求,并且处理 data 和 template,现在缺的是 Model。 路由是属于 Controller ,即 if(pathname === '/html') 开始,因为这里接收到请求并根据请求来处理数据,即逻辑部分。

Model

Model 属于和数据库交互的部分,在这里定义了方法来实现增删改查,并封装成模块将方法暴露给外部使用。假设:


// 连接数据库

function fetch() {

    //从数据库中获取全部数据


}

function fetchItem(id) {

    // 根据id 从数据库中获取到指定数据


}

exports.fetch = fetch

exports.fetchItem = fetchItem

然后就可以在 Controller 内调用方法获取数据,这样 Controller 就将 Model 和 View 联系在一起了。

var http = require('http'),
    url = require('url'),
    fs = require('fs'),
    path = require('path')

http.createServer(function (req, res){
  //解析请求
  var pathname = url.parse(req.url).pathname
  if(pathname === '/index') {
    //只处理这一种情况
    fs.readFile(path.join(__dirname, './index.html'), 'utf-8', function (err, file){
      if(err) console.log(err)
      /*var data = {
        "title": "MVC project",
        "author": "litao",
        "content": "a project content"
      }*/

      //将 Model 加载进来
      var model = require('model.js');
      // 获取数据
      var data = model.fetch();
      //parse html code and insert data
      var pattern = /({{)[a-z]+(}})/g;
      var ary = file.match(pattern);
      for (var i = ary.length - 1; i >= 0; i--) {
        ary[i] = ary[i].replace(/\W/g, ')
      }
      //console.log(ary)
      // 将 html 文件内的变量替换
      for (var j = ary.length - 1; j >= 0; j--) {
        file = file.replace('{{' + ary[j] + '}}', data[ary[j]])
      }
      // output 
      res.writeHead(200, {"Content-Type": "text/html"})
      res.write(file)
      res.end()
    }) 
  }
}).listen(3000)

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

代码整理

我们有了 M ,有了 V,也有 C,但 C 没有分离出来。

var http = require('http'),
    url = require('url'),
    fs = require('fs'),
    path = require('path')

http.createServer(function (req, res){
  //解析请求
  var pathname = url.parse(req.url).pathname
  if(pathname === '/index') {
    //只处理这一种情况
    var index = require('index');

    index.index();


  }
}).listen(3000)

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

将业务逻辑拿出来单独作为一个文件


exports.index = function () {    

    fs.readFile(path.join(__dirname, './index.html'), 'utf-8', function (err, file){
      if(err) console.log(err)

      //表示将 Model 加载进来
      var model = require('model.js');
      // 加载数据
      var data = model.fetch();
      //parse html code and insert data
      var pattern = /({{)[a-z]+(}})/g;
      var ary = file.match(pattern);
      for (var i = ary.length - 1; i >= 0; i--) {
        ary[i] = ary[i].replace(/\W/g, ')
      }
      //console.log(ary)
      // 将 html 文件内的变量替换
      for (var j = ary.length - 1; j >= 0; j--) {
        file = file.replace('{{' + ary[j] + '}}', data[ary[j]])
      }
      // output 
      res.writeHead(200, {"Content-Type": "text/html"})
      res.write(file)
      res.end()
    }) 

}

这样是否实现了一个 MVC 架构的服务端程序?

总结

对 nodeJs 的 http 模块有了一个基本的了解,对之后学习 koa 应该有很大的帮助。

参考

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 219,701评论 6 508
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 93,649评论 3 396
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 166,037评论 0 356
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,994评论 1 295
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 68,018评论 6 395
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,796评论 1 308
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,481评论 3 420
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,370评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,868评论 1 319
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 38,014评论 3 338
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 40,153评论 1 352
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,832评论 5 346
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,494评论 3 331
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 32,039评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 33,156评论 1 272
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,437评论 3 373
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 45,131评论 2 356

推荐阅读更多精彩内容