Node.js 入门与杂项
Node.js 学习笔记
背景
一段时间前,实际上是很久以前,我通过一位同事和 Youtube 接触了 Node.js。但我并不感兴趣,因为他们没有很好地宣传它。他们强调我们可以使用 JavaScript 编写服务器代码,但我从未觉得 JavaScript 在编写服务器代码方面比 Java 和 C# 等成熟语言有任何优势,也从未觉得掌握这些成熟语言的程序员短缺。Node.js 出现的时间比 Tomcat 和 IIS 等基于线程的 Web 服务器晚得多。我相信如果你来得晚,你需要显著不同才能被接受。嗯,Node.js 是不同的。以下是 Node.js 网站对自己的描述
Node.js® 是一个基于 Chrome 的 JavaScript 运行时构建的平台,用于轻松构建快速、可伸缩的网络应用。Node.js 使用事件驱动、非阻塞 I/O 模型,使其轻量高效,非常适合运行在分布式设备上的数据密集型实时应用。
“事件驱动、非阻塞 I/O 模型”是 Node.js 将自己与基于线程的 Web 服务器区分开来的方式。我确信如果使用得当,它应该是“高效,并且非常适合运行在分布式设备上的数据密集型实时应用”。现在是时候学习 Node.js 并了解它的工作原理了。
安装 Node.js
尽管本学习笔记基于 Windows,但它对其他环境也应具有一定的参考价值。我们可以从 Node.js 的官方网站下载安装包。就我而言,我下载了“node-v0.12.0-x64.msi”包并将其安装在我的计算机上。为了不弄乱我的“C:\Program Files”文件夹,我选择将 Node.js 安装在“C:\Node”文件夹中。
在安装文件夹中,我们可以找到 Node.js 两个最重要的文件。
- “node.exe”文件是启动 node 应用程序的可执行文件;
- “npm.cmd”文件是启动node 包管理器的“cmd”文件。Node.js 使用依赖注入来查找程序的依赖模块。npm 是帮助我们管理 node 模块的实用工具。
如果您现在转到“控制面板”->“系统”->“高级系统设置”->“环境变量...”,您会发现安装包已将两个文件夹添加到您的路径变量中。
- 文件夹“C:\Node”已添加到系统路径中,因此我们可以在命令提示符窗口中从任何位置运行“node.exe”和“npm.cmd”;
- 文件夹“C:\Users\song_li\AppData\Roaming\npm”已添加到用户路径。这是 npm 全局安装位置。如果您使用 npm 全局安装 node 包,则包将安装在此文件夹中。根据文档,如果您想在程序中通过“
require()
”使用某个包,您应该将其安装在本地。但是如果您想在命令行中运行该包,例如“forever”,您应该将其全局安装。
如果您现在在命令提示符窗口中输入“node -v
”,您应该会看到安装的 Node.js 版本。在我的情况下,它是“v0.12.0
”。这表明 Node.js 安装成功。
查找 IDE
在编写 Node.js 应用程序之前,我将找到一个 IDE。许多 IDE,如 Visual Studio、NetBeans 都提供 Node.js 支持,但我选择了 Eclipse,仅仅因为我目前正在使用 Eclipse。我使用的 Eclipse 版本是“Eclipse Java EE IDE for Web Developers, Kepler Service Release 2”,由 Java 7 提供支持。如果您使用的是更新的版本,我认为您也不会遇到任何问题。
要在 Eclipse 中使用 Node.js,您可以安装流行的“Enide”插件。从 Eclipse 菜单中,如果您转到“帮助”->“Eclipse Marketplace ...”并搜索“Enide”,您将找到该插件。由于我已经安装了该插件,我看到“更新”和“卸载”按钮。如果您从未安装该插件,您应该会看到“安装”按钮。安装“Enide”后,我们已在 Eclipse 上启用了 Node.js 支持。
第一个 Node.js 应用程序
在 Eclipse 和“Enide”的帮助下,创建 Node.js 应用程序确实非常容易。我们可以创建一个空文件夹作为 Eclipse 工作区来启动 Eclipse。从 Eclipse 菜单中,转到“文件”->“新建”->“项目...”以启动项目向导。
与其创建一个裸 Node.js 项目,不如创建一个 Node.js Express 项目。
我们可以为项目命名并选择一个 HTML 模板引擎来创建我们的第一个 Node.js 应用程序。
这是一个简单但功能齐全的 Web 应用程序。“package.json”文件声明了第三方依赖项。
{
"name": "FirstNodeApplication",
"version": "0.0.1",
"private": true,
"scripts": {
"start": "node app.js"
},
"dependencies": {
"express": "3.2.6",
"ejs": "*"
}
}
由于我们在项目中使用了 Express 和“ejs
”,我们可以看到这两个依赖项。如果我们更改了依赖项,我们只需右键单击 Eclipse 中的“package.json”文件 ->“运行方式”->“npm install”即可更新“node_modules”文件夹中的依赖项安装。“app.js”文件是 Web 应用程序的入口点。
var express = require('express')
, routes = require('./routes')
, user = require('./routes/user')
, http = require('http')
, path = require('path');
var app = express();
// all environments
app.set('port', process.env.PORT || 3000);
app.set('views', __dirname + '/views');
app.set('view engine', 'ejs');
app.use(express.favicon());
app.use(express.logger('dev'));
app.use(express.bodyParser());
app.use(express.methodOverride());
app.use(app.router);
app.use(express.static(path.join(__dirname, 'public')));
// development only
if ('development' == app.get('env')) {
app.use(express.errorHandler());
}
app.get('/', routes.index);
app.get('/users', user.list);
http.createServer(app).listen(app.get('port'), function(){
console.log('Express server listening on port ' + app.get('port'));
});
JavaScript 代码相当一目了然,但如果您对 Node.js 没有任何背景知识,以下解释可能有助于您理解代码。
- 全局“
require()
”函数是 Node.js 加载模块的方式。如果未提供路径信息,则该模块要么是 Node.js 自身提供的“核心模块”,要么是位于“node_modules”文件夹中的模块。如果提供了路径信息,Node.js 将转到指定路径查找模块。在单个 Node.js 应用程序中,模块是单例。 - “
app.use()
”函数配置Express 中间件。您可以访问此链接获取有关中间件的更多详细信息。 - “
app.get()
”函数通过 URL 模式将 HTTP 请求映射到 Node.js 模块中处理此请求的函数。 - “
http.createServer(app).listen(...)
”函数启动 Web 服务器,监听指定端口的 HTTP 请求。
如果您熟悉 ASP.NET MVC 或 Spring MVC,您会立即注意到 Express Node.js 应用程序是一个 MVC 应用程序。如果我们使用 MVC 术语,应用程序的控制器位于“routes\index.js”和“routes\user.js”文件中。
exports.index = function(req, res){
res.render('index', { title: 'Express' });
};
exports.list = function(req, res){ res.send("respond with a resource"); };
MVC 视图是“views\index.ejs”文件。
<!DOCTYPE html>
<html>
<head>
<title><%= title %></title>
<link rel='stylesheet' href='/stylesheets/style.css' />
</head>
<body>
<h1><%= title %></h1>
<p>Welcome to <%= title %></p>
</body>
</html>
如果我们现在右键单击“app.js”文件 ->“运行方式”->“Node 应用程序”,我们就可以启动 Web 应用程序。如果我们打开浏览器并输入正确的 URL,我们可以看到 Node.js 服务器正在运行并提供 HTML 内容。
Express 版本问题
尽管“Enide Studio 2014”帮助我们创建了第一个 Express Node.js 应用程序,但它使用的是 Express 3.2.6 版本。在准备本学习笔记时,最新的 Express 版本是 4.11.2,所以我修改了“package.json”文件以使用新版本。运行“npm install”后,我立即注意到应用程序未能启动。正如Express 文档所解释的,Express 不再捆绑中间件包,我们需要自己安装中间件。所以我更新了“package.json”文件以安装中间件。
{
"name": "FirstNodeApplication",
"version": "0.0.1",
"private": true,
"scripts": {
"start": "node app.js"
},
"dependencies": {
"express": "4.11.2",
"ejs": "2.2.4",
"serve-favicon": "2.2.0",
"morgan": "1.5.1",
"body-parser": "1.11.0",
"method-override": "2.3.1",
"errorhandler": "1.3.3"
}
}
我还更新了“app.js”以使用新安装的中间件。
var express = require('express'),
fs = require('fs'),
http = require('http'),
https = require('https'),
path = require('path'),
favicon = require('serve-favicon'),
morgan = require('morgan'),
bodyParser = require('body-parser'),
methodOverride = require('method-override'),
errorhandler = require('errorhandler');
var app = express();
// all environments
app.set('httpport', process.env.PORT || 3000);
app.set('httpsport', 3443);
app.set('views', __dirname + '/views');
app.set('view engine', 'ejs');
app.use(favicon(__dirname + '/public/favicon.png'));
app.use(morgan('combined'));
app.use(bodyParser.urlencoded({ extended: false }));
app.use(bodyParser.json());
app.use(methodOverride('X-HTTP-Method-Override'));
app.use('/public', express.static(path.join(__dirname, 'public')));
// development only
if ('development' === app.get('env')) {
app.use(errorhandler());
}
var routes = require('./routes');
var user = require('./routes/user');
app.get('/', routes.index);
app.get('/users', user.list);
http.createServer(app).listen(app.get('port'), function(){
console.log('Express server listening on port ' + app.get('port'));
});
在“package.json”文件上重新运行“npm install
”后,我成功启动了 Node.js 应用程序。Node.js 和 Express 都发展迅速。开发环境无法跟上变化并不奇怪。但我希望将来“Enide
”能够更新以使用新版本的 Express。
Node.js 模块和依赖注入
创建 Node.js 模块
Node.js 使用模块来组织代码。根据文档,文件和模块是一一对应的。我们可以使用“exports
”从 Node.js 模块中公开数据和函数。
exports.add = function(n1, n2) {
return n1 + n2;
};
exports.substract = function(n1, n2) {
return n1 - n2;
};
exports.multiply = function(n1, n2) {
return n1 * n2;
};
exports.divide = function(n1, n2) {
return n1 / n2;
};
我们也可以通过“module.exports
”对象来完成。
module.exports = {
add: function(n1, n2) {
return n1 + n2;
},
substract: function(n1, n2) {
return n1 - n2;
},
multiply: function(n1, n2) {
return n1 * n2;
},
divide: function(n1, n2) {
return n1 / n2;
}
};
很容易混淆“exports
”和“module.exports
”之间的区别,直到我找到了这篇博客文章。我不会重复他博客中的内容,我只在此处添加链接。如果您有兴趣,可以查看一下。您也可以仔细查阅文档,以便更好地理解“exports
”和“module.exports
”。
exports 与 module.exports
一个常见的问题是 Node 模块中“exports
”和“module.exports
”之间的区别。在幕后,通过“require()
”函数从 Node 模块返回对象的逻辑伪代码如下(来源:stackoverflow)。
var module = { exports: {} };
var exports = module.exports;
// your code
return module.exports;
- “
module
”对象在开始时创建,它有一个名为“exports
”的属性。 - 局部变量“
exports
”引用“module.exports
”对象。 - “
module.exports
”是“require()
”函数返回的对象。
“exports
”和“module.exports
”之间的区别导致我们实现 Node 模块时行为不同。
- 当一个对象赋值给“
exports
”变量时,该对象是“require()
”函数返回对象的属性; - 当一个对象设置给“
module.exports
”变量时,该对象本身就是“require()
”函数返回的对象。
require() Node.js 模块
Node.js 使用全局函数“require('module identifier')
”来访问模块。模块标识符通常是指定与模块对应的文件的一种方式。
- 如果传递给“
require()
”函数的模块标识符没有路径,则该模块要么是 Node.js 核心模块,要么是“node_modules
”文件夹中的模块。核心模块在 node 的源代码的lib/文件夹中定义; - 如果找不到确切的文件名,则 node 将尝试加载添加了“.js”、“.json”和“.node”扩展名的所需文件名;
- 在 Linux 中,如果模块标识符以“/”为前缀,则它是文件的绝对路径;
- 如果模块标识符以“./”或“../”开头,则它是调用
require()
函数的文件相对于自身的路径; - 如果 Node.js 无法解析模块位置,
require()
将抛出其代码属性设置为“MODULE_NOT_FOUND
”的 Error。
将程序和库组织到自包含的文件夹中是很方便的。在这种情况下,我们可以在文件夹中添加一个“package.json”文件。
{ "name" : "some-library",
"main" : "./lib/some-library.js" }
根据文档,如果它在一个名为“./some-library”的文件夹中,那么require('./some-library')
将尝试加载“./some-library/lib/some-library.js”文件。如果没有“package.json”文件,Node.js 将尝试加载该文件夹中的“index.js”文件。如果没有“index.js”文件,Node.js 将尝试加载该文件夹中的“index.node”文件。
Require() 返回单例
根据文档,模块在首次加载后会被缓存。这意味着,如果每次调用“require()
”函数都解析到同一个文件,它将返回完全相同的对象。如果您希望模块多次执行某些代码,则导出函数,然后调用该函数。
获取和发布数据
要创建一个有意义的 Web 应用程序,首要问题之一是如何从浏览器获取通过 URL 和表单发布发送的数据。
<!DOCTYPE html>
<html>
<head>
<title>Get & post data</title>
<link rel='stylesheet' href='/public/stylesheets/style.css' />
</head>
<body>
<h1>Get & post data</h1>
<form
action="<%=baseUrl%>getAndPostDataProcessData?parameter1=value1¶meter2=value2"
method="POST">
<div>
<select name="selection" style="margin: 5px">
<option value="Data items No.1">Data items No.1</option>
<option value="Data items No.2">Data items No.2</option>
<option value="Data items No.3">Data items No.3</option>
</select>
<button type="submit">Submit</button>
</div>
</form>
</body>
</html>
上面的“ejs
”页面将在单击“提交”按钮时发送两个 URL 参数和下拉框中的选择。
var common = require('../common');
exports.frontPage = function(req, res) {
var data = {};
data.baseUrl = common.baseUrl;
res.render('getAndPostDataFrontpage', data);
};
exports.processData = function(req, res) {
var data = {};
data.baseUrl = common.baseUrl;
// Get the url param data
data.parameter1 = req.query.parameter1;
data.parameter2 = req.query.parameter2;
// Get the form post data
data.selection = req.body.selection;
res.render('getAndPostDataProcessData', data);
};
- “
frontPage
”方法将加载上面的“ejs
”页面; - “
processData
”方法读取 URL 参数和表单数据,并在另一个“ejs”文件中渲染结果。
我们可以使用“req.query
”对象获取 URL 参数,使用“req.body
”对象获取 POST 数据。为了让“req.body
”对象包含 POST 数据,我们需要在“package.json”文件中添加 Express 中间件“body-parser”作为依赖项,并在app.js文件中对其进行配置。
var express = require('express'),
bodyParser = require('body-parser');
var app = express();
app.use(bodyParser.urlencoded({ extended: false }));
根据文档,如果您设置“{extended: true}
”,body parser 将使用“qs”库来解析数据,这允许编码丰富的对象和数组。如果我们现在加载页面并提交数据,我们可以看到 URL 参数和表单数据都正确接收。
Web 会话
大多数 Web 服务器都支持 Web 会话,Node.js 也不例外。要在 Express 中获得 Web 会话支持,我们需要在“package.json”文件中添加“mssql”作为依赖项,并运行“npm install
”进行安装。我们还需要在“app.js”文件中配置“mssql
”中间件。
var express = require('express'),
session = require('express-session');
var app = express();
var session_option = {
secret: 'keyboard cat', resave: false,
saveUninitialized: true, cookie: { maxAge: null } };
app.use(session(session_option));
在 Express 控制器中,我们可以使用“req.session
”对象访问 Web 会话中的信息。
exports = module.exports = function(req, res) {
var accessCount = req.session.accessCount;
if (!accessCount) {
accessCount = 0;
}
accessCount ++;
req.session.accessCount = accessCount;
res.send('This is your No.' + accessCount
+ ' time to the page in the session');
};
如果加载页面,它会告诉我们这是我们第 1 次来到这个页面。如果刷新页面,我们会看到每次数字都会加 1。
Cookie
要在 Express 中使用 cookie,我们需要在“package.json”中添加“cookie-parser
”并运行“npm install
”将其安装到“node_modules”文件夹中。我们还需要在“app.js”文件中配置“cookie-parser
”。
var express = require('express'),
cookieParser = require('cookie-parser'),
var app = express();
app.use(cookieParser());
然后我们可以在 Express MVC 控制器中使用 cookie。
exports.action = function(req, res) {
var accessCount = req.cookies.accessCount;
if (!accessCount) {
accessCount = 0;
}
accessCount ++;
res.cookie('accessCount', accessCount, { maxAge: 900000, httpOnly: true });
res.send('This is your No.' + accessCount
+ ' time to the page in the session');
};
如果我们在浏览器中加载页面,我们可以看到访问次数。每次刷新页面,访问次数都会加 1
。即使我们关闭浏览器并重新启动它,我们也会发现访问次数不会丢失,直到达到指定的最高使用期限。
连接到数据库
Node.js 使用事件驱动、非阻塞 I/O 模型,使其轻量高效,非常适合数据密集型实时应用程序。在大多数情况下,数据位于数据库中。理想情况下,我应该使用MongoDB来评估 Node.js,因为有所谓的“MEAN”堆栈。但由于我有一个可用的 SQL Server 数据库,我只是使用这个 SQL Server 数据库来体验 Node.js 中的数据库访问。您可以从此链接找到创建此数据库的脚本。在我的环境中,我使用了 SQL Server Express 2014。在创建数据库的脚本中,我没有授予数据库用户表级访问权限。如果您想运行我的示例,您需要运行以下脚本来授予访问权限。
USE Experiment
GO
GRANT SELECT ON OBJECT::[dbo].[TABLE_FILES] TO FileLoader
为了连接到 SQL Server,我们需要在“package.json”文件中添加“mssql”作为依赖项,并运行“npm install
”进行安装。
var sql = require('mssql');
var config = {
user: 'FileLoader',
password: 'Pwd123456',
server: 'localhost',
database: 'Experiment',
options: { encrypt: false }
};
exports.readdata = function(req, res) {
var connection = new sql.Connection(config);
connection.connect(function(err) {
if (err) { res.status(500).send(err); return; }
var request = new sql.Request(connection);
var sqlString = 'SELECT COUNT(*) CT FROM TABLE_FILES';
request.query(sqlString, function(err, rs) {
connection.close();
if (err) { res.status(500).send(err); return; }
var count = rs[0].CT;
res.send("There are " + count + " files in the table");
});
});
};
在上面的 Express MVC 控制器中,您应该注意以下几点
- 为了不阻塞主线程,所有数据库访问代码都在回调函数中实现;
- 每个回调函数都有一个“
err
”参数来报告是否存在任何错误。我们始终需要检查此参数并正确处理错误。我们绝不应该在回调函数中抛出任何异常,因为它会使线程崩溃。在最坏的情况下,它甚至可能使整个 Node.js 服务器崩溃; - 当我们不再需要连接对象时,我们应该关闭它。我使用SQL profiler来观察 Node.js 的连接行为。如果我们不关闭连接,即使连接对象超出范围,它也需要很长时间才能关闭。
如果我们加载网页,它显示我的数据库中保存了四个文件。如果您想重复我的示例,并且您没有对数据库做任何操作,您应该会看到文件数为 0。处理数据库是一个很大的话题。这个小例子只是为了尝尝鲜。幸运的是,大多数数据库驱动程序都有很好的文档。您可以从此链接查看 MongoDB 的文档。
process.nextTick vs. setImmediate
Node.js 使用事件驱动、非阻塞 I/O 模型,使其轻量高效,非常适合运行在分布式设备上的数据密集型实时应用。大多数网站确实是数据密集型的。访问数据时,Node.js 不会等待数据操作完成。相反,在调度回调函数后,它会立即释放线程以处理其他并发请求。当数据操作完成时,回调函数将处理数据并向客户端发送响应。如果在任何情况下,我们有 CPU 密集型操作,我们需要将工作分成更小的块。完成一块工作后,我们需要在回调函数中调度其余工作并释放线程。否则,其他并发请求将被这个长时间运行的单片工作阻塞。
exports = module.exports = function(req, res){
var data = {};
data.title = 'process.nextTick vs. setImmediate';
var i = 1;
setImmediate(function() {
data.setImmediateExecutionOrder = i++;
if (data.setImmediateExecutionOrder && data.nextTickeExecutionOrder) {
res.render('nextTickOrSetImmediate', data);
}
});
process.nextTick(function() {
data.nextTickeExecutionOrder = i++;
if (data.setImmediateExecutionOrder && data.nextTickeExecutionOrder) {
res.render('nextTickOrSetImmediate', data);
}
});
};
有两种常用的调度回调函数的方法:“process.nextTick()
”和“setImmediate()
”。
<!DOCTYPE html>
<html>
<head>
<title><%=title%></title>
<link rel='stylesheet' href='/public/stylesheets/style.css' />
</head>
<body>
<h1><%=title%></h1>
<div>
<ul>
<li>process.nextTick execution order: <%=nextTickeExecutionOrder%></li>
<li>setImmediate execution order: <%=setImmediateExecutionOrder%></li>
</ul>
</div>
</body>
</html>
如果我们在上面的“ejs
”视图中渲染结果,我们得到以下结果
我们可以看到“process.nextTick()
”调度的回调函数在“setImmediate()
”调度的回调函数之前运行。根据文档
- “
process.nextTick()
” - 一旦当前事件循环完成,调用回调函数。 - “
setImmediate()
” - immediate 的回调按创建顺序排队。整个回调队列在每次事件循环迭代中处理。
Express 中的异常
异常从来都不是好事情。但与 Tomcat 和 IIS 等基于线程的 Web 服务器相比,Node.js 对异常更加敏感,因为多个请求由单个线程处理。如果异常处理不当,线程将死亡,来自完全不相关用户的多个请求将失败。
exports.exceptionInController = function(req, res) {
throw 'An artificial exception in controller';
res.send("send some data to the client");
};
exports.exceptionInView = function(req, res) {
// The exceptionInView.ejs file will throw an exception
var data = {};
res.render('exceptionInView', data);
};
exports.exceptionInCallback = function(req, res) {
setImmediate(function() {
throw 'An artificial exception in the callback';
res.send("send some data to the client");
});
};
如果我们使用 MVC 术语,上面的控制器实现了三个动作方法。每个方法都有一个人工异常。
- “
exceptionInController
” - 异常发生在控制器中 - “
exceptionInView
” - 异常发生在视图中 - “
exceptionInCallback
” - 异常发生在回调函数中
以下是抛出异常的“ejs
”视图“exceptionInView
”。
<!DOCTYPE html>
<html>
<head>
<title>Exception in the view</title>
<link rel='stylesheet' href='/public/stylesheets/style.css' />
</head>
<body>
<h1>Exception in the view</h1>
<%=A[1]%>
</body>
</html>
由于数组“A
”未定义,嵌入式代码“<%=A[1]%>
”将抛出异常。运行上述代码,我们可以发现以下内容
- Express 和 Node.js 处理控制器和视图中发生的异常。
- 回调函数中发生的异常不由 Express 和 Node.js 处理。如果我们不自己处理这些异常,线程将会崩溃和死亡。
上面显示控制器中的人工异常由 Express 处理。但在编写 Node.js 应用程序时,回调函数将被广泛使用。我们需要尽一切努力处理回调函数中所有可能的异常。如果我们没有正确处理它们,线程将会崩溃。
利用多核计算机
众所周知,Node.js 应用程序在单核计算机上以单线程运行。但大多数现代计算机都具有多核。如果我们在多核计算机上只使用单个线程,则会浪费计算资源。这个问题的答案是“cluster”模块。它是一个 Node.js 内置模块。我们需要做的就是使用以下代码来启动服务器。
// Start the server
var cluster = require('cluster');
var numCPUs = require('os').cpus().length;
if (cluster.isMaster) {
for (var i = 0; i < numCPUs; i++) { cluster.fork(); }
cluster.on('exit', function(deadWorker, code, signal) { cluster.fork(); });
} else {
http.createServer(app).listen(app.get('httpport'), function(){
console.log('Express server listening on port ' + app.get('httpport'));
});
}
- “
numCPUs
”是计算机 CPU 的数量。对于每个 CPU,我们将 fork 一个线程来监听 HTTP 请求。 - 该代码监听 cluster 对象的“
exit
”事件。如果一个线程因未捕获的异常而死亡,它将被重新启动。
Https 支持
作为生产就绪的 Web 服务器,Node.js 通过其“https
”模块内置了 Https 支持。由于我更喜欢 GUI 密钥管理工具,我选择密钥库资源管理器来创建私钥文件和公钥证书文件,以在本学习笔记中设置 Https 支持。如果您没有使用过密钥库资源管理器,您可以参考这个链接,我在其中使用它为 Tomcat 服务器设置 Https 支持。
Node.js 不使用密钥库来保存密钥/证书信息,所以密钥库的类型并不重要。但为了使用密钥库资源管理器,我无论如何都选择了“PKCS12”。
创建空的密钥库后,我们可以右键单击空部分以向密钥库添加密钥对。我们需要填写所需信息并为密钥对指定别名以将其添加到密钥库。在我的情况下,我直接使用了默认别名“localhost
”。
现在我们可以右键单击密钥库资源管理器中的密钥对 ->“导出”->“导出私钥”来创建私钥文件。请务必选择“OpenSSL”密钥类型。密钥库资源管理器会要求我们为文件命名并设置密码。我简单地将文件命名为“localhost.openssl
”,并将密码设置为“mypassword
”用于实验目的。
我们还需要右键单击密钥对 ->“导出”->“导出证书链”以获取证书文件。为简单起见,我直接使用了默认文件名“localhost.cer”。我们需要“localhost.openssl”和“localhost.cer”文件来设置 Node.js 上的 Https 支持。密钥库资源管理器会询问我们是否要保存密钥库。在本学习笔记中,这不是必需的。但如果您想保留密钥库以备将来参考,您可以保存它。然后我们可以在项目中创建一个文件夹,并将这两个文件添加到该文件夹中,然后使用以下代码启动 Node.js 服务器。
// Start the server
var tsloptions = {
key: fs.readFileSync('./server/localhost.openssl'),
cert: fs.readFileSync('./server/localhost.cer'),
passphrase: 'mypassword'
};
var cluster = require('cluster');
var numCPUs = require('os').cpus().length;
if (cluster.isMaster) {
for (var i = 0; i < numCPUs; i++) { cluster.fork(); }
cluster.on('exit', function(deadWorker, code, signal) { cluster.fork(); });
} else {
https.createServer(tsloptions, app).listen(app.get('httpsport'), function(){
console.log('Express server ssl listening on port ' + app.get('httpsport'));
});
}
然后我们可以打开浏览器并输入正确的 URL,以检查 Https 支持确实已启用。由于证书未由任何证书颁发机构签名,因此在进入网页之前,浏览器会向我们发出警告。在生产网站中,您需要让您的证书由证书颁发机构签名,以摆脱烦人的警告消息。
永远
众所周知,一个 Node.js 线程服务多个请求。如果发生未处理的异常,线程将死亡。在最坏的情况下,Node.js 服务器会崩溃。“forever”是一个运行 node 应用程序的工具。如果服务器宕机,forever 工具将重新启动它。
npm install forever -g
由于我们将在命令行中运行forever
,因此我们应该全局安装它。要启动 Node.js 服务器,我们可以打开 Windows 命令提示符并进入包含 Node.js 启动 JavaScript 文件的文件夹。在我们的“FirstNodeApplication
”程序中,它是“app.js”文件。然后我们可以发出以下命令来启动应用程序。
forever start -w app.js
“-w
”选项告诉 forever 如果应用程序中的任何文件被重新部署,则重新加载应用程序。要停止 forever,您可以发出以下命令
forever stop app.js
将 Node.js 应用程序作为服务运行
虽然“Forever
”是一个确保 Node.js 应用程序不停止的好工具,但理想情况是我们能将应用程序作为服务运行。在 Windows 环境中,我们有“node-windows”。文档提供了非常好的使用说明。我们可以将“node-windows
”作为依赖项添加到“package.json”文件中,并运行“npm install
”进行安装。然后我们可以创建一个 Node.js 模块来使用“node-windows”。
var Service = require('node-windows').Service;
// Get the absolute path to the "app.js" file
var apppath = require('path').join(__dirname, 'app.js');
var svc = new Service({
name:'First Node Application',
description: 'The First Node Application.',
script: apppath
});
svc.on('install',function(){
svc.start();
});
exports = module.exports = svc;
然后我们可以创建两个简单的单行 JavaScript 文件来使用此服务。其中一个 JavaScript 文件是“appinstall.js”。
require('./appservice').install();
另一个 JavaScript 文件是“appuninstall.js”。
require('./appservice').uninstall();
我们可以发出“node appinstall
”来将 Node.js 应用程序安装为 Windows 服务。我们可以在命令提示符窗口中或在 Eclipse 中右键单击“appinstall.js”文件来发出此命令。成功安装后,我们可以在服务管理器中看到该服务。
如果现在打开浏览器并输入正确的 URL,我们可以看到应用程序正在运行。
如果要卸载服务,只需发出命令“node appuninstall
”。根据文档,如果您不是 Windows 用户,也可以找到 Mac 和 Linux 版本的“node-windows”。
从 Localhost 公开
让人们访问运行在您本地主机上的测试网站可能会非常有用。为此,您可以查看 https://dashboard.ngrok.com/ (NGROK)。
关注点
- 这是关于 Node.js 的学习笔记。我意识到 Node.js 是一个比我最初想象的更庞大的主题。我希望我能足够简洁,用尽可能少的句子涵盖一些有趣的方面。
- 我已将示例项目附在此学习笔记中。如果您想运行它,您需要 Eclipse Kepler 或更高版本,并安装“Enide”插件。然后您可以创建一个空文件夹作为 Eclipse 工作区,并使用“文件”->“导入...”->“通用”->“将现有项目导入工作区”来导入它。
- 如果您想重复我的示例,但无法运行应用程序,常见原因之一是您的计算机上运行着另一个应用程序实例。由于端口号已被另一个实例占用,操作系统将阻止新实例启动。找到正在运行的实例并停止它之后,您应该能够启动新实例。
- 希望您喜欢我的帖子,也希望这篇文章能以某种方式帮助您。
历史
- 2015年2月27日:第一次修订