0
0
mirror of https://github.com/nodejs/node.git synced 2024-12-01 16:10:02 +01:00
nodejs/lib/_http_common.js
Brian White 918159e0f6
http: optimize checkIsHttpToken()
This commit both makes checkIsHttpToken() inlinable and extracts
the character checking logic to a separate inlinable function so that
the main loop can be unrolled a bit.

PR-URL: https://github.com/nodejs/node/pull/6570
Reviewed-By: James M Snell <jasnell@gmail.com>
Reviewed-By: Ben Noordhuis <info@bnoordhuis.nl>
Reviewed-By: Сковорода Никита Андреевич <chalkerx@gmail.com>
2016-06-14 15:13:00 -04:00

315 lines
8.6 KiB
JavaScript

'use strict';
const binding = process.binding('http_parser');
const methods = binding.methods;
const HTTPParser = binding.HTTPParser;
const FreeList = require('internal/freelist').FreeList;
const incoming = require('_http_incoming');
const IncomingMessage = incoming.IncomingMessage;
const readStart = incoming.readStart;
const readStop = incoming.readStop;
const debug = require('util').debuglog('http');
exports.debug = debug;
exports.CRLF = '\r\n';
exports.chunkExpression = /chunk/i;
exports.continueExpression = /100-continue/i;
exports.methods = methods;
const kOnHeaders = HTTPParser.kOnHeaders | 0;
const kOnHeadersComplete = HTTPParser.kOnHeadersComplete | 0;
const kOnBody = HTTPParser.kOnBody | 0;
const kOnMessageComplete = HTTPParser.kOnMessageComplete | 0;
const kOnExecute = HTTPParser.kOnExecute | 0;
// Only called in the slow case where slow means
// that the request headers were either fragmented
// across multiple TCP packets or too large to be
// processed in a single run. This method is also
// called to process trailing HTTP headers.
function parserOnHeaders(headers, url) {
// Once we exceeded headers limit - stop collecting them
if (this.maxHeaderPairs <= 0 ||
this._headers.length < this.maxHeaderPairs) {
this._headers = this._headers.concat(headers);
}
this._url += url;
}
// `headers` and `url` are set only if .onHeaders() has not been called for
// this request.
// `url` is not set for response parsers but that's not applicable here since
// all our parsers are request parsers.
function parserOnHeadersComplete(versionMajor, versionMinor, headers, method,
url, statusCode, statusMessage, upgrade,
shouldKeepAlive) {
var parser = this;
if (!headers) {
headers = parser._headers;
parser._headers = [];
}
if (!url) {
url = parser._url;
parser._url = '';
}
parser.incoming = new IncomingMessage(parser.socket);
parser.incoming.httpVersionMajor = versionMajor;
parser.incoming.httpVersionMinor = versionMinor;
parser.incoming.httpVersion = versionMajor + '.' + versionMinor;
parser.incoming.url = url;
var n = headers.length;
// If parser.maxHeaderPairs <= 0 assume that there's no limit.
if (parser.maxHeaderPairs > 0)
n = Math.min(n, parser.maxHeaderPairs);
parser.incoming._addHeaderLines(headers, n);
if (typeof method === 'number') {
// server only
parser.incoming.method = methods[method];
} else {
// client only
parser.incoming.statusCode = statusCode;
parser.incoming.statusMessage = statusMessage;
}
// The client made non-upgrade request, and server is just advertising
// supported protocols.
//
// See RFC7230 Section 6.7
//
// NOTE: RegExp below matches `upgrade` in `Connection: abc, upgrade, def`
// header.
if (upgrade &&
parser.outgoing !== null &&
(parser.outgoing._headers.upgrade === undefined ||
!/(^|\W)upgrade(\W|$)/i.test(parser.outgoing._headers.connection))) {
upgrade = false;
}
parser.incoming.upgrade = upgrade;
var skipBody = 0; // response to HEAD or CONNECT
if (!upgrade) {
// For upgraded connections and CONNECT method request, we'll emit this
// after parser.execute so that we can capture the first part of the new
// protocol.
skipBody = parser.onIncoming(parser.incoming, shouldKeepAlive);
}
if (typeof skipBody !== 'number')
return skipBody ? 1 : 0;
else
return skipBody;
}
// XXX This is a mess.
// TODO: http.Parser should be a Writable emits request/response events.
function parserOnBody(b, start, len) {
var parser = this;
var stream = parser.incoming;
// if the stream has already been removed, then drop it.
if (!stream)
return;
var socket = stream.socket;
// pretend this was the result of a stream._read call.
if (len > 0 && !stream._dumped) {
var slice = b.slice(start, start + len);
var ret = stream.push(slice);
if (!ret)
readStop(socket);
}
}
function parserOnMessageComplete() {
var parser = this;
var stream = parser.incoming;
if (stream) {
stream.complete = true;
// Emit any trailing headers.
var headers = parser._headers;
if (headers) {
parser.incoming._addHeaderLines(headers, headers.length);
parser._headers = [];
parser._url = '';
}
// For emit end event
stream.push(null);
}
// force to read the next incoming message
readStart(parser.socket);
}
var parsers = new FreeList('parsers', 1000, function() {
var parser = new HTTPParser(HTTPParser.REQUEST);
parser._headers = [];
parser._url = '';
parser._consumed = false;
parser.socket = null;
parser.incoming = null;
parser.outgoing = null;
// Only called in the slow case where slow means
// that the request headers were either fragmented
// across multiple TCP packets or too large to be
// processed in a single run. This method is also
// called to process trailing HTTP headers.
parser[kOnHeaders] = parserOnHeaders;
parser[kOnHeadersComplete] = parserOnHeadersComplete;
parser[kOnBody] = parserOnBody;
parser[kOnMessageComplete] = parserOnMessageComplete;
parser[kOnExecute] = null;
return parser;
});
exports.parsers = parsers;
// Free the parser and also break any links that it
// might have to any other things.
// TODO: All parser data should be attached to a
// single object, so that it can be easily cleaned
// up by doing `parser.data = {}`, which should
// be done in FreeList.free. `parsers.free(parser)`
// should be all that is needed.
function freeParser(parser, req, socket) {
if (parser) {
parser._headers = [];
parser.onIncoming = null;
if (parser._consumed)
parser.unconsume();
parser._consumed = false;
if (parser.socket)
parser.socket.parser = null;
parser.socket = null;
parser.incoming = null;
parser.outgoing = null;
parser[kOnExecute] = null;
if (parsers.free(parser) === false)
parser.close();
parser = null;
}
if (req) {
req.parser = null;
}
if (socket) {
socket.parser = null;
}
}
exports.freeParser = freeParser;
function ondrain() {
if (this._httpMessage) this._httpMessage.emit('drain');
}
function httpSocketSetup(socket) {
socket.removeListener('drain', ondrain);
socket.on('drain', ondrain);
}
exports.httpSocketSetup = httpSocketSetup;
/**
* Verifies that the given val is a valid HTTP token
* per the rules defined in RFC 7230
* See https://tools.ietf.org/html/rfc7230#section-3.2.6
*
* Allowed characters in an HTTP token:
* ^_`a-z 94-122
* A-Z 65-90
* - 45
* 0-9 48-57
* ! 33
* #$%&' 35-39
* *+ 42-43
* . 46
* | 124
* ~ 126
*
* This implementation of checkIsHttpToken() loops over the string instead of
* using a regular expression since the former is up to 180% faster with v8 4.9
* depending on the string length (the shorter the string, the larger the
* performance difference)
*
* Additionally, checkIsHttpToken() is currently designed to be inlinable by v8,
* so take care when making changes to the implementation so that the source
* code size does not exceed v8's default max_inlined_source_size setting.
**/
function isValidTokenChar(ch) {
if (ch >= 94 && ch <= 122)
return true;
if (ch >= 65 && ch <= 90)
return true;
if (ch === 45)
return true;
if (ch >= 48 && ch <= 57)
return true;
if (ch === 34 || ch === 40 || ch === 41 || ch === 44)
return false;
if (ch >= 33 && ch <= 46)
return true;
if (ch === 124 || ch === 126)
return true;
return false;
}
function checkIsHttpToken(val) {
if (typeof val !== 'string' || val.length === 0)
return false;
if (!isValidTokenChar(val.charCodeAt(0)))
return false;
const len = val.length;
if (len > 1) {
if (!isValidTokenChar(val.charCodeAt(1)))
return false;
if (len > 2) {
if (!isValidTokenChar(val.charCodeAt(2)))
return false;
if (len > 3) {
if (!isValidTokenChar(val.charCodeAt(3)))
return false;
for (var i = 4; i < len; i++) {
if (!isValidTokenChar(val.charCodeAt(i)))
return false;
}
}
}
}
return true;
}
exports._checkIsHttpToken = checkIsHttpToken;
/**
* True if val contains an invalid field-vchar
* field-value = *( field-content / obs-fold )
* field-content = field-vchar [ 1*( SP / HTAB ) field-vchar ]
* field-vchar = VCHAR / obs-text
**/
function checkInvalidHeaderChar(val) {
val = '' + val;
for (var i = 0; i < val.length; i++) {
const ch = val.charCodeAt(i);
if (ch === 9) continue;
if (ch <= 31 || ch > 255 || ch === 127) return true;
}
return false;
}
exports._checkInvalidHeaderChar = checkInvalidHeaderChar;