分类
nodejs

Node.js HTTP模块(node.js+express)

内置HTTP模块

Node.js 中有 HTTP 这个内置模块,HTTP 模块允许 Node.js 通过超文本传输协议(HTTP)传输数据。

如果要使用 HTTP 模块,需要先通过 require() 方法引入 HTTP 模块,如下所示:

var http = require('http');

简单服务器的创建

Node.js 中的 HTTP 模块可以创建一个 HTTP 服务器,该服务器侦听服务器端口并将响应返回给客户端。

示例:

使用createServer()方法创建HTTP服务器,如下所示:

var http = require('http');
http.createServer(function (req, res) {
  res.write('Hello xkd'); 
  res.end(); 
}).listen(8888); 

在命令工具中启动 Node.js 文件(你自定义的文件名),打开浏览器,访问http://127.0.0.1:8888/,就可以看到浏览器页面中会显示”Hello xkd“,这样一个最简单的服务器搭建成功了。

Node.js 中的 HTTP 模块中封装了一个 HTTP 服务器和一个简单的 HTTP 客户端,http.Server 是一个基于事件的 HTTP 服务器,http.request 则是一个 HTTP 客户端工具,用于向 HTTP 服务器发起请求。createServer() 方法中的两个参数 req 和 res 分别代表了请求对象和响应对象。其中 req 是 http.IncomingMessage 的实例,res 是 http.ServerResponse 的实例。

HTTP服务器

除了可以使用 createServer() 方法返回一个 http.Server 对象来创建服务器,我们还可以使用下列方法来实现。

示例:

创建一个http.Server对象,然后为其添加request事件监听:

var http = require("http");
var server = new http.Server();

server.on("request",function(req,res){
    res.writeHead(200,{        
      "content-type":"text/plain; charset=utf-8"
    });
    res.write("xkd");
    res.end();
});
server.listen(8888);

http.Server的事件

http.Server 是一个基于事件的服务器,继承自 EventEmitter。http.Server 提供的事件如下所示:

  • request:当客户端请求到来时,该事件被触发,提供两个参数req和res,表示请求和响应信息,是最常用的事件。
  • connection:当TCP连接建立时,该事件被触发,提供一个参数socket,是net.Socket的实例。
  • close:当服务器关闭时,触发事件(注意不是在用户断开连接时)。

HTTP客户端

http 模块提供了两个函数 http.request 和 http.get,功能是作为客户端向 http 服务器发起请求。

http.request(option,callback)

http.request() 方法接受两个参数,其中参数 option 是一个类似关联数组的对象,表示请求的参数。callback 是请求的回调函数,需要传递一个参数。

示例:


var http=require('http');
var querystring=require('querystring');
//启动服务
http.createServer(function(req,res){
    console.log('开始请求,解析参数');
    //解析post请求
    var post='';
    req.on('data',function(chunk){
        post+=chunk;
    });
    req.on('end',function(){
        post=querystring.parse(post);
        //解析完成
        console.log('参数解析完成,返回name参数');
        res.end(post.name);
    });
}).listen(8888);
 
 
//客户端请求
var contents=querystring.stringify({
    name:'Tom',
    age:18,
    city:'beijing'
});

//声明请求参数
var options={
    host:'localhost',
    path:'/',
    port:8888,
    method:'POST',
    headers:{
        'Content-Type':'application/x-www-form-urlencoded',
        'Content-Length':contents.length
    }
};
//发送请求
var req=http.request(options,function(res){
    res.setEncoding('utf-8');
    res.on('data',function(data){
        console.log('后台返回数据');
        console.log('name参数的值为:' + data);
    })
});
req.write(contents);
//必须调用end()
req.end();

然后启动 Node.js 文件,会输出如下内容:

> node file.js
开始请求,解析参数
参数解析完成,返回name参数
后台返回数据
name参数的值为:Tom

http.get(option,callback)

http.get() 方法是 http.request 方法的简化版,唯一区别只是 http.get 自动将请求方法设为了 GET 请求,同时不需要手动调用 req.end()。如果我们使用 http.request 方法时没有调用 end 方法,服务器将不会收到信息。

示例:

var http=require('http');
var url=require('url');
var util=require('util');
//启动服务
http.createServer(function(req,res){
    console.log('开始请求,解析参数');
    var params=url.parse(req.url,true);
    console.log('解析完成');
    console.log(util.inspect(params));
    console.log('向客户端返回name');
    res.end(params.query.name);
}).listen(8888);
 
//客户端请求
var request=http.get({
    host:'localhost',
    path:'/user?name=Tom&age=18&city=beijing',
    port:8888},function(res){
    res.setEncoding('utf-8');
    res.on('data',function(data){
        console.log('服务端相应name值为:'+data);
    })
});

然后启动 Node.js 文件,会输出如下内容:

>node file.js
开始请求,解析参数
解析完成
Url {
  protocol: null,
  slashes: null,
  auth: null,
  host: null,
  port: null,
  hostname: null,
  hash: null,
  search: '?name=Tom&age=18&city=beijing',
  query: { name: 'Tom', age: '18', city: 'beijing' },
  pathname: '/user',
  path: '/user?name=Tom&age=18&city=beijing',
  href: '/user?name=Tom&age=18&city=beijing' }
向客户端返回name
服务端相应name值为:Tom

分类
nodejs

node 中间层怎样做的请求合并转发(node作为中间件做接口转发)

1) 什么是中间层

  • 就是前端—请求—> nodejs —-请求—->后端 —-响应—>nodejs–数据处理—响应—->前端。这么一个流程,这个流程的好处就是当业务逻辑过多,或者业务需求在不断变更的时候,前端不需要过多当去改变业务逻辑,与后端低耦合。前端即显示,渲染。后端获取和存储数据。中间层处理数据结构,返回给前端可用可渲染的数据结构。
  • nodejs是起中间层的作用,即根据客户端不同请求来做相应的处理或渲染页面,处理时可以是把获取的数据做简单的处理交由底层java那边做真正的数据持久化或数据更新,也可以是从底层获取数据做简单的处理返回给客户端。
  • 通常我们把Web领域分为客户端和服务端,也就是前端和后端,这里的后端就包含了网关,静态资源,接口,缓存,数据库等。而中间层呢,就是在后端这里再抽离一层出来,在业务上处理和客户端衔接更紧密的部分,比如页面渲染(SSR),数据聚合,接口转发等等。
  • 以SSR来说,在服务端将页面渲染好,可以加快用户的首屏加载速度,避免请求时白屏,还有利于网站做SEO,他的好处是比较好理解的。

2)中间层可以做的事情

  • 代理:在开发环境下,我们可以利用代理来,解决最常见的跨域问题;在线上环境下,我们可以利用代理,转发请求到多个服务端。
  • 缓存:缓存其实是更靠近前端的需求,用户的动作触发数据的更新,node中间层可以直接处理一部分缓存需求。
  • 限流:node中间层,可以针对接口或者路由做响应的限流。
  • 日志:相比其他服务端语言,node中间层的日志记录,能更方便快捷的定位问题(是在浏览器端还是服务端)。
  • 监控:擅长高并发的请求处理,做监控也是合适的选项。
  • 鉴权:有一个中间层去鉴权,也是一种单一职责的实现。
  • 路由:前端更需要掌握页面路由的权限和逻辑。
  • 服务端渲染:node中间层的解决方案更灵活,比如SSR、模板直出、利用一些JS库做预渲染等等。

3)node转发API(node中间层)的优势

  • 可以在中间层把java|php的数据,处理成对前端更友好的格式
  • 可以解决前端的跨域问题,因为服务器端的请求是不涉及跨域的,跨域是浏览器的同源策略导致的
  • 可以将多个请求在通过中间层合并,减少前端的请求

4)如何做请求合并转发

  • 使用express中间件multifetch可以将请求批量合并
  • 使用express+http-proxy-middleware实现接口代理转发

5)不使用用第三方模块手动实现一个nodejs代理服务器,实现请求合并转发

1.实现思路

  • ①搭建http服务器,使用Node的http模块的createServer方法
  • ②接收客户端发送的请求,就是请求报文,请求报文中包括请求行、请求头、请求体
  • ③将请求报文发送到目标服务器,使用http模块的request方法

2.实现步骤

  • 第一步:http服务器搭建
const http = require("http");
const server = http.createServer();
server.on('request',(req,res)=>{
    res.end("hello world")
})
server.listen(3000,()=>{
    console.log("running");
})
  • 第二步:接收客户端发送到代理服务器的请求报文
const http = require("http");
const server = http.createServer();
server.on('request',(req,res)=>{
    // 通过req的data事件和end事件接收客户端发送的数据
    // 并用Buffer.concat处理一下
    //
    let postbody = [];
    req.on("data", chunk => {
        postbody.push(chunk);
    })
    req.on('end', () => {
        let postbodyBuffer = Buffer.concat(postbody);
        res.end(postbodyBuffer)
    })
})
server.listen(3000,()=>{
    console.log("running");
})

这一步主要数据在客户端到服务器端进行传输时在nodejs中需要用到buffer来处理一下。处理过程就是将所有接收的数据片段chunk塞到一个数组中,然后将其合并到一起还原出源数据。合并方法需要用到Buffer.concat,这里不能使用加号,加号会隐式的将buffer转化为字符串,这种转化不安全。

  • 第三步:使用http模块的request方法,将请求报文发送到目标服务器第二步已经得到了客户端上传的数据,但是缺少请求头,所以在这一步根据客户端发送的请求需要构造请求头,然后发送
const http = require("http");
const server = http.createServer();

server.on("request", (req, res) => {
    var { connection, host, ...originHeaders } = req.headers;
    var options = {
        "method": req.method,
        // 随表找了一个网站做测试,被代理网站修改这里
        "hostname": "www.nanjingmb.com",
        "port": "80",
        "path": req.url,
        "headers": { originHeaders }
    }
    //接收客户端发送的数据
    var p = new Promise((resolve,reject)=>{
        let postbody = [];
        req.on("data", chunk => {
            postbody.push(chunk);
        })
        req.on('end', () => {
            let postbodyBuffer = Buffer.concat(postbody);
            resolve(postbodyBuffer)
        })
    });
    //将数据转发,并接收目标服务器返回的数据,然后转发给客户端
    p.then((postbodyBuffer)=>{
        let responsebody=[]
        var request = http.request(options, (response) => {
            response.on('data', (chunk) => {
                responsebody.push(chunk)
            })
            response.on("end", () => {
                responsebodyBuffer = Buffer.concat(responsebody)
                res.end(responsebodyBuffer);
            })
        })
        // 使用request的write方法传递请求体
        request.write(postbodyBuffer)
        // 使用end方法将请求发出去
        request.end();
    })
});
server.listen(3000, () => {
    console.log("runnng");
})

分类
nodejs

使用 Mocha 和 Assert 测试 Node.js 模块(中)

在上文最后一步中,我们手动测试了我们的应用程序。这将适用于个别用例,但随着我们的模块扩展,这种方法变得不那么可行。当我们测试新功能时,我们必须确定添加的功能没有在旧功能中产生问题。我们希望针对代码中的每次更改重新测试每个功能,但是手动执行此操作会花费大量精力并且容易出错。

  更有效的做法是设置自动化测试。这些是像任何其他代码块一样编写的脚本测试。我们使用定义的输入运行我们的函数并检查它们的效果以确保它们的行为符合我们的预期。随着我们的代码库增长,我们的自动化测试也会增长。当我们在功能旁边编写新测试时,我们可以验证整个模块是否仍然有效——无需每次都记住如何使用每个功能。

  在本教程中,我们将 Mocha 测试框架与 Node.jsassert模块一起使用。让我们获得一些实践经验,看看它们是如何协同工作的。

  首先,创建一个新文件来存储我们的测试代码:

touch index.test.js

  现在使用您喜欢的文本编辑器打开测试文件。您可以nano像以前一样使用:

nano index.test.js

  在文本文件的第一行,我们将像在 Node.js shell 中一样加载 TODOs 模块。然后,我们将在编写测试时加载assert模块。添加以下行:

const Todos = require('./index');

const assert = require('assert').strict;

  该模块的strict属性assert将允许我们使用 Node.js 推荐的特殊相等测试,并且有利于未来的验证,因为它们考虑了更多的用例。

  在我们开始编写测试之前,让我们讨论一下 Mocha 如何组织我们的代码。在 Mocha 中构建的测试通常遵循以下模板:

describe([String with Test Group Name], function() {

it([String with Test Name], function() {

[Test Code]

});

});

  注意两个关键功能:describe()和it()。该describe()函数用于对类似的测试进行分组。Mocha 不需要运行测试,但是分组测试使我们的测试代码更易于维护。建议您以一种便于您将相似的测试一起更新的方式对测试进行分组。

  包含我们的it()测试代码。这是我们将与模块的功能交互并使用assert库的地方。许多it()函数可以在一个describe()函数中定义。

  我们在本节中的目标是使用 Mocha 并assert自动化我们的手动测试。我们将从我们的描述块开始逐步执行此操作。在模块行之后将以下内容添加到您的文件中:

describe("integration test", function() {

});

  使用这个代码块,我们为我们的集成测试创建了一个分组。单元测试将一次测试一个功能。集成测试验证模块内或模块之间的功能如何协同工作。当 Mocha 运行我们的测试时,该描述块中的所有测试都将在该"integration test"组下运行。

  让我们添加一个it()函数,以便我们可以开始测试我们模块的代码:

describe("integration test", function() {

it("should be able to add and complete TODOs", function() {

});

});

  请注意我们为测试命名的描述性。如果有人运行我们的测试,将立即清楚什么是通过或失败。一个经过良好测试的应用程序通常是一个文档齐全的应用程序,并且测试有时可以成为一种有效的文档。

  对于我们的第一个测试,我们将创建一个新Todos对象并验证其中没有项目:

describe("integration test", function() {

it("should be able to add and complete TODOs", function() {

let todos = new Todos();

assert.notStrictEqual(todos.list().length, 1);

});

});

  第一行新代码实例化了一个新Todos对象,就像我们在 Node.js REPL 或其他模块中所做的那样。在第二个新行中,我们使用assert模块。

  从assert模块中我们使用notStrictEqual()方法。这个函数有两个参数:我们想要测试的值(称为actual值)和我们期望获得的值(称为expected值)。如果两个参数相同,则notStrictEqual()抛出错误以使测试失败。

  保存并退出index.test.js。

  基本情况将是真的,因为长度应该是0,而不是1。让我们通过运行 Mocha 来确认这一点。为此,我们需要修改我们的package.json文件。package.json使用文本编辑器打开文件:

nano package.json

  现在,在您的scripts属性中,将其更改为如下所示:

"scripts": {

"test": "mocha index.test.js"

},

  我们刚刚改变了 npm 的 CLItest命令的行为。当我们运行时npm test,npm 会检查我们刚刚输入的命令package.json。它将在我们的文件夹中查找 Mocha 库并使用我们的测试文件node_modules运行命令。

  保存并退出package.json。

  让我们看看运行测试时会发生什么。在您的终端中,输入:

npm test

  该命令将产生以下输出:

Output

> [email protected] test your_file_path/todos

> mocha index.test.js

integrated test

? should be able to add and complete TODOs

1 passing (16ms)

  这个输出首先向我们展示了它将运行哪组测试。对于组中的每个单独测试,测试用例都会缩进。我们看到我们在it()函数中描述的测试名称。测试用例左侧的勾号表示测试通过。

  在底部,我们得到了所有测试的摘要。在我们的例子中,我们的一项测试通过并在 16 毫秒内完成(时间因计算机而异)。

  我们的测试已经开始成功。但是,当前的测试用例可能会出现误报。误报是当它应该失败时通过的测试用例。

  我们目前检查数组的长度是否不等于1。让我们修改测试,使这个条件在不应该成立时成立。将以下行添加到index.test.js:

describe("integration test", function() {

it("should be able to add and complete TODOs", function() {

let todos = new Todos();

todos.add("get up from bed");

todos.add("make up bed");

assert.notStrictEqual(todos.list().length, 1);

});

});

  保存并退出文件。

  我们添加了两个 TODO 项目。让我们运行测试看看会发生什么:

npm test

  这将给出以下内容:

Output

integrated test

? should be able to add and complete TODOs

1 passing (8ms)

  按预期通过,因为长度大于 1。但是,它违背了进行第一次测试的最初目的。第一个测试旨在确认我们从空白状态开始。更好的测试将在所有情况下证实这一点。

  让我们更改测试,使其只有在我们绝对没有 TODO 存储时才能通过。对进行以下更改index.test.js:

describe("integration test", function() {

it("should be able to add and complete TODOs", function() {

let todos = new Todos();

todos.add("get up from bed");

todos.add("make up bed");

assert.strictEqual(todos.list().length, 0);

});

});

  您更改notStrictEqual()为strictEqual(),一个检查其实际参数和预期参数是否相等的函数。如果我们的论点不完全相同,严格相等将失败。

  保存并退出,然后运行测试,这样我们就可以看到会发生什么:

npm test

  这一次,输出将显示错误:

Output

integration test

1) should be able to add and complete TODOs

0 passing (16ms)

1 failing

1) integration test

should be able to add and complete TODOs:

AssertionError [ERR_ASSERTION]: Input A expected to strictly equal input B:

+ expected – actual

– 2

+ 0

+ expected – actual

-2

+0

at Context.<anonymous> (index.test.js:9:10)

npm ERR! Test failed. See above for more details.

  我们的测试摘要不再位于输出的底部,而是显示在我们的测试用例列表之后:

0 passing (29ms)

1 failing

  剩余的输出为我们提供了有关我们失败的测试的数据。首先,我们看看哪个测试用例失败了:

1) integrated test

should be able to add and complete TODOs:

  然后,我们看到了为什么我们的测试失败了:

AssertionError [ERR_ASSERTION]: Input A expected to strictly equal input B:

+ expected – actual

– 2

+ 0

+ expected – actual

-2

+0

at Context.<anonymous> (index.test.js:9:10)

  失败AssertionError时抛出一个。strictEqual()我们看到expected值 0 与actual值 2 不同。

  然后我们在测试文件中看到代码失败的行。在这种情况下,它是第 10 行。

  现在,我们已经亲眼看到,如果我们预期不正确的值,我们的测试将会失败。让我们将测试用例改回正确的值。首先,打开文件:

nano index.test.js

  然后取出这些todos.add行,使您的代码如下所示:

describe("integration test", function () {

it("should be able to add and complete TODOs", function () {

let todos = new Todos();

assert.strictEqual(todos.list().length, 0);

});

});

  保存并退出文件。

  再次运行它以确认它通过而没有任何潜在的误报:

npm test

  输出如下:

Output

integration test

? should be able to add and complete TODOs

1 passing (15ms)

  我们现在已经大大提高了测试的弹性。让我们继续我们的集成测试。下一步是将新的 TODO 项添加到index.test.js:

describe("integration test", function() {

it("should be able to add and complete TODOs", function() {

let todos = new Todos();

assert.strictEqual(todos.list().length, 0);

todos.add("run code");

assert.strictEqual(todos.list().length, 1);

assert.deepStrictEqual(todos.list(), [{title: "run code", completed: false}]);

});

});

  使用该add()函数后,我们确认我们现在有一个 TODO 由我们的todos对象管理,带有strictEqual(). 我们的下一个测试证实了todoswith中的数据deepStrictEqual()。该deepStrictEqual()函数递归地测试我们的预期对象和实际对象是否具有相同的属性。在这种情况下,它会测试我们期望的数组中是否都有一个 JavaScript 对象。然后它检查它们的 JavaScript 对象是否具有相同的属性,即它们的title属性都是"run code"并且它们的completed属性都是false.

  然后,我们通过添加以下突出显示的行,根据需要使用这两个相等检查来完成剩余的测试:

describe("integration test", function() {

it("should be able to add and complete TODOs", function() {

let todos = new Todos();

assert.strictEqual(todos.list().length, 0);

todos.add("run code");

assert.strictEqual(todos.list().length, 1);

assert.deepStrictEqual(todos.list(), [{title: "run code", completed: false}]);

todos.add("test everything");

assert.strictEqual(todos.list().length, 2);

assert.deepStrictEqual(todos.list(),

[

{ title: "run code", completed: false },

{ title: "test everything", completed: false }

]

);

todos.complete("run code");

assert.deepStrictEqual(todos.list(),

[

{ title: "run code", completed: true },

{ title: "test everything", completed: false }

]

);

});

});

  保存并退出文件。

  我们的测试现在模仿我们的手动测试。使用这些程序化测试,如果我们的测试在运行时通过,我们就不需要连续检查输出。您通常希望测试使用的各个方面,以确保正确测试代码。

  npm test让我们再次运行我们的测试以获得这个熟悉的输出:

Output

integrated test

? should be able to add and complete TODOs

1 passing (9ms)

  您现在已经使用 Mocha 框架和assert库设置了一个集成测试。

  让我们考虑一下我们与其他一些开发人员共享我们的模块并且他们现在正在给我们反馈的情况。如果到目前为止还没有添加 TODO ,我们的很大一部分用户希望该complete()函数返回错误。让我们在我们的函数中添加这个complete()功能。

  index.js在文本编辑器中打开:

nano index.js

  将以下内容添加到函数中:

complete(title) {

if (this.todos.length === 0) {

throw new Error("You have no TODOs stored. Why don't you add one first?");

}

let todoFound = false

this.todos.forEach((todo) => {

if (todo.title === title) {

todo.completed = true;

todoFound = true;

return;

}

});

if (!todoFound) {

throw new Error(`No TODO was found with the title: "${title}"`);

}

}

  保存并退出文件。

  现在让我们为这个新特性添加一个新的测试。我们想验证如果我们对一个Todos没有项目的对象调用 complete ,它将返回我们的特殊错误。

  回到index.test.js:

nano index.test.js

  在文件末尾,添加以下代码:

describe("complete()", function() {

it("should fail if there are no TODOs", function() {

let todos = new Todos();

const expectedError = new Error("You have no TODOs stored. Why don't you add one first?");

assert.throws(() => {

todos.complete("doesn't exist");

}, expectedError);

});

});

  我们的测试从创建一个新todos对象开始。然后我们定义我们在调用complete()函数时期望收到的错误。

  接下来,我们使用模块的throws()功能assert。创建了这个函数,以便我们可以验证代码中抛出的错误。它的第一个参数是一个包含引发错误的代码的函数。第二个参数是我们期望收到的错误。

  在您的终端中,npm test再次运行测试,您现在将看到以下输出:

Output

integrated test

? should be able to add and complete TODOs

complete()

? should fail if there are no TODOs

2 passing (25ms)

  至此,我们的测试已经验证了同步代码的结果。让我们看看我们需要如何验证异步代码。

分类
nodejs

node相关知识点(上)(node指什么)

Node 里的模块是什么?

node 中,每个文件模块都是一个对象,它定义如下:

function Module(id, parent) {
  this.id = id;
  this.exports = {};
  this.parent = parent;
  this.filename = null;
  this.loaded = false;
  this.children = [];
}

module.exports = Module;
var module = new Module(filename, parent);

require的模块加载机制

  1. 计算模块的绝对路径
  2. 如果缓存中有该模块,则从缓存中取出该模块
  3. 按优先级依次寻找并编译执行模块,将模块推入缓存(require.cache)中
  4. 输出模块的exports 属性

Node中内存泄漏问题和解决方案

内存泄漏原因

  • 全局变量:全局变量挂载在root对象上,不会被清除掉
  • 闭包:如果闭包未释放,就会导致内存泄漏
  • 事件监听: 对同一个事件重复监听,忘记移除(removeListener),将造成内存泄漏

解决方案

  • 最容易出现也是最难排查的就是事件监听造成的内存泄漏,所以事件监听这块需要格外注意小心使用。
  • 如果出现了内存泄漏问题,需要检测内存使用情况,对内存泄漏的位置进行定位,然后对对应的内存泄漏代码进行修复
  • 使用浏览器的快照功能来测试快速定位问题

两个模块相互引用会发生什么问题?

假设 A 和 B 模块相互引用,此时运行 A 模块

  • A 模块将会被缓存,但是此时缓存的是一个未执行完毕的 A 模块
  • A 模块引入 B 模块将会被完整地加载并且正常的使用
  • B 模块中调用 A 模块将会是默认的空对象(module.exports的默认值),A 模块不具有任何功能

如何实现热更新

node 中有一个Api 是 require.cache,如果这个对象中的引用被清除后,下次再调用就会重新加载,这个机制可以用来热加载更新的模块

function clearCache(modulePath) {
    const path = require.resolve(modulePath)
    if (require.cache[path]) {
        require.cache[path] = null
    }
}

然后使用 fs.watchFile 监听文件的更改,文件更改后调用 clearCache 传入对应的模块名

使用 pm2 reload 也可以实现暴力更新,它会保证在新的实例重启成功后才会把旧的进程杀死,可以保证服务一直能够响应

Node 适合处理 I/O 密集型任务

  • node 在处理 I/O 密集型任务的时候可以异步调用,利用事件循环的处理能力,资源占用极少,事件循环可以避开多线程调用,在调用方面是单线程,内部处理其实是多线程
  • javascript 是单线程的原因,node 不合适处理 CPU 密集型的任务, CPU 密集型的任务会导致CPU 时间片不能释放,使得后续 I/O 无法发起,从而会造成堵塞,这时候可以使用多线程来处理,但是js不支持多线程
分类
nodejs

(转)Node.js 项目的配置文件(配置nodejs环境变量)

作者:老雷

原文地址:https://morning.work/page/2015-09/nodejs_project_config_loader.html


在使用 Node.js 编写一个完整的项目时,程序中往往需要用到一些可配置的变量,从而使得程序能在不同的环境中运行。本文将介绍几种常见的方法。

通过环境变量指定配置

环境变量(environment variables) 一般是指在操作系统中用来指定操作系统运行环境的一些参数,如:临时文件夹位置和系统文件夹位置等。比如HOME表示当前用户的根目录,TMPDIR表示系统临时目录等,我们可以通过设置一些特定的环境变量,程序在启动时可以读取这些环境变量并做相应的初始化动作。

在 Node.js 中可以通过process.env来访问当前的环境变量信息,比如:

{ PATH: '/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin',
  TMPDIR: '/var/folders/rs/g4wqpvvj7bj08t35dxvfm0rr0000gn/T/',
  LOGNAME: 'glen',
  XPC_FLAGS: '0x0',
  HOME: '/Users/glen',
  TERM: 'xterm-256color',
  COLORFGBG: '7;0',
  USER: 'glen',
  ITERM_PROFILE: 'Glen',
  TERM_PROGRAM: 'iTerm.app',
  XPC_SERVICE_NAME: '0',
  SHELL: '/bin/zsh',
  ITERM_SESSION_ID: 'w0t4p0',
  PWD: '/Users/glen/work',
  __CF_USER_TEXT_ENCODING: '0x1F5:0x0:0x0',
  LC_CTYPE: 'UTF-8',
  SHLVL: '1',
  OLDPWD: '/Users/glen/work',
  ZSH: '/Users/glen/.oh-my-zsh',
  PAGER: 'less',
  LESS: '-R',
  LSCOLORS: 'Gxfxcxdxbxegedabagacad',
  AUTOJUMP_SOURCED: '1',
  AUTOJUMP_ERROR_PATH: '/Users/glen/Library/autojump/errors.log',
  RUST_SRC_PATH: '/Users/glen/work/source/rust/src',
  _: '/usr/local/bin/node' }

设置环境变量

环境变量的名字一般为大写,多个单词之间可通过下划线来连接。

Windows 系统下可通过set命令来设置环境变量,比如:

$ set HELLO_MSG="Hello, world!"

Linux 系统下可通过export命令来设置,比如:

$ export HELLO_MSG="Hello, world!"

在 Node.js 中读取环境变量

创建文件1.js,代码如下:

console.log(process.env.HELLO_MSG);

然后在命令行中执行:

$ export HELLO_MSG="Hello, world" && node 1.js

控制台将输出Hello, world,即我们启动程序时给环境变量HELLO_MSG设置的值。

通过配置文件指定配置

一些规模较小的项目往往会通过单一的配置文件来存储其配置,比如 CNode 中文社区的开源项目 nodeclub 在启动时会载入文件config.js,该文件的大概结构如下:

var config = {
  // debug 为 true 时,用于本地调试
  debug: true,

  name: 'Nodeclub', // 社区名字
  description: 'CNode:Node.js专业中文社区', // 社区的描述
  keywords: 'nodejs, node, express, connect, socket.io',
  
  // 其他配置项...
};
module.exports = config;

在程序启动的时候,可以使用require()来载入此文件,得到一个对象,然后通过此对象的属性来读取相应的配置信息:

// 载入配置文件
var config = require('./config');

// 以下为使用到配置的部分代码:
if (!config.debug && config.oneapm_key) {
  require('oneapm');
}

app.use(session({
  secret: config.session_secret,
  store: new RedisStore({
    port: config.redis_port,
    host: config.redis_host,
  }),
  resave: true,
  saveUninitialized: true,
}))

app.listen(config.port, function () {
  logger.log('NodeClub listening on port', config.port);
  logger.log('God bless love....');
  logger.log('You can debug your app with http://' + config.hostname + ':' + config.port);
  logger.log('');
});

使用配置文件与使用环境变量来指定配置相比,配置文件的可读性更强,可以表示一些更复杂的结构,而使用环境变量一般只限于key=value的形式。但在配置项数量较少时,使用环境变量会更简单,比如项目中只需要配置一个监听端口,可以简单使用export PORT=3000 && node app.js命令来启动程序,而不需要单独创建一个配置文件。大多数时候往往会结合这两种方式来进行,下文讲详细讲解。

其他配置文件格式

一般为了方便,在 Node.js 项目中会习惯使用.js文件格式,它的好处是可以使用通过程序来动态生成一些配置项,比如 nodeclub 的其中一个配置项:

var config = {
  // 文件上传配置
  // 注:如果填写 qn_access,则会上传到 7牛,以下配置无效
  upload: {
    path: path.join(__dirname, 'public/upload/'),
    url: '/public/upload/'
  },
}

其中使用到了path.join()__dirname来生成upload.path

JSON格式

另外,我们也可以使用 JSON 格式的配置文件,比如文件config.json

{
  "debug": true,
  "name": "Nodeclub",
  "description": "CNode:Node.js专业中文社区",
  "keywords": "nodejs, node, express, connect, socket.io"
}

在程序中可以通过以下方式来载入JSON文件配置:

// 通过require()函数
var config = require('./config.json');

// 读取文件并使用JSON.parse()解析
var fs = require('fs');
var config = JSON.parse(fs.readFileSync('./config.json').toString());

大多数时候,我们往往需要添加一些备注信息来说明某个配置项的使用方法及用途,在标准JSON文件中是不允许添加备注的,我们可以使用strip-json-comments模块来去掉配置文件中的备注,再将其当作标准的JSON来解析。

比如以下是带备注信息的JSON配置文件:

{
  // debug 为 true 时,用于本地调试
  "debug": true,
  // 社区名字
  "name": "Nodeclub",
  // 社区的描述
  "description": "CNode:Node.js专业中文社区",
  "keywords": "nodejs, node, express, connect, socket.io"
}

我们可以编写一个loadJSONFile()函数来载入带有备注的JSON文件:

var fs = require('fs');
var stripJsonComments = require('strip-json-comments');

function loadJSONFile (file) {
  var json = fs.readFileSync(file).toString();
  return JSON.parse(stripJsonComments(json));
}

var config = loadJSONFile('./config.json');
console.log(config);

YAML格式

YAML 是面向所有编程语言的对人类友好的数据序列化标准。其最大的优点是可读性较好,比如以下 YAML 格式的配置:

name: John Smith
age: 37
spouse:
  name: Jane Smith
  age: 25
children:
  - name: Jimmy Smith
    age: 15
  - name: Jenny Smith
    age: 12

其对应的JSON结构如下:

{
  "age": 37, 
  "spouse": {
    "age": 25, 
    "name": "Jane Smith"
  }, 
  "name": "John Smith", 
  "children": [
    {
      "age": 15, 
      "name": "Jimmy Smith"
    }, 
    {
      "age": 12, 
      "name": "Jenny Smith"
    }
  ]
}

在 Node.js 中可以通过yamljs模块来解析 YAML 格式,比如可以编写一个loadYAMLFile()函数来载入 YAML 格式的配置文件:

var fs = require('fs');
var YAML = require('yamljs');

function loadYAMLFile (file) {
  return YAML.parse(fs.readFileSync(file).toString());
}

var config = loadYAMLFile('./config.yaml');
console.log(config);

根据运行环境选择不同的配置

大多数情况下,程序在本地开发环境和生产环境中的配置信息是不一样的,比如开发时连接到的数据库里面的数据是模拟出来的,而生产环境要连接到实际的数据库上,因此我们需要让程序能根据不同的运行环境来载入不同的配置文件。

使用单一配置文件名

以 nodeclub 项目为例,其载入的配置文件名为./config.js,项目中有一个默认配置文件./config.default.js。要运行程序,首先需要复制一份默认配置文件,并保存为./config.js,再根据当前运行环境来修改./config.js

由于./config.js文件已经被添加到.gitignore文件中,因此我们./config.js文件的修改不会被纳入到项目的版本管理中,所以不同机器中的./config.js不会产生冲突,可以使用各自的配置来启动程序。

通过环境变量指定配置文件名

我们可以通过环境变量来指定配置文件,比如:

$ export CONFIG_FILE="./config/production.js"

然后可以通过以下方式来载入配置文件:

var path = require('path');
var config = require(path.resolve(process.env.CONFIG_FILE));

另外,也可以通过环境变量来指定当前运行环境的名称,然后在指定目录下载入相应的配置,比如:

$ export NODE_ENV="production"

然后可以通过以下方式来载入配置文件:

var path = require('path');
var configFile = path.resolve('./config', process.env.NODE_ENV + '.js');
var config = require(configFile);

使用 config 模块来读取配置

config 模块是 NPM 上下载量最高的 Node.js 配置文件管理模块,其实现原理与上文中介绍的方法大同小异,在实际开发中我们可以考虑使用这个现成的模块。下面将介绍此模块的简单使用方法。

config模块通过环境变量NODE_CONFIG_DIR来指定配置文件所在的目录,默认为./config(即当前运行目录下的config目录),通过环境变量NODE_ENV来指定当前的运行环境版本。

配置文件使用 JSON 格式,模块加载后,会首先载入默认的配置文件${NODE_CONFIG_DIR}/default.json,再载入文件${NODE_CONFIG_DIR}/${NODE_ENV}.json,如果配置项有冲突则覆盖默认的配置。

比如我们新建默认配置文件config/default.json

{
  // Customer module configs
  "Customer": {
    "dbConfig": {
      "host": "localhost",
      "port": 5984,
      "dbName": "customers"
    },
    "credit": {
      "initialLimit": 100,
      // Set low for development
      "initialDays": 1
    }
  }
}

再新建production环境配置文件config/production.json

{
  "Customer": {
    "dbConfig": {
      "host": "prod-db-server"
    },
    "credit": {
      "initialDays": 30
    }
  }
}

再新建测试文件1.js

var config = require('config');
console.log(config);

执行程序,可看到其输出的结果为默认的配置:

{ Customer:
   { dbConfig: { host: 'localhost', port: 5984, dbName: 'customers' },
     credit: { initialLimit: 100, initialDays: 1 } } }

假如要使用production的配置,则使用以下命令启动:

$ export NODE_ENV=production && node 1.js

则其输出将是如下结果:

{ Customer:
   { dbConfig: { host: 'prod-db-server', port: 5984, dbName: 'customers' },
     credit: { initialLimit: 100, initialDays: 30 } } }

production.json文件中,重新定义了Customer.dbConfig.hostCustomer.credit.initialDays这两个配置项,所以在production环境中仅这两项被覆盖为新的值,而其他配置项则使用default.json中指定的值。

载入config模块后,其返回的对象实际上就是当前的配置信息,同时提供了两个方法get()has()来操作配置项。比如:

var config = require('config');
console.log(config);
console.log(config.get('Customer'));
console.log(config.get('Customer.dbConfig'));
console.log(config.has('Customer.dbConfig.host'));
console.log(config.has('Customer.dbConfig.host2'));

执行程序后输出结果如下:

{ Customer:
   { dbConfig: { host: 'localhost', port: 5984, dbName: 'customers' },
     credit: { initialLimit: 100, initialDays: 1 } } }
{ dbConfig: { host: 'localhost', port: 5984, dbName: 'customers' },
  credit: { initialLimit: 100, initialDays: 1 } }
{ host: 'localhost', port: 5984, dbName: 'customers' }
true
false

其中get()用来获取指定配置,可以使用诸如Customer.dbConfig这样的格式,如果配置项不存在则会抛出异常。has()用来检测指定配置项是否存在,如果存在则返回true

关于config模块的详细使用方法可阅读其帮助文档。

分类
nodejs

Electron 使用Node原生模块(electron node版本对应关系)

本节我们学习如何在 Electron 中使用 Node 原生模块。

Electron 支持原生的 Node 模块,但由于和官方的 Node 相比,Electron 有可能使用一个和我们系统上所安装的 Node 不同的 V8 引擎,所以使用的模块需要重新编译才能使用。如果我们想编译原生模块,则需要手动设置 Electronheaders 的位置。

如何安装原生模块

有三种安装原生模块的方法,分别是 :

  • Electron 安装并重新编译模块。
  • 通过 npm 安装原生模块。
  • Electron 手动编译。

为Electron安装并重新编译模块

最简单的方式就是通过 electron-rebuild 包为 Electron 重建模块,该模块可以自动确定 Electron 的版本,并处理下载 headers、为应用程序重建本机模块等步骤。

示例:

例如要通过 electron-rebuild 来重建模块,首先需要安装 electron-rebuild

npm install --save-dev electron-rebuild

每次运行 npm install 时,也会同时运行下面这条命令:

./node_modules/.bin/electron-rebuild

windows 下如果上述命令遇到了问题,可以尝试执行如下命令:

.\node_modules\.bin\electron-rebuild.cmd

通过npm安装

我们还可以通过 npm 来直接安装原生模块。大部分步骤和安装普通模块时一样,但是需要自己设置一些系统环境变量。

示例:

例如要安装所有 Electron 的依赖:

# Electron的版本
export npm_config_target=1.2.3
# Electron的目标架构
export npm_config_arch=x64
export npm_config_target_arch=x64
# 下载Electron的headers
export npm_config_disturl=https://electronjs.org/headers
# 告诉node-pre-gyp我们是在为Electron生成模块
export npm_config_runtime=electron
# 告诉node-pre-gyp从源代码构建模块
export npm_config_build_from_source=true
# 安装所有依赖,并缓存到 ~/.electron-gyp
HOME=~/.electron-gyp npm install

为Electron手动编译

原生模块的开发人员如果想要在 Electron 中进行测试,可能要手动编译 Electron 模块。可以使用 node-gyp 来直接编译。

示例:

例如我们要告诉 node-gyp 去哪下载 Electronheaders,以及下载什么版本:

$ cd /path-to-module/
$ HOME=~/.electron-gyp node-gyp rebuild --target=0.29.1 --arch=x64 --dist-url=https://atom.io/download/atom-shell
  • HOME=~/.electron-gyp :设置去哪找开发时的 headers
  • –target=0.29.1 :设置了 Electron 的版本。
  • –dist-url=… :设置了 Electronheaders 的下载地址。
  • –arch=x64 :设置了该模块为适配 64 位操作系统而编译。

链接:https://www.9xkd.com/

分类
易语言例程

易语言调用node模块

调用自定义模块需要全局安装,可以更改模块输出文件到根目录,也有点麻烦可能用的少就输出到临时目录了.

易语言调用node模块.png