node.js 程序
by Abhinav Pandey
通过Abhinav Pandey
In this post, we will dive deep inside Node.js fundamentals by creating a Node.js web app without any external packages. We will cover core concepts like streams, events, exceptions, HTTP etc.
在本文中,我们将通过创建一个没有任何外部包的Node.js Web应用程序来深入探究Node.js的基础知识 。 我们将涵盖流,事件,异常,HTTP等核心概念。
Currently, whenever we say we are going to implement a service in Node.js, most of the time we are going to use Express or other 3rd party libraries to implement our functionality. And I am not going to say there is any harm in doing that. These libraries provide necessary abstraction over redundant concepts which make us efficient.
当前,每当我们说要在Node.js中实现服务时,大多数时候我们将使用Express或其他第三方库来实现我们的功能。 而且我不会说这样做有任何危害。 这些库提供了使我们高效的冗余概念的必要抽象。
But with greater abstraction, the low-level logic of your program is hidden from you. As a result, we’re not able to develop a clear picture of how our business logic interacts with Node.js.
但是有了更大的抽象,您的程序的底层逻辑就被隐藏了。 结果,我们无法清楚地了解我们的业务逻辑如何与Node.js交互。
But as Ryan Dahl, the creator of Node.js said:
但是正如Node.js的创建者Ryan Dahl所说:
You can never understand everything. But, you should push yourself to understand the system.
您永远无法理解一切。 但是,您应该推动自己去理解系统。
We will push ourselves to form this clear picture in entirety.
我们将全力以赴形成这张清晰的图画。
So, let’s build a raw HTTP Node.js application with no Framework, no NPM, and no Package.json.
因此,让我们构建一个没有框架,没有NPM,也没有Package.json的原始HTTP Node.js应用程序。
We will build an app that will:
我们将构建一个应用程序,它将:
Import necessary modules
导入必要的模块
Create a Server instance
创建一个服务器实例
Attach listeners to the request
event of the server object
将侦听器附加到服务器对象的request
事件
Parse request body and headers
解析请求正文和标头
Sending Response to the client.
向客户端发送响应 。
Handle error events at request and response streams.
处理请求和响应流中的错误事件 。
But, here is the catch ;)
但是,这是要抓住的地方;)
We will do all of it from scratch with just
我们将仅从头开始做所有这一切
a terminal, and
一个终端,以及
an editor.
编辑。
Yes!! We will use nobody else’s framework, nobody else’s libraries just raw JavaScript and core Node.js runtime.
是!! 我们将使用其他人的框架 , 其他人的库仅使用原始JavaScript和核心Node.js运行时 。
Let’s Begin!
让我们开始!
Before creating an HTTP server, let’s clear up the necessary concept of an HTTP module in Node.js.
在创建HTTP服务器之前,让我们在Node.js中清除HTTP模块的必要概念。
What is HTTP?
什么是HTTP?
http
in Node.js is an inbuilt module that allows client-server communication via HTTP protocol. This module provides an interface to create either an HTTP client or HTTP server that can communicate with other HTTP servers or clients.
Node.js中的http
是一个内置模块,允许通过HTTP协议进行客户端-服务器通信。 该模块提供了一个接口,以创建可以与其他HTTP服务器或客户端进行通信的HTTP客户端或HTTP服务器。
And to make this communication space efficient, an http
module provides streaming of data using stream interface. And since stream passes data in chunks, that means Node.js never buffers the entire request or response at once in the memory. We will come to streams soon.
而为了让这个通信节省空间,一个http
模块提供的流 数据使用流接口。 而且由于流将数据分块传递,这意味着Node.js永远不会在内存中一次缓冲整个请求或响应。 我们将很快来到流 。
So for our app, we will use this http
interface to create an HTTP server that will listen to a particular port and give data back to the user.
因此,对于我们的应用程序,我们将使用此http
接口创建一个HTTP服务器,该服务器将侦听特定端口并将数据返回给用户。
To use either the http
server or client you must require http
module.
要使用http
服务器或客户端,您必须使用http
模块。
var http = require(“http”);
Now let us see how the above line actually works:
现在让我们看看上面的行实际上是如何工作的:
For loading an instance of a particular module in our runtime, Node.js provides us a require
variable which is globally accessible. We use that globally defined require
variable and tell Node to load the http
module (by passing 'http'
as the only param to the require
function call).
为了在运行时中加载特定模块的实例,Node.js向我们提供了一个require
变量,该变量可以全局访问。 我们使用全局定义的require
变量,并告诉Node加载http
模块(通过将'http'
作为对require
函数调用的唯一参数)。
There is a list of other globally available Node.js objects that you can check out in node REPL( by pressing
您可以在节点REPL(通过按两次
But the 2 most important for our use are:
但是,对于我们而言,最重要的两个是:
The require module
需求模块
The module (in-depth explanation in the next article)
该模块 (下一篇文章中的深入说明)
(We do not need to require(‘require’)
or require (‘module’)
as they are global ).
(我们不需要require('require')
或require ('module')
因为它们是global)。
How does require
work?
require
如何工作?
At runtime when Node.js invokes a require
call (require(‘./path/to/fileName’), it searches for a file with a name the same as what’s provided in the only parameter to the require function call.
在运行时,当Node.js调用require
调用(require('./ path / to / fileName')时,它将搜索名称与require函数调用唯一参数中提供的名称相同的文件。
And once the file name matches, Node.js checks for 3 types of extensions:
一旦文件名匹配,Node.js就会检查3种扩展名:
.js
— Node.js looks for “fileName.js” at the specified path to load as js script.
.js
— Node.js在指定路径下查找“ fileName.js”,以作为js脚本加载。
.json
— If Node.js finds “filename.json” file at the specified path it loads a file with the name corresponding to the value of the ‘main’ key in the JSON file.
.json
—如果Node.js在指定路径下找到“ filename.json”文件,它将加载一个名称与JSON文件中“ main”键值相对应的文件。
.node
— Node.js loads binary addons with name fileName.node at the specified path.
.node
— Node.js在指定路径加载名称为fileName.node的二进制插件。
Now that we have included the http
module, we need to create an HTTP web server object. This can be done by using the createServer
method on the http
module.
现在我们已经包含了http
模块,我们需要创建一个HTTP Web服务器对象。 这可以通过使用http
模块上的createServer
方法来完成。
To createServer
method, we pass a callback function which is called every time a request is received on the server.
对于createServer
方法,我们传递一个回调函数,该函数每次在服务器上收到请求时都会调用。
This createServer
method returns a server object that we store in the variable app
. This server object is an event emitter.
此createServer
方法返回一个服务器对象,该对象存储在变量app
。 该服务器对象是事件发射器。
Okay wait, what is an event emitter
?
好吧,什么是event emitter
?
Let’s look a bit into the named event
and emitter
objects.
让我们看一下命名event
和emitter
对象。
Much of the Node.js core APIs are built around an event-driven architecture. Certain kinds of objects (called “emitters”) can cause some Function (“listeners”) to be called by emitting any “named” events.
许多Node.js核心API都是基于事件驱动的体系结构构建的。 某些类型的对象(称为“发射器”)可以通过发出任何“命名”事件来导致某些功能(“侦听器”)被调用。
Let us see an example to get the hang of it.
让我们看一个例子来说明问题。
Output : Called namedEvent in myEventObject’s attached listner
输出: Called namedEvent in myEventObject's attached listner
Explanation
说明
In the above example, we saw the namedEvent
has a listener (a function) attached to it. By attached, we mean the listener is called after it hears the named event. So the listener prints the output on the console screen when the emitters object emits namedEvent
.
在上面的示例中,我们看到namedEvent
了一个侦听器(一个函数)。 所谓附加,是指侦听器在听到命名事件之后被调用。 因此,当发射器对象发出namedEvent
时,侦听器在控制台屏幕上打印输出。
Apart from attaching the listeners, the eventEmitter
object provides many other properties and functions such as
除了附加侦听器外, eventEmitter
对象还提供许多其他属性和功能,例如
You can refer to the Node.js official docs for more detailed information about events in Node.js.
您可以参考Node.js官方文档,以获取有关Node.js中事件的更多详细信息。
Moving back to our example…
回到我们的例子……
Our web server object is also like all other emitter objects implementing event emitter interfaces. It also emits different kinds of named events.
我们的Web服务器对象也与实现事件发射器接口的所有其他发射器对象一样。 它还发出不同种类的命名事件。
Some of them are the following:
其中一些如下:
connect
— raised for all the ‘connect’ request by the HTTP client.
connect
—由HTTP客户端针对所有“连接”请求引发。
connection
— Emitted when a new TCP stream is established. Provide access to the socket established.
connection
-建立新的TCP流时发出。 提供对建立的套接字的访问。
request
— Emitted for Each request from the client (We would listen here).
request
—针对客户端的每个请求发出的请求(我们将在此处侦听)。
upgrade
— emitted each time a client requests an upgrade of the protocol (can be HTTP version).
upgrade
-每次客户端请求协议upgrade
发出的消息(可以是HTTP版本)。
You can get the complete list of events emitted by our web server from the official Node.js docs.
您可以从官方的Node.js文档中获取我们的Web服务器发出的事件的完整列表。
Now since our server needs to listen to the incoming request, we will listen to the request
event of our HTTP server.
现在,由于我们的服务器需要侦听传入的请求,因此我们将侦听HTTP服务器的request
事件。
Code Sample:
代码样例:
In the 3rd line, a listener function is attached to listen to all the request
events on our server object.
在第三行中,附加了一个侦听器功能,以侦听服务器对象上的所有request
事件。
The request
event provides the listener function with 2 parameters which are:
request
事件为侦听器功能提供2个参数,它们是:
request — an instance of http.incomingMessage object and
request — http.incomingMessage对象的实例,以及
response — an instance of http.ServerResponse object.
response — http.ServerResponse对象的实例。
These request
and response
objects have properties and methods that they inherit from the http.incomingMessage
and http.ServerResponse
classes, respectively.
这些request
和response
对象具有分别从http.incomingMessage
和http.ServerResponse
类继承的属性和方法。
Now that we have access to request
and response
object…
现在我们可以访问request
和response
对象了……
The first few things that you might want to know about incoming requests are the URL, method, and Headers. Node.js makes it very easy by attaching them as properties to the request
object (passed as the first parameter for the listener of request
event).
您可能想了解传入请求的前几件事是URL,方法和Headers 。 通过将它们作为属性附加到request
对象(作为request
事件的侦听器的第一个参数传递),Node.js使其变得非常容易。
You can de-structure the request object to get them out like this:
您可以对请求对象进行解构以将其释放,如下所示:
const {headers, url, method } = request;
const {headers, url, method } = request;
headers
passed in request are present as an independent object inside the request
object (secret : they are all in lower-case).
headers
在请求中传递的存在形式与内一个独立的对象request
对象(秘密:它们都在小写)。
After looking at the http
method, in case of a PUT or POST request, we are interested in looking at the data
sent in the request body.
查看http
方法后,如果有PUT或POST请求,我们有兴趣查看请求正文中发送的data
。
But to take the data out of the request body we need to know a few key points about the request object.
但是要将数据带出请求主体,我们需要了解有关请求对象的一些关键点。
Request Object — a readable stream
请求对象-可读流
The request
object that’s passed into the handler also implements the readable stream interface. This means that our request
object is a stream that can be listened to or piped elsewhere to grab the data that is flowing into it. We will also grab the data right out of the request
stream by listening to the stream’s data
and end
events.
传递到处理程序中的request
对象还实现了可读流接口。 这意味着我们的request
对象是一个流,可以在其他地方监听或通过管道传输以获取流入其中的数据。 我们还将通过侦听request
流的data
和end
事件,从request
流中直接获取数据。
Different kinds of data may be passed to our server, but to keep it simple we will be passing only the string in the body.
可能会将不同类型的数据传递到我们的服务器,但是为了简单起见,我们将仅传递正文中的字符串。
To use that data we need to parse it, so we will be using the data
and end
event of the readable stream which is implemented by our request
object as mentioned earlier.
要使用该数据,我们需要对其进行解析 ,因此我们将使用由我们的request
对象实现的可读流的data
和end
事件,如前所述。
On each data
event, the readable stream passes data as a buffer chunk. We will be appending all the chunks in an empty array. And at the end
event we will concat and stringify the array to get the cumulative body.
在每个data
事件中, 可读流将数据作为缓冲区块传递。 我们将所有块附加到一个空数组中。 并在end
时,我们将Concat的和字符串化阵列以获得累积体。
So here is the code up until now:
所以这是到目前为止的代码:
After collecting data from the HTTP request we need to give an appropriate response to the client. But since the request
object implements a readable stream only, we need a writable stream where we can write out our response.
从HTTP请求中收集数据后,我们需要对客户端进行适当的响应。 但是由于request
对象仅实现可读流,因此我们需要可写流,在其中可以写出响应。
Response Object — a writable stream
响应对象-可写流
For doing so, Node.js provide us with a 2nd parameter that is the response
object to the request
event listener.
为此,Node.js为我们提供了第二个参数,它是request
事件侦听器的response
对象。
By using the response
object, we can set HTTP status code, set headers, and write content in the write stream of the response object.
通过使用response
对象,我们可以设置HTTP状态代码,设置标头并在响应对象的写入流中写入内容。
Although if you do not set the response code explicitly, then Node.js itself sets it to 200. But as complexity increases you will want to set the desired statusCode
of the HTTP response.
尽管如果您未明确设置响应代码,则Node.js本身会将其设置为200。但是随着复杂度的增加,您将需要设置HTTP响应的所需statusCode
。
Implicit headers setting
隐式标题设置
You can set, get and remove headers to the response using setHeader(name, value)
, getHeader(name)
, and removeHeader(name)
API.
您可以使用setHeader(name, value)
, getHeader(name)
和removeHeader(name)
API 设置,获取和删除响应的标头。
Code Sample:
代码样例:
When using the above setHeader()
method for setting headers, we are depending on Node.js to implicitly set the response headers before sending the response body.
当使用上述setHeader()
方法设置标头时,我们依赖于Node.js在发送响应主体之前隐式设置响应标头。
To set headers and status code explicitly, we have a response.writeHead()
method.
为了显式 设置标题和状态代码,我们有一个response.writeHead()
方法。
Code Sample:
代码样例:
While explicitly setting headers we should keep in mind that headers come before the body in the HTTP response. That is, we should prefer using the writeHead()
method before writing anything to the response body.
在显式设置标头时,我们应记住, 标头位于HTTP响应的主体之前 。 也就是说,在将任何内容写入响应主体之前,我们应该首选使用writeHead()
方法。
Now let us see how we can write data to a response.
现在让我们看看如何将数据写入响应。
Since the response object is a writable stream object, we just need to use write stream methods to write data chunks into the HTTP response object.
由于响应对象是可写的流对象,因此我们只需要使用写流方法将数据块写入HTTP响应对象。
Code Sample:
代码样例:
After writing to the response stream, we need to close the stream so that Node.js gets to know that it’s time to send the response back to the client.
写入响应流之后,我们需要关闭该流,以便Node.js知道是时候将该响应发送回客户端了。
.end()
method allows us to close the HTTP connection that was set up at the time of the request hitting our server. The end()
method also accepts a last string to be written before closing the connection.
.end()
方法允许我们关闭在请求到达服务器时建立的HTTP 连接 。 end()
方法还接受在关闭连接之前要写入的最后一个字符串。
If we do not use the end method, Node.js will write data to the write stream and wait…
如果不使用end方法,Node.js会将数据写入写流并等待…
…until the default timeout in the server object expires. That is, for any request, Node.js only waits for a fixed time (which is specified in the server object) before closing the connection. And once the connection is closed (either manually using end()
or the timeout expires), Node frees up all the allocated resources immediately.
…直到服务器对象中的默认超时 到期。 也就是说,对于任何请求, Node.js仅 在关闭连接之前 等待固定时间 (在服务器对象中指定)。 一旦关闭连接(手动使用end()
或超时到期), Node就会立即释放所有分配的资源 。
You can set or change the timeout using server.setTimeout([msecs][, callback])
.
您可以使用server.setTimeout([msecs][, callback])
设置或更改超时。
To disable the timeout, you can set the timeout value to 0. But as the timeout is assigned at the time of forming a new connection, the timeout will only be updated for upcoming new connections.
要禁用超时,可以将超时值设置为0。但是由于在建立新连接时分配了超时 ,所以只会为即将到来的新连接更新超时 。
Now that we have written our response, our server should work fine.
现在我们已经写了响应,我们的服务器应该可以正常工作了。
We need to hear the error
events of request
and response
streams. An error
event is raised every time an exception occurs. You can try to avoid it but they do come and we have to catch and handle them properly.
我们需要听到request
和response
流的error
事件。 每次发生异常时都会引发一个error
事件。 您可以尝试避免它,但是它们确实来了,我们必须抓住并正确处理它们。
But how?
但是如何?
We will handle them by attaching error handlers to the error
events of request
and response
streams.
我们将通过将错误处理程序附加到request
和response
流的error
事件来处理它们。
Explanation
说明
Here we are catching all the error
events of request
and response
streams and just logging them into the console. You can also use util
instead of the console
in the production environment (although in production it’s advised to inspect errors properly).
在这里,我们捕获了request
和response
流的所有error
事件,并将它们记录到控制台中。 您还可以在生产环境中使用util
而不是console
(尽管建议在生产环境中正确检查错误)。
Now let us have a look at the code sample we have up til now.
现在,让我们看一看直到现在为止的代码示例。
Okay so our server is capable of the following things at this point:
好的,此时我们的服务器可以执行以下操作:
Import necessary modules
导入必要的模块
create a Server instance
创建一个服务器实例
Attach listeners to the request
event of server object
将侦听器附加到服务器对象的request
事件
Parse request body and headers
解析请求正文和标头
Write the response to response Stream
将响应写入响应流
Handle error events at request and response streams.
处理请求和响应流中的错误事件 。
By now we have made our server object capable of taking head on to new connections but we have not told it where to listen for new connections. That is, this server object also needs to be bound to a particular port so that our server can have access to all the incoming requests at that port.
到现在为止,我们已经使服务器对象能够进行新连接,但是我们还没有告诉它在哪里监听新连接。 也就是说,该服务器对象还需要绑定到特定端口,以便我们的服务器可以访问该端口上的所有传入请求。
To do so we will use the .listen
method of our HTTP server object, .listen(PORT , CB).
为此,我们将使用HTTP服务器对象.listen(PORT , CB).
的.listen
方法.listen(PORT , CB).
@params PORT is the port number where we want our server to listen.
@params PORT是我们希望服务器监听的端口号。
@params Callback is called once the server starts listening.
服务器开始侦听后,将调用@params回调。
Code Sample:
代码样例:
By now our server is ready to receive requests.
至此,我们的服务器已准备就绪,可以接收请求。
Let us run our Node.js app:
让我们运行Node.js应用程序:
node app.js
And hit our server with the following curl on a terminal:
并在终端上通过以下卷曲命中我们的服务器:
curl -d “Hello World” -H “Content-Type: text” -X POST http://localhost:8008
WooHoo!! Congratulations, You have created a Node.js app without any external packages.
呜呜! 恭喜,您已经创建了一个没有任何外部程序包的Node.js应用程序。
It is wonderfully applaudable that you stayed this long.
你们住了这么长时间真是值得称赞。
If you are willing to learn more about the Node.js core like this, then let me know by bursting the claps counts to 50.
如果您愿意像这样学习有关Node.js核心的更多信息,请通过将鼓掌数增加到50来告诉我。
In the next articles, we will continue building over this basic app and add other critical features of routing, middleware, error handling etc. Get notified by following me here on Medium.
在接下来的文章中,我们将继续构建此基本应用程序,并添加路由,中间件,错误处理等其他关键功能。
I have tried to make this article as complete as possible. If you have any ideas that could make it better, please mention in your valuable comments.
我试图使本文尽可能完整。 如果您有任何可以改善的想法,请在有价值的评论中提及。
You can connect me via gmail or tweet me here.
您可以通过gmail与我联系,或者在这里向我发送推文。
Thank you so much for your love! Pardon my mistakes, you have been a wonderful audience.
非常感谢您的爱! 请原谅我的错误,您真是一个很棒的听众。
翻译自: https://www.freecodecamp.org/news/a-no-frills-guide-to-node-js-how-to-create-a-node-js-web-app-without-external-packages-a7b480b966d2/
node.js 程序