Node.js指南(HTTP事务的剖析)
http事务的剖析
本指南的目的是让你充分了解node.js http处理的过程,我们假设你在一般意义上知道http请求的工作方式,无论语言或环境如何,我们还假设你对node.js eventemitters和streams有点熟悉,如果你对它们不太熟悉,那么值得快速每个api文档。
创建服务器
任何节点web服务器应用程序在某些时候都必须创建web服务器对象,这是通过使用createserver完成的。
const http = require('http');
const server = http.createserver((request, response) => {
// magic happens here!
});
传递给createserver的函数对于针对该服务器发出的每个http请求都会调用一次,因此它被称为请求处理程序,实际上,createserver返回的server对象是一个eventemitter,我们这里只是创建server对象的简写,然后稍后添加监听器。
const server = http.createserver();
server.on('request', (request, response) => {
// the same kind of magic happens here!
});
当http请求命中服务器时,node使用一些方便的对象调用请求处理函数来处理事务、request和response,我们很快就会讲到。
为了实际处理请求,需要在server对象上调用listen方法,在大多数情况下,你需要传递给listen的是你希望服务器监听的端口号,还有一些其他选项,请参阅api参考。
方法、url和headers
处理请求时,你可能要做的第一件事就是查看方法和url,以便采取适当的措施,node通过将方便的属性放在request对象上来使这相对轻松。
const { method, url } = request;
注意:request对象是incomingmessage的一个实例。
这里的method将始终是普通的http方法/动作,url是没有服务器、协议或端口的完整url,对于典型的url,这意味着包括第三个正斜杠后的所有内容。
headers也不远,它们在自己的request对象中,被称为headers。
const { headers } = request;
const useragent = headers['user-agent'];
这里需要注意的是,无论客户端实际发送它们的方式如何,所有headers都仅以小写字母表示,这简化了为任何目的解析headers的任务。
如果重复某些headers,则它们的值将被覆盖或以逗号分隔的字符串连接在一起,具体取决于header,在某些情况下,这可能会有问题,因此rawheaders也可用。
请求体
收到post或put请求时,请求体可能对你的应用程序很重要,获取body数据比访问请求headers更复杂一点,传递给处理程序的request对象实现了readablestream接口,就像任何其他流一样,可以在其他地方监听或传输此流,我们可以通过监听流的'data'和'end'事件来直接从流中获取数据。
每个'data'事件中发出的块是一个buffer,如果你知道它将是字符串数据,那么最好的方法是在数组中收集数据,然后在'end',连接并对其进行字符串化。
let body = [];
request.on('data', (chunk) => {
body.push(chunk);
}).on('end', () => {
body = buffer.concat(body).tostring();
// at this point, `body` has the entire request body stored in it as a string
});
注意:这看起来有点单调乏味,而且在很多情况下确实如此,幸运的是,在npm上有像concat-stream和body这样的模块可以帮助隐藏一些逻辑,在走这条路之前,要很好地了解正在发生的事情,这就是为什么你在这里!
关于错误的简单介绍
由于request对象是一个readablestream,它也是一个eventemitter,发生错误时的行为与此类似。
request流中的错误通过在流上发出'error'事件来呈现,如果你没有该事件的侦听器,则会抛出错误,这可能会导致node.js程序崩溃。因此,你应该在请求流上添加'error'侦听器,即使你只是记录它并继续前进(虽然最好发送某种http错误响应,稍后会详细介绍)。
request.on('error', (err) => {
// this prints the error message and stack trace to `stderr`.
console.error(err.stack);
});
还有其他方法可以处理这些错误,例如其他抽象和工具,但始终要注意错误可能并且确实会发生,并且你将不得不处理它们。
到目前为止我们已经得到了什么
此时,我们已经介绍了如何创建服务器,并从请求中获取方法、url、headers和body,当我们将它们放在一起时,它可能看起来像这样:
const http = require('http');
http.createserver((request, response) => {
const { headers, method, url } = request;
let body = [];
request.on('error', (err) => {
console.error(err);
}).on('data', (chunk) => {
body.push(chunk);
}).on('end', () => {
body = buffer.concat(body).tostring();
// at this point, we have the headers, method, url and body, and can now
// do whatever we need to in order to respond to this request.
});
}).listen(8080); // activates this server, listening on port 8080.
如果我们运行此示例,我们将能够接收请求,但不会响应它们,实际上,如果你在web中请求此示例,则你的请求将超时,因为没有任何内容被发送回客户端。
到目前为止,我们还没有涉及响应对象,它是serverresponse的一个实例,它是一个writablestream,它包含许多用于将数据发送回客户端的有用方法,接下来我们将介绍。
http状态码
如果不设置它,响应中的http状态码始终为200,当然,并非每个http响应都保证这一点,并且在某些时候你肯定希望发送不同的状态码,为此,你可以设置statuscode属性。
response.statuscode = 404; // tell the client that the resource wasn't found.
还有其他一些快捷方式,我们很快就会看到。
设置响应headers
headers是通过一个名为setheader的方便方法设置的。
response.setheader('content-type', 'application/json');
response.setheader('x-powered-by', 'bacon');
在响应上设置headers时,大小写对其名称不敏感,如果重复设置标题,则设置的最后一个值是发送的值。
显式发送header数据
我们已经讨论过的设置headers和状态码的方法假设你正在使用“隐式headers”,这意味着在开始发送body数据之前,你需要依赖node在正确的时间为你发送headers。
如果需要,可以将headers显式写入响应流,为此,有一个名为writehead的方法,它将状态码和headers写入流。
response.writehead(200, {
'content-type': 'application/json',
'x-powered-by': 'bacon'
});
一旦设置了headers(隐式或显式),你就可以开始发送响应数据了。
发送响应体
由于response对象是writablestream,因此将响应体写入客户端只需使用常用的流方法即可。
response.write('
'); response.write(''); response.write('
hello, world!
'); response.write(''); response.write(''); response.end();
流上的end函数也可以接收一些可选数据作为流上的最后一位数据发送,因此我们可以如下简化上面的示例。
response.end('
hello, world!
');
注意:在开始向body写入数据块之前设置状态和headers很重要,这是有道理的,因为headers在http响应中位于body之前。
关于错误的另一件事
response流也可以发出'error'事件,在某些时候你也必须处理它,所有关于request流错误的建议仍然适用于此处。
把它放在一起
现在我们已经了解了如何进行http响应,让我们把它们放在一起,在前面的示例的基础上,我们将创建一个服务器,用于发回用户发送给我们的所有数据,我们将使用json.stringify将该数据格式化为json。
const http = require('http');
http.createserver((request, response) => {
const { headers, method, url } = request;
let body = [];
request.on('error', (err) => {
console.error(err);
}).on('data', (chunk) => {
body.push(chunk);
}).on('end', () => {
body = buffer.concat(body).tostring();
// beginning of new stuff
response.on('error', (err) => {
console.error(err);
});
response.statuscode = 200;
response.setheader('content-type', 'application/json');
// note: the 2 lines above could be replaced with this next one:
// response.writehead(200, {'content-type': 'application/json'})
const responsebody = { headers, method, url, body };
response.write(json.stringify(responsebody));
response.end();
// note: the 2 lines above could be replaced with this next one:
// response.end(json.stringify(responsebody))
// end of new stuff
});
}).listen(8080);
echo服务器示例
让我们简化前面的示例来进行一个简单的echo服务器,它只是在响应中发送请求中收到的任何数据,我们需要做的就是从请求流中获取数据并将该数据写入响应流,类似于我们之前所做的。
const http = require('http');
http.createserver((request, response) => {
let body = [];
request.on('data', (chunk) => {
body.push(chunk);
}).on('end', () => {
body = buffer.concat(body).tostring();
response.end(body);
});
}).listen(8080);
现在让我们调整一下,我们只想在以下条件下发送echo:
请求方法是post。 url是/echo。
在任何其他情况下,我们只想响应404。
const http = require('http');
http.createserver((request, response) => {
if (request.method === 'post' && request.url === '/echo') {
let body = [];
request.on('data', (chunk) => {
body.push(chunk);
}).on('end', () => {
body = buffer.concat(body).tostring();
response.end(body);
});
} else {
response.statuscode = 404;
response.end();
}
}).listen(8080);
注意:通过这种方式检查url,我们正在做一种“路由”的形式,其他形式的路由可以像switch语句一样简单,也可以像express这样的整个框架一样复杂,如果你正在寻找可以进行路由的东西,请尝试使用router。
现在让我们来简化一下吧,请记住,request对象是readablestream,response对象是writablestream,这意味着我们可以使用pipe将数据从一个引导到另一个,这正是我们想要的echo服务器!
const http = require('http');
http.createserver((request, response) => {
if (request.method === 'post' && request.url === '/echo') {
request.pipe(response);
} else {
response.statuscode = 404;
response.end();
}
}).listen(8080);
我们还没有完成,正如本指南中多次提到的,错误可以而且确实会发生,我们需要处理它们。
为了处理请求流上的错误,我们将错误记录到stderr并发送400状态码以指示bad request,但是,在实际应用程序中,我们需要检查错误以确定正确的状态码和消息是什么,与通常的错误一样,你应该查阅错误文档。
在响应中,我们只是将错误记录到stderr。
const http = require('http');
http.createserver((request, response) => {
request.on('error', (err) => {
console.error(err);
response.statuscode = 400;
response.end();
});
response.on('error', (err) => {
console.error(err);
});
if (request.method === 'post' && request.url === '/echo') {
request.pipe(response);
} else {
response.statuscode = 404;
response.end();
}
}).listen(8080);
我们现在已经介绍了处理http请求的大部分基础知识,此时,你应该能够:
使用请求处理程序函数实例化http服务器,并让它侦听端口。 从request对象中获取headers、url、方法和body数据。 根据request对象中的url和/或其他数据做出路由决策。 通过response对象发送headers、http状态码和body数据。 从request对象和response对象管道数据。 处理request和response流中的流错误。
从这些基础知识中,可以构建用于许多典型用例的node.js http服务器,这些api提供了许多其他功能,因此请务必阅读有关eventemitters、streams和http的api文档。