Skip to content

Latest commit

 

History

History
749 lines (398 loc) · 27.1 KB

File metadata and controls

749 lines (398 loc) · 27.1 KB

六、Node.js 简介

任何傻瓜都能写出计算机能理解的代码。优秀的程序员编写人类能够理解的代码。——马丁·福勒T3】

在本章中,我们将介绍以下内容:

  • 在 Node.js 中构建“Hello World”
  • Node.js 核心模块
  • npm Node 程序包管理器
  • 带有 Node.js 的留言板:内存存储版本
  • 单元测试 Node. js

Node.js 是一个用于构建 web 应用的非阻塞平台。它使用 JavaScript,所以它是我们 fullstack JavaScript 开发的核心。我们将从 Hello World 开始,讨论核心模块和 npm。然后,我们将 Hello World 应用部署到云中。

在 Node.js 中构建“Hello World”

引导您完成实施并演示项目的补充视频: http://bit.ly/1QnqFmF

要检查您的计算机上是否安装了 Node.js,请在终端中键入并执行以下命令:

$ node -v

在撰写本文时,最新版本是 5.1.0。如果你没有安装 Node.js,或者你的版本落后,可以在 nodejs.org/#download 下载最新版本。您可以使用这些工具之一进行版本管理(例如,在 Node.js 版本之间切换):

通常,你可以在 https://github.com/azat-co/fullstack-javascript/tree/master/07-hello 复制示例代码,或者从头开始编写自己的程序。如果您想做后者,为您的“Hello World”node . js 应用创建一个文件夹hello。然后创建一个server.js文件,一行一行地输入下面的代码。

这将加载服务器的核心http模块(稍后将详细介绍这些模块):

var http = require(’http’)

我们需要 Node.js 服务器的端口号。要从环境中获取或分配 1337(如果未设置环境),请使用:

var port = process.env.PORT || 1337

这将创建一个服务器,一个回调函数将包含响应处理程序代码:

var server = http.createServer(function (req, res) {

要设置正确的标题和状态代码,请使用:

res.writeHead(200, {’Content-Type’: ’text/plain’})

要输出带有行尾符号的“Hello World ”,请使用:

res.end(’Hello World\n’)

})

要设置端口并显示服务器的地址和端口号,请使用:

server.listen(port, function() {

console.log(’Server is running at %s:%s ’,

server.address().address, server.address().port)

})

从您有server.js的文件夹中,在您的终端中启动以下命令:

$ node server.js

打开 localhost:1337 或 127.0.0.1:1337 或任何其他你在终端中看到的地址作为console.log()功能的结果,你应该在浏览器中看到“Hello World”。要关闭服务器,请按 Control + C。

Note

主文件的名称可以不同于 server.js(例如 index.js 或 app.js)。如果需要启动app.js文件,只需使用$ node app.js即可。

Node.js 核心模块

与其他编程技术不同,Node.js 没有附带沉重的标准库。node.js 的核心模块是最少的,其余的可以通过 Node 包管理器(NPM)注册中心挑选。主要的核心模块、类、方法和事件包括:

这些是最重要的核心模块。让我们逐一介绍。

这是负责 Node.js HTTP server 的主要模块。以下是主要方法:

  • http.createServer():返回一个新的 web 服务器对象
  • http.listen():开始接受指定端口和主机名上的连接
  • http.createClient() : node app 可以是客户端,向其他服务器发出请求
  • http.ServerRequest():传入的请求被传递给请求处理程序
    • data:收到一段消息体时发出
    • end:每个请求只发出一次
    • request.method():字符串形式的请求方法
    • request.url():请求 URL 字符串
  • http.ServerResponse():这个对象是由 HTTP 服务器内部创建的——而不是由用户创建的,并被用作请求处理程序的输出
    • response.writeHead():发送请求的响应头
    • response.write():发送响应正文
    • response.end():发送并结束响应体

该模块提供了用于调试的实用程序。一些方法包括:

  • util.inspect():返回一个对象的字符串表示,对调试很有用

这个模块提供了处理查询字符串的工具。一些方法包括:

  • querystring.stringify():将对象序列化为查询字符串
  • querystring.parse():将查询字符串反序列化为对象

这个模块有 URL 解析和解析的工具。一些方法包括:

  • parse():取一个 URL 字符串,返回一个对象

fs

文件系统处理文件系统操作,例如读写文件。库中有同步和异步方法。一些方法包括:

  • fs.readFile():异步读取文件
  • fs.writeFile():将数据异步写入文件

不需要安装或下载核心模块。要将它们包含在您的应用中,您只需遵循以下语法:

var http = require(’http’)

非核心模块列表可在以下位置找到:

如果你想知道如何编写你自己的模块,看看这篇文章: https://quickleft.com/blog/creating-and-publishing-a-node-js-module/

npm Node 程序包管理器

Node 程序包管理器(或称 NPM)为您管理依赖关系并安装模块。Node.js 安装自带 NPM,其网址为 npmjs.org

包含关于 Node.js 应用的元信息,比如版本号;作者姓名;最重要的是,我们在应用中使用什么依赖关系。所有这些信息都在 JSON 格式的对象中,由 NPM 读取。

如果您想安装package.json中指定的软件包和依赖项,请键入:

$ npm install

典型的package.json文件可能如下所示:

{

"name": "Blerg",

"description": "Blerg blerg blerg.",

"version": "0.0.1",

"author": {

"name" : "John Doe",

"email" : "john.doe@gmail.com"

},

"repository": {

"type": "git",

"url": "http://github.com/johndoe/blerg.gitT2】

},

"engines": [

"node >= 0.6.2"

],

"scripts": {

"start": "server.js"

},

"license" : "MIT",

"dependencies": {

"express": ">= 2.5.6",

"mustache": "0.4.0",

"commander": "0.5.2"

},

"bin" : {

"blerg" : "./cli.js"

}

}

虽然上面的package.json示例中的大多数属性,如descriptionname,都是不言自明的,但是其他的属性需要更多的解释。Dependencies 是一个对象,每一项左边有名称,右边有版本号(如“express”:>= 2 . 5 . 6)。版本可以是精确的:例如," express": "2.5.6 ",或者大于,或者通配符,例如," express": "* "(一种在生产中使用新的未经测试的依赖项来放大应用的好方法:因此不推荐)。

bin属性用于命令行实用程序。它告诉系统启动什么文件。而scripts对象有你可以用$ npm run SCRIPT_NAME启动的脚本。start脚本和测试是例外。你可以用$ npm start$ npm test来运行它们。

要将软件包更新到当前最新版本或package.json中定义的版本规范允许的最新版本,请使用:

$ npm update name-of-the-package

或者对于单模块安装:

$ npm install name-of-the-package

本书示例中使用的唯一模块是mongodb,它不属于核心 Node.js 包。我们将在本书的后面安装它。

Heroku 将需要package.json在服务器上运行 NPM。

想了解更多关于 NPM 的信息,可以看看“游 NPM”(http://tobyho.com/2012/02/09/tour-of-npm)这篇文章。

将“Hello World”部署到 PaaS

对于 Heroku 和 Windows Azure 部署,我们需要一个 Git 存储库。要从项目的根目录创建它,请在终端中键入以下命令:

$ git init

Git 将创建一个隐藏的.git文件夹。现在,我们可以添加文件并进行第一次提交:

$ git add .

$ git commit -am "first commit"

提示要查看 Mac OS X Finder 应用上的隐藏文件,请在终端窗口中执行此命令:defaults write com.apple.finder AppleShowAllFiles -bool true。要将标志改回隐藏状态:defaults write com.apple.finder AppleShowAllFiles -bool false

部署到 Windows Azure

为了将我们的“Hello World”应用部署到 Windows Azure,我们必须添加 Git remote。您可以从网站下的 Windows Azure 门户复制 URL,并通过以下命令使用它:

$ git remote add azure yourURL

现在,我们应该能够使用这个命令进行推送了:

$ git push azure master

如果一切顺利,您应该会在终端中看到成功日志,并在 Windows Azure 网站 URL 的浏览器中看到“Hello World”。

要推动更改,只需执行:

$ git add .

$ git commit -m "changing to hello azure"

$ git push azure master

更细致的指导可以在 https://azure.microsoft.com/en-us/documentation/articles/web-sites-nodejs-develop-deploy-mac 教程中找到。

部署到 Heroku

对于 Heroku 部署,我们需要创建两个额外的文件:Procfilepackage.json。你可以从 https://github.com/azat-co/fullstack-javascript/tree/master/07-hello 获得源代码,或者自己写一个。

“Hello World”应用的结构如下所示:

/07-hello

-package.json

-Procfile

-server.js

Procfile 是一种机制,用于声明 Heroku 平台上应用的 dynos 运行哪些命令。基本上,它告诉 Heroku 运行什么进程。在这种情况下,Procfile 只有一行:

web: node server.js

对于这个例子,我们保持package.json简单:

{

"name": "node-example",

"version": "0.0.1",

"dependencies": {

},

"engines": {

"node": ">=0.6.x"

}

}

在项目文件夹中有了所有文件之后,我们可以使用 Git 来部署应用。除了我们需要添加 Git remote,并使用以下命令创建 Cedar 堆栈之外,这些命令与 Windows Azure 非常相似:

$ heroku create

完成后,我们推送并更新:

$ git push heroku master

$ git add .

$ git commit -am "changes :+1:"

$ git push heroku master

如果一切顺利,您应该会在终端中看到成功日志,并在 Heroku 应用 URL 的浏览器中看到“Hello World”。

带有 Node.js 的留言板:内存存储版本

引导您完成实施并演示项目的补充视频: http://bit.ly/1QnqO9P

第一版的留言板后端应用为了亲亲只在运行时内存存储中存储消息( http://en.wikipedia.org/wiki/KISS_principle )。这意味着每次我们启动/重置服务器时,数据都会丢失。

我们将首先从一个简单的测试用例开始,来说明测试驱动的开发方法。完整代码可在 https://github.com/azat-co/fullstack-javascript/tree/master/08-test 获得。

单元测试 Node. js

我们应该有两种方法:

Get all of the messages as an array of JSON objects for the GET /message endpoint using the getMessages() method   Add a new message with properties name and message for POST /messages route via the addMessage() function  

我们将从创建一个空的mb-server.js文件开始。在它出现之后,让我们切换到测试并创建包含以下内容的test.js文件:

var http = require(’http’)

var assert = require(’assert’)

var querystring = require(’querystring’)

var util = require(’util’)

var messageBoard = require(’./mb-server’)

assert.deepEqual(’[{"name":"John","message":"hi"}]’,

messageBoard.getMessages())

assert.deepEqual (’{"name":"Jake","message":"gogo"}’,

messageBoard.addMessage ("name=Jake&message=gogo"))

assert.deepEqual(’[{"name":"John","message":"hi"},{"name":"Jake",message":"gogo"}]’,

messageBoard.getMessages())

请记住,这是一个非常简单的字符串比较,而不是 JavaScript 对象。所以每个空格、引用和大小写都很重要。您可以通过将一个字符串解析成一个 JSON 对象来使比较“更智能”,方法是:

JSON.parse(str)

为了测试我们的假设,我们使用 Node.js 模块的核心断言。它提供了一堆有用的方法,如equal()deepEqual()等。

更高级的库包括 TDD 和/或 BDD 方法的替代接口:

对于更多的测试驱动开发和前沿自动化测试,您可以使用以下库和模块:

您可以暂时将“Hello World”脚本复制到mb-server.js文件中,或者甚至保留为空。如果我们通过终端命令运行test.js:

$ node test.js

我们应该会看到一个错误。大概是这样的:

TypeError: Object #<Object> has no method ’getMessages’

那完全没问题,因为我们还没有写getMessages()方法。因此,让我们这样做,并通过添加两个新方法使我们的应用更有用:获取聊天消息列表和向集合中添加新消息。

带有全局exports对象的mb-server.js文件:

exports.getMessages = function() {

return JSON.stringify(messages)

// Output array of messages as a string/text

}

exports.addMessage = function (data){

messages.push(querystring.parse(data))

// To convert string into JavaScript object we use parse/deserializer

return JSON.stringify(querystring.parse(data))

// Output new message in JSON as a string

}

我们导入依赖关系:

var http = require(’http’)

// Loads http module

var util= require(’util’)

// Usefull functions

var querystring = require(’querystring’)

// Loads querystring module, we’ll need it to serialize and deserialize objects and query strings

并设置端口。如果它是在 env 变量中设置的,我们使用那个值;如果没有设置,我们使用硬编码值 1337:

var port = process.env.PORT || 1337

到目前为止,没什么特别的,对吧?为了存储消息列表,我们将使用一个数组:

var messages=[]

// This array will hold our messages

messages.push({

’name’: ’John’,

’message’: ’hi’

})

// Sample message to test list method

一般来说,像虚拟数据这样的设备属于测试/规范文件,而不属于主要的应用代码库。

我们的服务器代码看起来会稍微有趣一些。为了获得消息列表,根据 REST 方法,我们需要发出一个 GET 请求。对于创建/添加新消息,它应该是 POST 请求。因此,在我们的 createServer 对象中,我们应该添加req.method()req.url()来检查 HTTP 请求类型和 URL 路径。

让我们加载 http 模块:

var http = require(’http’)

我们将需要来自utilquerystring模块的一些方便的函数(来序列化和反序列化对象和查询字符串):

var util= require(’util’)

// Usefull functions

var querystring = require(’querystring’)

// Loads querystring module, we’ll need it to serialize and deserialize objects and query strings

创建一个服务器并将其暴露给外部模块(即test.js):

exports.server=http.createServer(function (req, res) {

// Creates server

在请求处理程序回调中,我们应该检查请求方法是否为 POST,URL 是否为messages/create.json:

if (req.method == ’POST’ && req.url == ’/messages/create.json’) {

// If method is POST and URL is messages/ add message to the array

如果上述条件为真,我们向数组中添加一条消息。但是,data必须在添加之前转换为字符串类型(编码为 UTF-8 ),因为它是一种缓冲区类型:

var message = ’’

req.on(’data’, function(data, msg){

console.log(data.toString(’utf-8’))

message=exports.addMessage(data.toString(’utf-8’))

// Data is type of Buffer and must be converted to string with encoding UTF-8 first

// Adds message to the array

})

这些日志将帮助我们监控终端中的服务器活动:

req.on(’end’, function(){

console.log(’message’, util.inspect(message, true, null))

console.log(’messages:’, util.inspect(messages, true, null))

// Debugging output into the terminal

输出应该是文本格式,状态为 200(正常):

res.writeHead(200, {’Content-Type’: ’text/plain’})

// Sets the right header and status code

我们输出一条带有新创建的对象 ID 的消息:

res.end(message)

// Out put message, should add object id

})

如果方法是 GET,URL 是/messages/list.json,则输出消息列表:

} else

if (req.method == ’GET’ && req.url == ’/messages/list.json’) {

// If method is GET and URL is /messages output list of messages

获取邮件列表:

var body = exports.getMessages()

// Body will hold our output

响应主体将保存我们的输出:

res.writeHead(200, {

’Content-Length’: body.length,

’Content-Type’: ’text/plain’

})

res.end(body)

下一个else是当前面的任何条件都不匹配时。这将设置正确的标题和状态代码:

} else {

res.writeHead(200, {’Content-Type’: ’text/plain’})

// Sets the right header and status code

如果不是上面的两个端点,我们输出一个带有行结束符号的字符串:

res.end(’Hello World\n’)

// Outputs string with line end symbol

}

启动服务器:

}).listen(port)

// Sets port and IP address of the server

现在,我们应该设置服务器的端口和 IP 地址:

console.log(’Server running at``http://127.0.0.1:%s/T2】

我们在test.js (exports 关键字)中公开了单元测试的方法,这个函数以字符串/文本的形式返回一组消息:

exports.getMessages = function() {

return JSON.stringify(messages)

}

addMessage()使用 querystring 中的 parse/deserializer 方法将字符串转换为 JavaScript 对象:

exports.addMessage = function (data){

messages.push(querystring.parse(data))

还以 JSON-as-a-string 格式返回新消息:

return JSON.stringify(querystring.parse(data))

}

下面是mb-server.js减去注释后的完整代码。也可在08-测试中获得:

var http = require(’http’)

// Loads http module

var util= require(’util’)

// Usefull functions

var querystring = require(’querystring’)

// Loads querystring module, we’ll need it to serialize and deserialize objects and query strings

var port = process.env.PORT || 1337

var messages=[]

// This array will hold our messages

messages.push({

’name’: ’John’,

’message’: ’hi’

})

// Sample message to test list method

exports.server=http.createServer(function (req, res) {

// Creates server

if (req.method == ’POST’ && req.url == ’/messages/create.json’) {

// If method is POST and URL is messages/ add message to the array

var message = ’’

req.on(’data’, function(data, msg){

console.log(data.toString(’utf-8’))

message=exports.addMessage(data.toString(’utf-8’))

// Data is type of Buffer and must be converted to string with encoding UTF-8 first

// Adds message to the array

})

req.on(’end’, function(){

console.log(’message’, util.inspect(message, true, null))

console.log(’messages:’, util.inspect(messages, true, null))

// Debugging output into the terminal

res.writeHead(200, {’Content-Type’: ’text/plain’})

// Sets the right header and status code

res.end(message)

// Out put message, should add object id

})

} else

if (req.method == ’GET’ && req.url == ’/messages/list.json’) {

// If method is GET and URL is /messages output list of messages

var body = exports.getMessages()

// Body will hold our output

res.writeHead(200, {

’Content-Length’: body.length,

’Content-Type’: ’text/plain’

})

res.end(body)

} else {

res.writeHead(200, {’Content-Type’: ’text/plain’})

// Sets the right header and status code

res.end(’Hello World\n’)

// Outputs string with line end symbol

}

}).listen(port)

// Sets port and IP address of the server

console.log(’Server running at``http://127.0.0.1:%s/T2】

exports.getMessages = function() {

return JSON.stringify(messages)

// Output array of messages as a string/text

}

exports.addMessage = function (data){

messages.push(querystring.parse(data))

// To convert string into JavaScript object we use parse/deserializer

return JSON.stringify(querystring.parse(data))

// Output new message in JSON as a string

}

要查看它,请访问。您应该会看到一条示例消息。

或者,您可以使用终端命令来获取消息:

$ curl http://127.0.0.1:1337/messages/list.json

使用命令行界面发出 POST 请求:

$ curl -d "name=BOB&message=test"http://127.0.0.1:1337/messages/create.jsonT3】

当您刷新时,您应该在服务器终端窗口中得到输出和一条新消息“test”。不用说,这三项测试都应该通过。

您的应用可能会随着更多的方法、要解析的 URL 路径和条件而变得更大。这就是框架派上用场的地方。它们提供了处理请求的助手和其他好东西,如静态文件支持、会话等。在这个例子中,我们故意没有使用任何类似 Express ( http://expressjs.com/ )或 Restify ( http://mcavage.github.com/node-restify/ )的框架。其他值得注意的 Node.js 框架:

要获得精心挑选的框架列表,请查看(http://node framework。com 。改进应用的方法:

  • 通过添加对象比较而不是字符串比较来改进现有的测试用例
  • 将种子数据从mb-server.js移动到test.js
  • 添加测试用例来支持您的前端(例如,向上投票、用户登录)
  • 添加方法来支持您的前端(例如,向上投票、用户登录)
  • 为每条消息生成唯一的 id,并将它们存储在哈希中,而不是存储在数组中
  • 安装 Mocha 和 re-factor test.js,以便它使用这个库

到目前为止,我们一直将消息存储在应用内存中,所以每次应用重启时,我们都会丢失消息。要修复它,我们需要添加一个持久性,方法之一就是使用 MongoDB 这样的数据库。

摘要

在这一章中,我们已经讨论了一些重要的主题,这些主题将为我们打下基础。他们展示了 Node.js 中的“Hello World”应用,一些最重要的核心模块列表,NPM 工作流,将 Node.js 应用部署到 Heroku 和 Windows Azure 的详细命令;也是测试驱动开发实践的一个例子。