nodejs事件机制
http服务器和客户端
node.js标准库提供了http模块,其中封装了一个高效的http服务器和一个简易的http客户端
HTTP服务器
1. http.createServer()创建HTTP服务器。
//服务器实例
var httpServer = require("http")
httpServer.createServer(function(req,res){
res.writeHead(200,{"Content-Type":"text/html"});
res.write("<h2>hello world</h2>");
res.end("<h3>你好</h3>");
}).listen(2333);
console.log("http server is listening at port 2333,localhost:2333");
将一个函数作为HTTP请求处理函数,这个函数接受两个参数,分别是请求对象(req)和响应对象(res),在函数体内,res显式的写回了响应代码200(表示请求成功),指定响应头为"Content-Type":"text/html",然后写入响应体通过res.end()结束并发送,最后调用listen函数,启动服务器并且监听2333端口。
- 运行之后,我们在浏览器打开localhost:2333,就能简单的访问我们实现的接口了
- 点击Network,我们看到了我们平时调用的接口,和返回的数据。
通过http.createServer方法创建HTTP服务器后,就可以使用http的服务端功能。node的HTTP服务端,主要涉及http.server,http.serverResponse,http.IncomingMessage三个对象。
2. http.Server
http.server是一个基于事件的http服务器,所有的请求都被封装为独立的事件,开发者只需要对他的事件编写响应函数即可实现HTTP服务器的所有功能,它继承自EventEmitter,他的核心由Node.js下层的C++部分实现,而接口由JavaScript封装,兼顾了性能和简易性,
为了处理客户端请求,需要在服务端监听来自客户的'request'事件,'request'事件的回调函数中,会返回一个http.IncomingMessage实例和一个http.ServerResponse。
- connection: 当TCP链接建立时,该事件被触发,提供一个参数socket,为net.Socket的实例,connection事件的粒度要大于request,因为客户端在keep-Alive模式下可能在同一个链接内发送多次请求(相对于createserver,可以进行前后端实时通信)
wsServer.on('connection',function(sock){
sock.on('a',function(num1,num2){
console.log(`接到了浏览器发送的数据:${num1}`)
})
setInterval(function(){
sock.emit('ttt',Math.random())
},500)
})
- close: 当服务器关闭时,该事件被触发,注意不是在用户连接断开时,等等事件
- request: 每次接收到一个请求时触发。
var server = new http.Server();
server.on("request", function (req, res) {
res.writeHead(200, { "Content-Type": "text/html" });
res.write("<h2>node.js</h2>");
res.end("<h3>test</h3>");
}).listen(2333);
console.log("http server is listening at port 2333:2333");
req 是一个http.IncomingMessage实例
res 是一个http.ServerResponse实例
3. http.ServerResponse 响应 res
http.ServerResponse是返回给客户端的信息,决定了用户最终能看到的结果,它也是由http.Server的request事件发送的,作为第二个参数传递,一般简称response或者res。
http.ServerResponse有三个重要的成员函数,用于返回响应头,响应内容以及结束请求:
(1)response.writeHead(statsCode,[headers]):向请求的客户端发送响应头,statusCode是HTTP状态码如200(请求成功),404(未找到)等,headers是一个类似关联数组的对象,表示响应头的每个属性,该函数在请求内最多只能调用一次,如果不调用,则会自动生成一个响应头。
(2)response.write(data,[encoding]):向请求的客户端发送响应内容,data是一个buffer或者字符串,表示要发送的内容,如果data是字符串,那么需要指定encoding编码方式,默认是utf-8,在response.end之前调用,response.write可以被调用多次;
(3)response.end([data],[encoding]):结束响应,告知客户端所有发送已经完成,当所有要返回的内容发送完毕的时候,该函数必须调用一次,他可接受两个参数,意义和response.write相同,如果不调用该函数,客户端将永远处于等待状态;
4. http.IncomingMessage 请求 req
HTTP客户端
http提供了两个函数http.request和http.get,作为客户端向HTTP服务器发起请求;
1. http.request(options[, callback])
http.request则是一个http客户端工具,用于向http服务发起请求;
const postData = querystring.stringify({
'msg' : 'Hello World!'
});
const options = {
hostname: 'www.google.com',
port: 80,
path: '/upload',
method: 'POST',
headers: {
'Content-Type': 'application/x-www-form-urlencoded',
'Content-Length': Buffer.byteLength(postData)
}
};
const req = http.request(options, (res) => {
console.log(`状态码: ${res.statusCode}`);
console.log(`响应头: ${JSON.stringify(res.headers)}`);
res.setEncoding('utf8');
res.on('data', (chunk) => {
console.log(`响应主体: ${chunk}`);
});
res.on('end', () => {
console.log('响应中已无数据。');
});
});
req.on('error', (e) => {
console.error(`请求遇到问题: ${e.message}`);
});
// 写入数据到请求主体
req.write(postData);
req.end();
注意,在例子中调用了 req.end()。 使用 http.request() 必须总是调用 req.end() 来表明请求的结束,即使没有数据被写入请求主体。
如果请求过程中遇到任何错误(DNS 解析错误、TCP 级的错误、或实际的 HTTP 解析错误),则在返回的请求对象中会触发 'error' 事件。 对于所有的 'error' 事件,如果没有注册监听器,则抛出错误。
以下是需要注意的几个特殊的请求头。
-
发送 'Connection: keep-alive' 会通知 Node.js,服务器的连接应一直持续到下一个请求。
-
发送 'Content-Length' 请求头会禁用默认的块编码。
-
发送 'Expect' 请求头会立即发送请求头。 通常情况下,当发送 'Expect: 100-continue' 时,超时时间与 continue 事件的监听器都需要被设置。 详见 RFC2616 章节 8.2.3。
-
发送 Authorization 请求头会替代 auth 选项计算基本身份验证。
2. http.get(options[, callback])
该对象在HTTP服务器内部被创建。作为第二个参数呗传入'request'事件
-
接收与http.request()相同的设置。 method一直设置为GET,忽略继承自原型的属性
-
返回: <http.ClientRequest>
http.get('http://nodejs.org/dist/index.json', (res) => {
const { statusCode } = res;
const contentType = res.headers['content-type'];
let error;
if (statusCode !== 200) {
error = new Error('请求失败。
' +
`状态码: ${statusCode}`);
} else if (!/^application/json/.test(contentType)) {
error = new Error('无效的 content-type.
' +
`期望 application/json 但获取的是 ${contentType}`);
}
if (error) {
console.error(error.message);
// 消耗响应数据以释放内存
res.resume();
return;
}
res.setEncoding('utf8');
let rawData = '';
res.on('data', (chunk) => { rawData += chunk; });
res.on('end', () => {
try {
const parsedData = JSON.parse(rawData);
console.log(parsedData);
} catch (e) {
console.error(e.message);
}
});
}).on('error', (e) => {
console.error(`错误: ${e.message}`);
});
3. http.ClientRequest
该对象在http.request()内部被创建并返回。它表示着一个正则处理的请求,其请求头已进入队列。请求头仍可使用 setHeader(name, value)、getHeader(name) 和 removeHeader(name) 进行修改。实际的请求头会与第一个数据块一起发送或当调用request.end()时发送。
- connect事件
const http = require('http');
const net = require('net');
const url = require('url');
// 创建一个 HTTP 代理服务器
const proxy = http.createServer((req, res) => {
res.writeHead(200, { 'Content-Type': 'text/plain' });
res.end('okay');
});
proxy.on('connect', (req, cltSocket, head) => {
// 连接到一个服务器
const srvUrl = url.parse(`http://${req.url}`);
const srvSocket = net.connect(srvUrl.port, srvUrl.hostname, () => {
cltSocket.write('HTTP/1.1 200 Connection Established
' +
'Proxy-agent: Node.js-Proxy
' +
'
');
srvSocket.write(head);
srvSocket.pipe(cltSocket);
cltSocket.pipe(srvSocket);
});
});
// 代理服务器正在运行
proxy.listen(1337, '127.0.0.1', () => {
// 发送一个请求到代理服务器
const options = {
port: 1337,
hostname: '127.0.0.1',
method: 'CONNECT',
path: 'www.google.com:80'
};
const req = http.request(options);
req.end();
req.on('connect', (res, socket, head) => {
console.log('已连接!');
// 通过代理服务器发送一个请求
socket.write('GET / HTTP/1.1
' +
'Host: www.google.com:80
' +
'Connection: close
' +
'
');
socket.on('data', (chunk) => {
console.log(chunk.toString());
});
socket.on('end', () => {
proxy.close();
});
});
});
4. http.Agent
负责为HTTP客户端管理连接的持续与复用。它为一个给定的主机和端口维护着一个等待请求的队列,且为每个请求重复使用一个单一的socket连接直到队列为空,此时socket会呗销毁到一个连接池中等待被有着相同主机与端口的请求再次使用。 是否被销毁或被放入连接池取决于 keepAlive 选项。
http.get(options, (res) => {
// 处理事情
}).on('socket', (socket) => {
socket.emit('agentRemove');
});
当 socket 触发 'close' 事件或 'agentRemove' 事件时,它会被移出代理。 当打算长时间保持打开一个 HTTP 请求且不想它留在代理中,则可以如上处理