This article focuses on the main process of generating services and processing requests through HTTP, but does not cover other functions.
Using the example
const http = require('http');
http.createServer((req, res) = > {
res.end('hello word');
}).listen(8080);
Copy the code
In this example, from generating the service, to receiving the request, and finally to responding to the request, there are four major parts of the work, respectively:
- call
http.createServer
To generate a service - call
listen
Function listening port - Receive the request and generate
req
andres
object - Execute the business function, execute
res.end
Respond to the request
HTTP. CreateServer and listen
// lib/http.js
function createServer(opts, requestListener) {
return new Server(opts, requestListener);
}
// lib/_http_server.js
function Server(options, requestListener) {
if (typeof options === 'function') {
requestListener = options;
options = {};
}
// ...
if (requestListener) {
// When both the REq and RES objects are generated, the Request event is raised and the business function processes the request
this.on('request', requestListener);
}
// The Connection event can be seen in the NET Server class and is triggered when the three-way handshake is complete
this.on('connection', connectionListener);
}
ObjectSetPrototypeOf(Server.prototype, net.Server.prototype);
ObjectSetPrototypeOf(Server, net.Server);
function connectionListener(socket) {
/ / here is to perform connectionListenerInternal function is introduced to this and the socket parameters
defaultTriggerAsyncIdScope(
getOrSetAsyncId(socket), connectionListenerInternal, this, socket
);
}
// The callback function after the connection event is triggered. This function is explained in the section "Parsing to generate REq and RES objects"
function connectionListenerInternal(server, socket) {
// ...
}
Copy the code
When the http.createServer function is called, an instance of a Server is returned, which is inherited from the NET Server class. Therefore, HTTP Server instances also have the ability to listen for port generation services and communicate with clients. The listen function called in the previous example is actually listen in NET Server.
During the instance of the Server object, the Request and Connection events are listened for, respectively.
connection
: This is listening tonet
In theconnection
Event, which is triggered by the server when the client initiates a request and the TCP three-way handshake succeedsconnection
Events.connection
Event callback functionconnectionListenerInternal
We’ll talk about that in the next section.request
: whenreq
andres
Once the objects have all been initialized successfully, they are publishedrequest
Event, as we saw in the previous coderequest
Event callback functionrequestListener
Is that the developer callshttp.createServer
Is passed to the callback function, which receives itreq
andres
Two objects.
Generate req and RES objects
When the client TCP request is successfully connected to the server, the server will trigger the Connection event, and then an HTTP-Parser is instantiated to parse the client request. When the client data is successfully parsed, a REQ object is generated. Next, let’s look at the reQ object generation process.
// lib/_http_server.js
function Server(options, requestListener) {
// ...
// The connection event is triggered when the client and server complete the three-way handshake
this.on('connection', connectionListener);
}
function connectionListener(socket) {
/ / here is to perform connectionListenerInternal function is introduced to this and the socket parameters
defaultTriggerAsyncIdScope(
getOrSetAsyncId(socket), connectionListenerInternal, this, socket
);
}
/ * * *@param {http Server} server
* @param {net Socket} socket* /
function connectionListenerInternal(server, socket) {
// ...
// The parsers.alloc function is executed using an HTTPParser object that returns a free list allocation
const parser = parsers.alloc();
// Request parser initialization work
parser.initialize(
HTTPParser.REQUEST,
new HTTPServerAsyncResource('HTTPINCOMINGMESSAGE', socket),
server.maxHeaderSize || 0,
server.insecureHTTPParser === undefined ?
isLenient() : server.insecureHTTPParser,
server.headersTimeout || 0,); parser.socket = socket; socket.parser = parser;// ...
}
// lib/_http_common.js
const parsers = new FreeList('parsers'.1000.function parsersCb() {
// The http-parser library is used as the request parser
const parser = new HTTPParser();
cleanParser(parser);
// ...
return parser;
});
Copy the code
The HTTP Server uses an HTTP-Parser instance as the parser for client requests. Note that the Free List data structure is used to allocate the Parser objects.
// lib/internal/freelist.js
class FreeList {
constructor(name, max, ctor) {
this.name = name;
this.ctor = ctor;
this.max = max;
this.list = [];
}
// Assign an object
alloc() {
return this.list.length > 0 ?
this.list.pop() :
// cTOR is the method passed in to instantiate FreeList
ReflectApply(this.ctor, this.arguments);
}
// Release the object when it runs out
free(obj) {
if (this.list.length < this.max) {
this.list.push(obj);
return true;
}
return false; }}Copy the code
This section applies to the free List data structure. This data structure is used to reduce the performance cost of object creation and destruction by maintaining a queue of fixed length in which all objects are of the same size. When an object needs to be used, the system preferentially obtains free objects from the queue. If no object is available in the queue, the system creates an object with the same size as the object stored in the queue for the program to use. After an object is used, it is not destroyed directly. Instead, it is pushed into a queue until it is later pushed out for use.
With the Free List in mind, let’s move on to client request parsing.
// lib/_http_common.js
const parsers = new FreeList('parsers'.1000.function parsersCb() {
const parser = new HTTPParser();
cleanParser(parser);
// Bind callback functions to these events
parser[kOnHeaders] = parserOnHeaders;
parser[kOnHeadersComplete] = parserOnHeadersComplete;
parser[kOnBody] = parserOnBody;
parser[kOnMessageComplete] = parserOnMessageComplete;
return parser;
});
Copy the code
Http-parser parses client requests based on events:
kOnHeaders
: Continuously parses the request headerskOnHeadersComplete
: Request header parsing is completekOnBody
: Constantly parses the request bodykOnMessageComplete
: Request body parsing is complete
During data transmission, TCP unpacks the data that exceeds the remaining space of the buffer. As a result, the same request packet may be sent to the server for multiple times. KOnHeaders and kOnBody are used to concatenate the split data, combining the same request.
When the request header is resolved, the kOnHeadersComplete callback is executed, where the REQ object is generated.
// lib/_http_common.js
const { IncomingMessage } = require('_http_incoming');
// The callback function executed after the request header is parsed
function parserOnHeadersComplete(versionMajor, versionMinor, headers, method, url, statusCode, statusMessage, upgrade, shouldKeepAlive) {
const parser = this;
const { socket } = parser;
// ...
// In most cases socket.server[kIncomingMessage] equals IncomingMessage
const ParserIncomingMessage = (socket && socket.server && socket.server[kIncomingMessage]) || IncomingMessage;
const incoming = parser.incoming = new ParserIncomingMessage(socket);
// ...
return parser.onIncoming(incoming, shouldKeepAlive);
}
// lib/_http_incoming.js
function IncomingMessage(socket) {
// ...
}
Copy the code
The IncomingMessage object instantiated in the kOnHeadersComplete callback is the REQ object. The callback ends by executing the parser.onIncoming function to generate the RES object.
// lib/_http_server.js
function connectionListenerInternal(server, socket) {
// ...
// This is the last function executed by the kOnHeadersComplete callback
parser.onIncoming = FunctionPrototypeBind(parserOnIncoming, undefined, server, socket, state);
// ...
}
Parser. onIncoming(incoming, shouldKeepAlive); // The fourth argument is the req object. The req object is the incoming object passed in when parser.onIncoming(incoming, shouldKeepAlive)
function parserOnIncoming(server, socket, state, req, keepAlive) {
// ...
ArrayPrototypePush(state.incoming, req);
// Instance the res object
const res = new server[kServerResponse](req);
if (socket._httpMessage) {
ArrayPrototypePush(state.outgoing, res);
}
// ...
// This event is raised when res.end is called
res.on('finish', FunctionPrototypeBind(resOnFinish, undefined, req, res, socket, state, server));
// ...
server.emit('request', req, res); // Issue the Request event and execute the createServer function to call the incoming business handler
// ...
}
// The ServerResponse here inherits from the OutgoingMessage class, which will be covered later
this[kServerResponse] = options.ServerResponse || ServerResponse;
Copy the code
When both the REQ and RES objects are initialized and stored, the createServer function is executed to call the incoming business handler function. When reQ is generated, the side executes parserOnIncoming to generate the RES object, and registers the Finish event in the RES object. This event is triggered when the business code executes res.end. When both the REQ and RES objects are ready, a Request event is issued and both the REQ and RES objects are passed in. The callback function for the Request event is the one passed in when the business code calls http.createserver.
Res. The end
const http = require('http');
http.createServer((req, res) = > {
res.end('hello word');
}).listen(8080);
Copy the code
When the business process is complete, the res.end() function is actively called in the business code in response to the client request. Let’s see.
// lib/_http_server.js
function ServerResponse(req) {
FunctionPrototypeCall(OutgoingMessage, this);
// ...
}
ObjectSetPrototypeOf(ServerResponse.prototype, OutgoingMessage.prototype);
ObjectSetPrototypeOf(ServerResponse, OutgoingMessage);
Copy the code
The ServerResponse class inherits from the OutgoingMessage class. The res.end method used in business is also defined in OutgoingMessage. Let’s look at the OutgoingMessage class implementation.
// lib/_http_outgoing.js
function OutgoingMessage() {
// ...
this._header = null;
// ...
}
OutgoingMessage.prototype.end = function end(chunk, encoding, callback) {
/ /...
if (chunk) {
// ...
write_(this, chunk, encoding, null.true);
}
// Subscribe to the Finish event, which is the callback passed in when res.end is called
if (typeof callback === 'function')
this.once('finish', callback);
// ...
// Write the response data to the content of the response request using write_, and then execute the _send binding finish. When the data response is complete, the finish function is triggered
const finish = FunctionPrototypeBind(onFinish, undefined.this);
this._send(' '.'latin1', finish);
}
function write_(msg, chunk, encoding, callback, fromEnd) {
// ...
len = Buffer.byteLength(chunk, encoding);
// ...
if(! msg._header) {if(fromEnd) { msg._contentLength = len; }}/ /...
_header is null. The _implicitHeader function is overridden in lib/_http_server.js. The _implicitHeader execution will assign a header+CRLF value to msg._header
if(! msg._header) { msg._implicitHeader(); }// ...
ret = msg._send(chunk, encoding, callback);
// ...
}
OutgoingMessage.prototype._send = function _send(data, encoding, callback) {
if (!this._headerSent) {
if (typeof data === 'string' &&
(encoding === 'utf8' || encoding === 'latin1'| |! encoding)) {The _implicitHeader function is generated as the _header assignment response header +CRLF, so the final value of data here is the response header +CRLF+ response body
data = this._header + data;
} else {
const header = this._header;
ArrayPrototypeUnshift(this.outputData, {
data: header,
encoding: 'latin1'.callback: null
});
}
this._headerSent = true;
}
return this._writeRaw(data, encoding, callback);
};
OutgoingMessage.prototype._writeRaw = _writeRaw;
function _writeRaw(data, encoding, callback) {
const conn = this.socket;
// ...
if (conn && conn._httpMessage === this && conn.writable) {
// ...
// The contents of the response are added to the response buffer and written out back to the user. When written out, the callback function is executed
return conn.write(data, encoding, callback);
}
// ...
}
Copy the code
Res.end, when executed, has two main flows:
- call
write_
Function, which first generates the response header and then stores the response header to_header
, then regenerate the response content, and write the response content (response header +CRLF+ response body) to the user through socket. - call
res._send
tosocket.write
writesfinish
The callback function is executed when the server response is fully written outfinish
The function,finish
The function is published internallyfinish
Events. There are two listening points in the programfinish
Events:parserOnIncoming
Generated in a functionres
Object on which to listenfinish
Events;res.end
Subscribed once in the functionfinish
Event, where the callback function is primarily a business code callres.end
Is passed to the callback function.
// Response header content processing
// lib/_http_server.js
ServerResponse.prototype._implicitHeader = function _implicitHeader() {
this.writeHead(this.statusCode);
};
ServerResponse.prototype.writeHead = writeHead;
function writeHead(statusCode, reason, obj) {
// ...
this._storeHeader(statusLine, headers);
// ...
}
// lib/_http_outgoing.js
OutgoingMessage.prototype._storeHeader = _storeHeader;
function _storeHeader(firstLine, headers) {
// ...
this._last = true;
// ...
this._header = header + CRLF;
this._headerSent = false;
// ...
}
Copy the code
The _implicitHeader execution stores the response header +CRLF content in res._header. The response header has been processed and will be returned to the client with the response body when socket.write is required to respond to the request.
// lib/_http_server.js
function parserOnIncoming(server, socket, state, req, keepAlive) {
// Notice that the finish event in the RES object is also subscribed
res.on('finish',
FunctionPrototypeBind(resOnFinish, undefined,
req, res, socket, state, server));
}
function resOnFinish(req, res, socket, state, server) {
// Clear the req object stored in state
ArrayPrototypeShift(state.incoming);
clearRequestTimeout(req);
clearIncoming(req);
/ / close the res
process.nextTick(emitCloseNT, res);
// Close the socket connection
if (res._last) {
if (typeof socket.destroySoon === 'function') {
socket.destroySoon();
} else {
socket.end(); // The socket is disconnected}}}function emitCloseNT(self) {
self.destroyed = true;
self._closed = true;
self.emit('close');
}
Copy the code
When the Finish event is triggered, the program first removes the buffered REq and RES objects and then closes the socket connection, and the client request is processed.