├── .gitignore ├── LICENSE ├── README.md ├── README_zh-CN.md ├── bin └── index.js ├── index.js ├── lib ├── client.js ├── header.js ├── node-http2 │ ├── LICENSE │ ├── lib │ │ ├── http.js │ │ ├── index.js │ │ └── protocol │ │ │ ├── compressor.js │ │ │ ├── connection.js │ │ │ ├── endpoint.js │ │ │ ├── flow.js │ │ │ ├── framer.js │ │ │ ├── index.js │ │ │ └── stream.js │ └── package.json └── server.js ├── package.json └── test.js /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | sftp-config.json 3 | 4 | # Logs 5 | logs 6 | *.log 7 | 8 | # Runtime data 9 | pids 10 | *.pid 11 | *.seed 12 | 13 | # Directory for instrumented libs generated by jscoverage/JSCover 14 | lib-cov 15 | 16 | # Coverage directory used by tools like istanbul 17 | coverage 18 | 19 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 20 | .grunt 21 | 22 | # node-waf configuration 23 | .lock-wscript 24 | 25 | # Compiled binary addons (http://nodejs.org/api/addons.html) 26 | build/Release 27 | 28 | # Dependency directory 29 | # https://www.npmjs.org/doc/misc/npm-faq.html#should-i-check-my-node_modules-folder-into-git 30 | node_modules 31 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | 23 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [中文版](README_zh-CN.md) 2 | 3 | # Introduction 4 | 5 | Pangolin, as it's name, is a simple reverse proxy that creates a secure tunnel from a public endpoint to a locally running web app. 6 | 7 | HTTP/1.1 requests are transmitted to local by HTTP/2 proxy. 8 | 9 | ## Principle 10 | 11 | **Web browser** <-------HTTP/1.1-------> **Public endpoint** <-------HTTP/2-------> **Local server** <-------HTTP/1.1-------> **Local web app** 12 | 13 | We can see that the public or other network's browsers can not access the local network application directly. I open a TCP Server by running in the public network, let the network client to connected by socket, and then based on the connection in the network was created, a HTTP/2 Server can be used to forward each http requests. 14 | 15 | I created both HTTP/2 server and client based on [node-http2](https://github.com/molnarg/node-http2) while I make a little change to make it use a specified socket instance to create server and send requests. 16 | 17 | I use h2c (HTTP2 cleartext), so the transmit data from public to local are sent in the clear, the data format is binary because of HTTP/2. It is also quite easy to add TSL, but I didn't do it for a convenient testing. 18 | 19 | ## Instructions 20 | 21 | Install pangolin on both server and local side. 22 | 23 | ```bash 24 | sudo npm install -g pangolin --verbose 25 | ``` 26 | 27 | ### Command line 28 | 29 | * Server 30 | 31 | ```bash 32 | pangolin server -p 10000 #Start to listen,TCP port 10000 33 | ``` 34 | 35 | * Local 36 | 37 | ```bash 38 | pangolin client -r : -l 39 | or 40 | pangolin client -r : -l : 41 | ``` 42 | 43 | ### Node.js API 44 | 45 | * Server 46 | 47 | ```js 48 | var pangolin = require('pangolin'); 49 | pangolin.createServer({ 50 | port: 10000, //TCP port 51 | httpConnects: 9 //Max http connections 52 | }); 53 | ``` 54 | 55 | * Local 56 | 57 | ```js 58 | var pangolin = require('pangolin'); 59 | pangolin.connect({ 60 | remoteHost : '127.0.0.1', //Server IP address 61 | remotePort : 10000, //Server TCP port 62 | localHost : '127.0.0.1', //Local web app IP address 63 | localPort : 8360, //Local web app port 64 | showAccessLog : false //Display logs or not 65 | }); 66 | ``` 67 | 68 | 69 | -------------------------------------------------------------------------------- /README_zh-CN.md: -------------------------------------------------------------------------------- 1 | # 说明 2 | 3 | pangolin,中文意思是「穿山甲],名字来自于同事的类似项目,在此表示感谢! 4 | 5 | 这是我为了验证一个想法,用几十行 Node.js 代码实现的一个公网到内网的 HTTP/1.1 代理,pangolin 服务端与客户端之间基于 HTTP/2 协议传输。 6 | 7 | 更多介绍:[基于 HTTP/2 的 WEB 内网穿透实现](https://imququ.com/post/tunnel-to-localhost-base-on-http2.html)。 8 | 9 | ## 简单原理 10 | 11 | **浏览器** <-------HTTP/1.1-------> **公网客户端** <-------HTTP/2-------> **内网客户端** <-------HTTP/1.1-------> **内网 WEB 应用** 12 | 13 | 可以看到,公网或其它内网中的浏览器没办法直接访问内网 WEB 应用。我通过运行在公网上的服务端开启一个 TCP Server,让内网客户端去连,再基于这条 socket 连接,在内网创建了一个 HTTP/2 Server 用来转发请求。 14 | 15 | HTTP/2 的 Server 和 Client 直接用的 [node-http2](https://github.com/molnarg/node-http2) 模块。但我做了一些修改,使之可以基于已有 socket 创建 HTTP Server 和发送 HTTP Request。 16 | 17 | 我用了 node-http2 的 h2c(HTTP2 cleartext),所以公网服务端和内网客户端之间的传输是明文,当然由于是 HTTP/2,流量是以二进制 frame 传输的。要加上 TLS 也简单,但现在这样测试更方便。 18 | 19 | ## 使用说明 20 | 21 | 首先在本地和服务器同时安装 pangolin 22 | 23 | ```bash 24 | sudo npm install -g pangolin --verbose 25 | ``` 26 | 27 | ### 命令行 28 | 29 | * 服务器 30 | 31 | ```bash 32 | pangolin server -p 10000 #启动服务,TCP端口为10000 33 | ``` 34 | 35 | * 本地 36 | 37 | ```bash 38 | pangolin client -r 远程HTTP地址或域名:端口号 -l 本地HTTP端口号 39 | 或 40 | pangolin client -r 远程HTTP地址或域名:端口号 -l 本地HTTP地址或域名:端口号 41 | ``` 42 | 43 | ### Node.js API 44 | 45 | ```js 46 | var pangolin = require('pangolin'); 47 | pangolin.connect({ 48 | remoteHost : '127.0.0.1', //Server 端 IP 49 | remotePort : 10000, //Server 端 TCP Server 端口,即上面的 TCP_PORT 50 | localHost : '127.0.0.1', //本地 WEB 应用所在 IP,一般不需要修改 51 | localPort : 8360, //本地 WEB 应用所在端口 52 | showAccessLog : false //是否显示请求日志 53 | }); 54 | ``` 55 | -------------------------------------------------------------------------------- /bin/index.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | var server = require('../lib/server.js'), 4 | client = require('../lib/client'); 5 | 6 | var path = require('path'); 7 | var fs = require('fs'); 8 | 9 | /** 10 | * get version 11 | * @return {String} [] 12 | */ 13 | function getVersion(){ 14 | var filepath = path.resolve(__dirname, '../package.json'); 15 | var version = JSON.parse(fs.readFileSync(filepath)).version; 16 | return version; 17 | } 18 | 19 | function displayVersion(){ 20 | var version = getVersion(); 21 | console.log('\npangolin v' + version + '\n'); 22 | } 23 | 24 | var program = require('commander'); 25 | 26 | program 27 | .usage('[command] ') 28 | .option('-v, --version', 'output the version number', function(){ 29 | displayVersion(); 30 | }) 31 | .option('-V', 'output the version number', function(){ 32 | displayVersion(); 33 | }); 34 | 35 | program 36 | .command('server') 37 | .description('start pangolin server') 38 | .option('-p, --port ', 'port of the tcp connection', parseInt) 39 | .option('-c, --connections ', 'max number of http connections', parseInt) 40 | .option('-t, --timeout ', 'timeout of the TCP connections', parseInt) 41 | .action(function(){ 42 | server({ 43 | port: (0|this.port) || 10000, 44 | httpConnects: (0|this.connections) || 99, 45 | timeout: this.timeout || 7200 46 | }); 47 | }); 48 | 49 | program 50 | .command('client') 51 | .description('start pangolin client') 52 | .option('-r, --remote ', 'the ip address of remote tcp server') 53 | .option('-l, --local ', 'port of the local http server') 54 | .option('-q, --queit', 'ignore access logs') 55 | .action(function(){ 56 | var remoteHost = ['127.0.0.1', 10000]; 57 | 58 | if(this.remote){ 59 | remoteHost = this.remote.split(':'); 60 | } 61 | 62 | var localHost = ['127.0.0.1', 80]; 63 | 64 | if(this.local) { 65 | if(/^\d+$/.test(this.local)) { 66 | localHost[1] = this.local; 67 | } else { 68 | localHost = this.local.split(':'); 69 | } 70 | } 71 | 72 | client({ 73 | remoteHost : remoteHost[0] || '127.0.0.1', 74 | remotePort : remoteHost[1] || 10000, 75 | localHost : localHost[0] || '127.0.0.1', 76 | localPort : localHost[1] || 80, 77 | showAccessLog: !this.queit 78 | }); 79 | }); 80 | 81 | var args = process.argv; 82 | if(args.length <= 2){ 83 | args[2] = '-h'; 84 | } 85 | 86 | program.parse(args); 87 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | createServer: require('./lib/server'), 3 | connect : require('./lib/client') 4 | }; 5 | -------------------------------------------------------------------------------- /lib/client.js: -------------------------------------------------------------------------------- 1 | /*** config start ***/ 2 | //var REMOTE_HOST = '123.59.74.81'; //Server 端 IP 3 | /*** config end ***/ 4 | 5 | var mixin = require('node-mixin'); 6 | 7 | var net = require('net'); 8 | var http = require('http'); 9 | var url = require('url'); 10 | var http2 = require('./node-http2'); 11 | var removeDeprecatedHeaders = require('./header').removeDeprecatedHeaders; 12 | var format = require('util').format; 13 | 14 | var formatDate = function(e) {function t(e){return("0"+e).slice(-2)}var n=e.getFullYear()+"-"+t(e.getMonth()+1)+"-"+t(e.getDate()),r=t(e.getHours())+":"+t(e.getMinutes())+":"+t(e.getSeconds());return n+" "+r}; 15 | 16 | function connect(options){ 17 | var options = mixin(options, { 18 | remoteHost : '127.0.0.1', //Server IP address 19 | remotePort : 10000, //Server TCP port 20 | localHost : '127.0.0.1', //Local web app IP address 21 | localPort : 8360, //Local web app port 22 | showAccessLog : true //Display logs or not 23 | }); 24 | 25 | var client = new net.Socket(); 26 | client.setKeepAlive(true); 27 | 28 | client.connect(options.remotePort, options.remoteHost, function() { 29 | //console.log('Connected to remote server...'); 30 | 31 | var serverOpts = { 32 | plain : true, 33 | createServer : function(start) { 34 | start(client); 35 | return client; 36 | } 37 | }; 38 | 39 | client.write('port?', 'utf-8'); 40 | 41 | client.on('data', function(data){ 42 | data = data.toString('utf-8'); 43 | 44 | if(/^port:/.test(data)){ 45 | console.log('Connected to', options.remoteHost, options.remotePort); 46 | console.log('Proxy pass to', options.localHost, options.localPort); 47 | console.log('Please visit http://' + options.remoteHost + ':' + data.slice(5)); 48 | //client.removeAllListeners('data'); 49 | client.write('start server', 'utf-8'); 50 | http2.raw.createServer(serverOpts, function(req, res) { 51 | var u = url.parse(req.url); 52 | req.headers.host = options.localHost + ':' + options.localPort; 53 | 54 | var httpOpts = { 55 | hostname : options.localHost, 56 | port : options.localPort, 57 | path : u.path, 58 | method : req.method, 59 | headers : req.headers 60 | }; 61 | 62 | if(options.showAccessLog) { 63 | var d = new Date; 64 | var ua = req.headers['user-agent']; 65 | var realIp = req.headers['x-real-ip']; 66 | 67 | console.log(format('[%s] - %s "%s %s" %s', formatDate(d), realIp, req.method, u.path, ua)); 68 | } 69 | 70 | var pReq = http.request(httpOpts, function(pRes) { 71 | var headers = removeDeprecatedHeaders(pRes.headers); 72 | res.writeHead(pRes.statusCode, headers); 73 | pRes.pipe(res); 74 | }).on('error', function(e) { 75 | res.writeHead(200); 76 | res.write('Can not reach the local service!'); 77 | res.end(); 78 | return; 79 | }); 80 | 81 | req.pipe(pReq); 82 | }); 83 | }else if(/^error!/.test(data)){ 84 | console.error(data.slice(6)); 85 | } 86 | }); 87 | }); 88 | 89 | client.on('error', function(e) { 90 | console.error('Can not connect remote server! ' + e.errno); 91 | }); 92 | 93 | client.on('close', function(e) { 94 | console.log('Remote server shutdown!'); 95 | process.exit(0); 96 | }); 97 | } 98 | 99 | module.exports = connect; -------------------------------------------------------------------------------- /lib/header.js: -------------------------------------------------------------------------------- 1 | var deprecatedHeaders = [ 2 | 'connection', 3 | 'host', 4 | 'keep-alive', 5 | 'proxy-connection', 6 | 'te', 7 | 'transfer-encoding', 8 | 'upgrade' 9 | ]; 10 | 11 | function removeDeprecatedHeaders(headers) { 12 | deprecatedHeaders.map(function(name) { 13 | delete headers[name]; 14 | }); 15 | 16 | return headers; 17 | } 18 | 19 | exports.removeDeprecatedHeaders = removeDeprecatedHeaders; -------------------------------------------------------------------------------- /lib/node-http2/LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License 2 | 3 | Copyright (C) 2013 Gábor Molnár , Google Inc 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining 6 | a copy of this software and associated documentation files (the 7 | 'Software'), to deal in the Software without restriction, including 8 | without limitation the rights to use, copy, modify, merge, publish, 9 | distribute, sublicense, and/or sell copies of the Software, and to 10 | permit persons to whom the Software is furnished to do so, subject to 11 | the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be 14 | included in all copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, 17 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 18 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 19 | IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 20 | CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, 21 | TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE 22 | SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 23 | -------------------------------------------------------------------------------- /lib/node-http2/lib/http.js: -------------------------------------------------------------------------------- 1 | // Public API 2 | // ========== 3 | 4 | // The main governing power behind the http2 API design is that it should look very similar to the 5 | // existing node.js [HTTPS API][1] (which is, in turn, almost identical to the [HTTP API][2]). The 6 | // additional features of HTTP/2 are exposed as extensions to this API. Furthermore, node-http2 7 | // should fall back to using HTTP/1.1 if needed. Compatibility with undocumented or deprecated 8 | // elements of the node.js HTTP/HTTPS API is a non-goal. 9 | // 10 | // Additional and modified API elements 11 | // ------------------------------------ 12 | // 13 | // - **Class: http2.Endpoint**: an API for using the raw HTTP/2 framing layer. For documentation 14 | // see [protocol/endpoint.js](protocol/endpoint.html). 15 | // 16 | // - **Class: http2.Server** 17 | // - **Event: 'connection' (socket, [endpoint])**: there's a second argument if the negotiation of 18 | // HTTP/2 was successful: the reference to the [Endpoint](protocol/endpoint.html) object tied to the 19 | // socket. 20 | // 21 | // - **http2.createServer(options, [requestListener])**: additional option: 22 | // - **log**: an optional [bunyan](https://github.com/trentm/node-bunyan) logger object 23 | // 24 | // - **Class: http2.ServerResponse** 25 | // - **response.push(options)**: initiates a server push. `options` describes the 'imaginary' 26 | // request to which the push stream is a response; the possible options are identical to the 27 | // ones accepted by `http2.request`. Returns a ServerResponse object that can be used to send 28 | // the response headers and content. 29 | // 30 | // - **Class: http2.Agent** 31 | // - **new Agent(options)**: additional option: 32 | // - **log**: an optional [bunyan](https://github.com/trentm/node-bunyan) logger object 33 | // - **agent.sockets**: only contains TCP sockets that corresponds to HTTP/1 requests. 34 | // - **agent.endpoints**: contains [Endpoint](protocol/endpoint.html) objects for HTTP/2 connections. 35 | // 36 | // - **http2.request(options, [callback])**: 37 | // - similar to http.request 38 | // 39 | // - **http2.get(options, [callback])**: 40 | // - similar to http.get 41 | // 42 | // - **Class: http2.ClientRequest** 43 | // - **Event: 'socket' (socket)**: in case of an HTTP/2 incoming message, `socket` is a reference 44 | // to the associated [HTTP/2 Stream](protocol/stream.html) object (and not to the TCP socket). 45 | // - **Event: 'push' (promise)**: signals the intention of a server push associated to this 46 | // request. `promise` is an IncomingPromise. If there's no listener for this event, the server 47 | // push is cancelled. 48 | // - **request.setPriority(priority)**: assign a priority to this request. `priority` is a number 49 | // between 0 (highest priority) and 2^31-1 (lowest priority). Default value is 2^30. 50 | // 51 | // - **Class: http2.IncomingMessage** 52 | // - has two subclasses for easier interface description: **IncomingRequest** and 53 | // **IncomingResponse** 54 | // - **message.socket**: in case of an HTTP/2 incoming message, it's a reference to the associated 55 | // [HTTP/2 Stream](protocol/stream.html) object (and not to the TCP socket). 56 | // 57 | // - **Class: http2.IncomingRequest (IncomingMessage)** 58 | // - **message.url**: in case of an HTTP/2 incoming request, the `url` field always contains the 59 | // path, and never a full url (it contains the path in most cases in the HTTPS api as well). 60 | // - **message.scheme**: additional field. Mandatory HTTP/2 request metadata. 61 | // - **message.host**: additional field. Mandatory HTTP/2 request metadata. Note that this 62 | // replaces the old Host header field, but node-http2 will add Host to the `message.headers` for 63 | // backwards compatibility. 64 | // 65 | // - **Class: http2.IncomingPromise (IncomingRequest)** 66 | // - contains the metadata of the 'imaginary' request to which the server push is an answer. 67 | // - **Event: 'response' (response)**: signals the arrival of the actual push stream. `response` 68 | // is an IncomingResponse. 69 | // - **Event: 'push' (promise)**: signals the intention of a server push associated to this 70 | // request. `promise` is an IncomingPromise. If there's no listener for this event, the server 71 | // push is cancelled. 72 | // - **promise.cancel()**: cancels the promised server push. 73 | // - **promise.setPriority(priority)**: assign a priority to this push stream. `priority` is a 74 | // number between 0 (highest priority) and 2^31-1 (lowest priority). Default value is 2^30. 75 | // 76 | // API elements not yet implemented 77 | // -------------------------------- 78 | // 79 | // - **Class: http2.Server** 80 | // - **server.maxHeadersCount** 81 | // 82 | // API elements that are not applicable to HTTP/2 83 | // ---------------------------------------------- 84 | // 85 | // The reason may be deprecation of certain HTTP/1.1 features, or that some API elements simply 86 | // don't make sense when using HTTP/2. These will not be present when a request is done with HTTP/2, 87 | // but will function normally when falling back to using HTTP/1.1. 88 | // 89 | // - **Class: http2.Server** 90 | // - **Event: 'checkContinue'**: not in the spec 91 | // - **Event: 'upgrade'**: upgrade is deprecated in HTTP/2 92 | // - **Event: 'timeout'**: HTTP/2 sockets won't timeout because of application level keepalive 93 | // (PING frames) 94 | // - **Event: 'connect'**: not yet supported 95 | // - **server.setTimeout(msecs, [callback])** 96 | // - **server.timeout** 97 | // 98 | // - **Class: http2.ServerResponse** 99 | // - **Event: 'close'** 100 | // - **Event: 'timeout'** 101 | // - **response.writeContinue()** 102 | // - **response.writeHead(statusCode, [reasonPhrase], [headers])**: reasonPhrase will always be 103 | // ignored since [it's not supported in HTTP/2][3] 104 | // - **response.setTimeout(timeout, [callback])** 105 | // 106 | // - **Class: http2.Agent** 107 | // - **agent.maxSockets**: only affects HTTP/1 connection pool. When using HTTP/2, there's always 108 | // one connection per host. 109 | // 110 | // - **Class: http2.ClientRequest** 111 | // - **Event: 'upgrade'** 112 | // - **Event: 'connect'** 113 | // - **Event: 'continue'** 114 | // - **request.setTimeout(timeout, [callback])** 115 | // - **request.setNoDelay([noDelay])** 116 | // - **request.setSocketKeepAlive([enable], [initialDelay])** 117 | // 118 | // - **Class: http2.IncomingMessage** 119 | // - **Event: 'close'** 120 | // - **message.setTimeout(timeout, [callback])** 121 | // 122 | // [1]: https://nodejs.org/api/https.html 123 | // [2]: https://nodejs.org/api/http.html 124 | // [3]: https://tools.ietf.org/html/rfc7540#section-8.1.2.4 125 | 126 | // Common server and client side code 127 | // ================================== 128 | 129 | var net = require('net'); 130 | var url = require('url'); 131 | var util = require('util'); 132 | var EventEmitter = require('events').EventEmitter; 133 | var PassThrough = require('stream').PassThrough; 134 | var Readable = require('stream').Readable; 135 | var Writable = require('stream').Writable; 136 | var protocol = require('./protocol'); 137 | var Endpoint = protocol.Endpoint; 138 | var http = require('http'); 139 | var https = require('https'); 140 | 141 | exports.STATUS_CODES = http.STATUS_CODES; 142 | exports.IncomingMessage = IncomingMessage; 143 | exports.OutgoingMessage = OutgoingMessage; 144 | exports.protocol = protocol; 145 | 146 | var deprecatedHeaders = [ 147 | 'connection', 148 | 'host', 149 | 'keep-alive', 150 | 'proxy-connection', 151 | 'transfer-encoding', 152 | 'upgrade' 153 | ]; 154 | 155 | // When doing NPN/ALPN negotiation, HTTP/1.1 is used as fallback 156 | var supportedProtocols = [protocol.VERSION, 'http/1.1', 'http/1.0']; 157 | 158 | // Ciphersuite list based on the recommendations of https://wiki.mozilla.org/Security/Server_Side_TLS 159 | // The only modification is that kEDH+AESGCM were placed after DHE and ECDHE suites 160 | var cipherSuites = [ 161 | 'ECDHE-RSA-AES128-GCM-SHA256', 162 | 'ECDHE-ECDSA-AES128-GCM-SHA256', 163 | 'ECDHE-RSA-AES256-GCM-SHA384', 164 | 'ECDHE-ECDSA-AES256-GCM-SHA384', 165 | 'DHE-RSA-AES128-GCM-SHA256', 166 | 'DHE-DSS-AES128-GCM-SHA256', 167 | 'ECDHE-RSA-AES128-SHA256', 168 | 'ECDHE-ECDSA-AES128-SHA256', 169 | 'ECDHE-RSA-AES128-SHA', 170 | 'ECDHE-ECDSA-AES128-SHA', 171 | 'ECDHE-RSA-AES256-SHA384', 172 | 'ECDHE-ECDSA-AES256-SHA384', 173 | 'ECDHE-RSA-AES256-SHA', 174 | 'ECDHE-ECDSA-AES256-SHA', 175 | 'DHE-RSA-AES128-SHA256', 176 | 'DHE-RSA-AES128-SHA', 177 | 'DHE-DSS-AES128-SHA256', 178 | 'DHE-RSA-AES256-SHA256', 179 | 'DHE-DSS-AES256-SHA', 180 | 'DHE-RSA-AES256-SHA', 181 | 'kEDH+AESGCM', 182 | 'AES128-GCM-SHA256', 183 | 'AES256-GCM-SHA384', 184 | 'ECDHE-RSA-RC4-SHA', 185 | 'ECDHE-ECDSA-RC4-SHA', 186 | 'AES128', 187 | 'AES256', 188 | 'RC4-SHA', 189 | 'HIGH', 190 | '!aNULL', 191 | '!eNULL', 192 | '!EXPORT', 193 | '!DES', 194 | '!3DES', 195 | '!MD5', 196 | '!PSK' 197 | ].join(':'); 198 | 199 | // Logging 200 | // ------- 201 | 202 | // Logger shim, used when no logger is provided by the user. 203 | function noop() {} 204 | var defaultLogger = { 205 | fatal: noop, 206 | error: noop, 207 | warn : noop, 208 | info : noop, 209 | debug: noop, 210 | trace: noop, 211 | 212 | child: function() { return this; } 213 | }; 214 | 215 | // Bunyan serializers exported by submodules that are worth adding when creating a logger. 216 | exports.serializers = protocol.serializers; 217 | 218 | // IncomingMessage class 219 | // --------------------- 220 | 221 | function IncomingMessage(stream) { 222 | // * This is basically a read-only wrapper for the [Stream](protocol/stream.html) class. 223 | PassThrough.call(this); 224 | stream.pipe(this); 225 | this.socket = this.stream = stream; 226 | 227 | this._log = stream._log.child({ component: 'http' }); 228 | 229 | // * HTTP/2.0 does not define a way to carry the version identifier that is included in the 230 | // HTTP/1.1 request/status line. Version is always 2.0. 231 | this.httpVersion = '2.0'; 232 | this.httpVersionMajor = 2; 233 | this.httpVersionMinor = 0; 234 | 235 | // * `this.headers` will store the regular headers (and none of the special colon headers) 236 | this.headers = {}; 237 | this.trailers = undefined; 238 | this._lastHeadersSeen = undefined; 239 | 240 | // * Other metadata is filled in when the headers arrive. 241 | stream.once('headers', this._onHeaders.bind(this)); 242 | stream.once('end', this._onEnd.bind(this)); 243 | } 244 | IncomingMessage.prototype = Object.create(PassThrough.prototype, { constructor: { value: IncomingMessage } }); 245 | 246 | // [Request Header Fields](https://tools.ietf.org/html/rfc7540#section-8.1.2.3) 247 | // * `headers` argument: HTTP/2.0 request and response header fields carry information as a series 248 | // of key-value pairs. This includes the target URI for the request, the status code for the 249 | // response, as well as HTTP header fields. 250 | IncomingMessage.prototype._onHeaders = function _onHeaders(headers) { 251 | // * Detects malformed headers 252 | this._validateHeaders(headers); 253 | 254 | // * Store the _regular_ headers in `this.headers` 255 | for (var name in headers) { 256 | if (name[0] !== ':') { 257 | if (name === 'set-cookie' && !Array.isArray(headers[name])) { 258 | this.headers[name] = [headers[name]]; 259 | } else { 260 | this.headers[name] = headers[name]; 261 | } 262 | } 263 | } 264 | 265 | // * The last header block, if it's not the first, will represent the trailers 266 | var self = this; 267 | this.stream.on('headers', function(headers) { 268 | self._lastHeadersSeen = headers; 269 | }); 270 | }; 271 | 272 | IncomingMessage.prototype._onEnd = function _onEnd() { 273 | this.trailers = this._lastHeadersSeen; 274 | }; 275 | 276 | IncomingMessage.prototype.setTimeout = noop; 277 | 278 | IncomingMessage.prototype._checkSpecialHeader = function _checkSpecialHeader(key, value) { 279 | if ((typeof value !== 'string') || (value.length === 0)) { 280 | this._log.error({ key: key, value: value }, 'Invalid or missing special header field'); 281 | this.stream.reset('PROTOCOL_ERROR'); 282 | } 283 | 284 | return value; 285 | }; 286 | 287 | IncomingMessage.prototype._validateHeaders = function _validateHeaders(headers) { 288 | // * An HTTP/2.0 request or response MUST NOT include any of the following header fields: 289 | // Connection, Host, Keep-Alive, Proxy-Connection, Transfer-Encoding, and Upgrade. A server 290 | // MUST treat the presence of any of these header fields as a stream error of type 291 | // PROTOCOL_ERROR. 292 | // If the TE header is present, it's only valid value is 'trailers' 293 | for (var i = 0; i < deprecatedHeaders.length; i++) { 294 | var key = deprecatedHeaders[i]; 295 | if (key in headers || (key === 'te' && headers[key] !== 'trailers')) { 296 | this._log.error({ key: key, value: headers[key] }, 'Deprecated header found'); 297 | this.stream.reset('PROTOCOL_ERROR'); 298 | return; 299 | } 300 | } 301 | 302 | for (var headerName in headers) { 303 | // * Empty header name field is malformed 304 | if (headerName.length <= 1) { 305 | this.stream.reset('PROTOCOL_ERROR'); 306 | return; 307 | } 308 | // * A request or response containing uppercase header name field names MUST be 309 | // treated as malformed (Section 8.1.3.5). Implementations that detect malformed 310 | // requests or responses need to ensure that the stream ends. 311 | if(/[A-Z]/.test(headerName)) { 312 | this.stream.reset('PROTOCOL_ERROR'); 313 | return; 314 | } 315 | } 316 | }; 317 | 318 | // OutgoingMessage class 319 | // --------------------- 320 | 321 | function OutgoingMessage() { 322 | // * This is basically a read-only wrapper for the [Stream](protocol/stream.html) class. 323 | Writable.call(this); 324 | 325 | this._headers = {}; 326 | this._trailers = undefined; 327 | this.headersSent = false; 328 | this.finished = false; 329 | 330 | this.on('finish', this._finish); 331 | } 332 | OutgoingMessage.prototype = Object.create(Writable.prototype, { constructor: { value: OutgoingMessage } }); 333 | 334 | OutgoingMessage.prototype._write = function _write(chunk, encoding, callback) { 335 | if (this.stream) { 336 | this.stream.write(chunk, encoding, callback); 337 | } else { 338 | this.once('socket', this._write.bind(this, chunk, encoding, callback)); 339 | } 340 | }; 341 | 342 | OutgoingMessage.prototype._finish = function _finish() { 343 | if (this.stream) { 344 | if (this._trailers) { 345 | if (this.request) { 346 | this.request.addTrailers(this._trailers); 347 | } else { 348 | this.stream.headers(this._trailers); 349 | } 350 | } 351 | this.finished = true; 352 | this.stream.end(); 353 | } else { 354 | this.once('socket', this._finish.bind(this)); 355 | } 356 | }; 357 | 358 | OutgoingMessage.prototype.setHeader = function setHeader(name, value) { 359 | if (this.headersSent) { 360 | return this.emit('error', new Error('Can\'t set headers after they are sent.')); 361 | } else { 362 | name = name.toLowerCase(); 363 | if (deprecatedHeaders.indexOf(name) !== -1) { 364 | return this.emit('error', new Error('Cannot set deprecated header: ' + name)); 365 | } 366 | this._headers[name] = value; 367 | } 368 | }; 369 | 370 | OutgoingMessage.prototype.removeHeader = function removeHeader(name) { 371 | if (this.headersSent) { 372 | return this.emit('error', new Error('Can\'t remove headers after they are sent.')); 373 | } else { 374 | delete this._headers[name.toLowerCase()]; 375 | } 376 | }; 377 | 378 | OutgoingMessage.prototype.getHeader = function getHeader(name) { 379 | return this._headers[name.toLowerCase()]; 380 | }; 381 | 382 | OutgoingMessage.prototype.addTrailers = function addTrailers(trailers) { 383 | this._trailers = trailers; 384 | }; 385 | 386 | OutgoingMessage.prototype.setTimeout = noop; 387 | 388 | OutgoingMessage.prototype._checkSpecialHeader = IncomingMessage.prototype._checkSpecialHeader; 389 | 390 | // Server side 391 | // =========== 392 | 393 | exports.Server = Server; 394 | exports.IncomingRequest = IncomingRequest; 395 | exports.OutgoingResponse = OutgoingResponse; 396 | exports.ServerResponse = OutgoingResponse; // for API compatibility 397 | 398 | // Forward events `event` on `source` to all listeners on `target`. 399 | // 400 | // Note: The calling context is `source`. 401 | function forwardEvent(event, source, target) { 402 | function forward() { 403 | var listeners = target.listeners(event); 404 | 405 | var n = listeners.length; 406 | 407 | // Special case for `error` event with no listeners. 408 | if (n === 0 && event === 'error') { 409 | var args = [event]; 410 | args.push.apply(args, arguments); 411 | 412 | target.emit.apply(target, args); 413 | return; 414 | } 415 | 416 | for (var i = 0; i < n; ++i) { 417 | listeners[i].apply(source, arguments); 418 | } 419 | } 420 | 421 | source.on(event, forward); 422 | 423 | // A reference to the function is necessary to be able to stop 424 | // forwarding. 425 | return forward; 426 | } 427 | 428 | // Server class 429 | // ------------ 430 | 431 | function Server(options) { 432 | options = util._extend({}, options); 433 | 434 | this._log = (options.log || defaultLogger).child({ component: 'http' }); 435 | this._settings = options.settings; 436 | 437 | var start = this._start.bind(this); 438 | var fallback = this._fallback.bind(this); 439 | 440 | // HTTP2 over TLS (using NPN or ALPN) 441 | if ((options.key && options.cert) || options.pfx) { 442 | this._log.info('Creating HTTP/2 server over TLS'); 443 | this._mode = 'tls'; 444 | options.ALPNProtocols = supportedProtocols; 445 | options.NPNProtocols = supportedProtocols; 446 | options.ciphers = options.ciphers || cipherSuites; 447 | options.honorCipherOrder = (options.honorCipherOrder != false); 448 | this._server = https.createServer(options); 449 | this._originalSocketListeners = this._server.listeners('secureConnection'); 450 | this._server.removeAllListeners('secureConnection'); 451 | this._server.on('secureConnection', function(socket) { 452 | var negotiatedProtocol = socket.alpnProtocol || socket.npnProtocol; 453 | // It's true that the client MUST use SNI, but if it doesn't, we don't care, don't fall back to HTTP/1, 454 | // since if the ALPN negotiation is otherwise successful, the client thinks we speak HTTP/2 but we don't. 455 | if (negotiatedProtocol === protocol.VERSION) { 456 | start(socket); 457 | } else { 458 | fallback(socket); 459 | } 460 | }); 461 | this._server.on('request', this.emit.bind(this, 'request')); 462 | 463 | forwardEvent('error', this._server, this); 464 | forwardEvent('listening', this._server, this); 465 | } 466 | 467 | // HTTP2 over plain TCP 468 | else if (options.plain) { 469 | this._log.info('Creating HTTP/2 server over plain TCP'); 470 | this._mode = 'plain'; 471 | 472 | if(options.createServer) { 473 | this._server = options.createServer(start); 474 | } else { 475 | this._server = net.createServer(start); 476 | } 477 | } 478 | 479 | // HTTP/2 with HTTP/1.1 upgrade 480 | else { 481 | this._log.error('Trying to create HTTP/2 server with Upgrade from HTTP/1.1'); 482 | throw new Error('HTTP1.1 -> HTTP2 upgrade is not yet supported. Please provide TLS keys.'); 483 | } 484 | 485 | this._server.on('close', this.emit.bind(this, 'close')); 486 | } 487 | Server.prototype = Object.create(EventEmitter.prototype, { constructor: { value: Server } }); 488 | 489 | // Starting HTTP/2 490 | Server.prototype._start = function _start(socket) { 491 | var endpoint = new Endpoint(this._log, 'SERVER', this._settings); 492 | 493 | this._log.info({ e: endpoint, 494 | client: socket.remoteAddress + ':' + socket.remotePort, 495 | SNI: socket.servername 496 | }, 'New incoming HTTP/2 connection'); 497 | 498 | endpoint.pipe(socket).pipe(endpoint); 499 | 500 | var self = this; 501 | endpoint.on('stream', function _onStream(stream) { 502 | var response = new OutgoingResponse(stream); 503 | var request = new IncomingRequest(stream); 504 | 505 | // Some conformance to Node.js Https specs allows to distinguish clients: 506 | request.remoteAddress = socket.remoteAddress; 507 | request.remotePort = socket.remotePort; 508 | request.connection = request.socket = response.socket = socket; 509 | 510 | request.once('ready', self.emit.bind(self, 'request', request, response)); 511 | }); 512 | 513 | endpoint.on('error', this.emit.bind(this, 'clientError')); 514 | socket.on('error', this.emit.bind(this, 'clientError')); 515 | 516 | this.emit('connection', socket, endpoint); 517 | }; 518 | 519 | Server.prototype._fallback = function _fallback(socket) { 520 | var negotiatedProtocol = socket.alpnProtocol || socket.npnProtocol; 521 | 522 | this._log.info({ client: socket.remoteAddress + ':' + socket.remotePort, 523 | protocol: negotiatedProtocol, 524 | SNI: socket.servername 525 | }, 'Falling back to simple HTTPS'); 526 | 527 | for (var i = 0; i < this._originalSocketListeners.length; i++) { 528 | this._originalSocketListeners[i].call(this._server, socket); 529 | } 530 | 531 | this.emit('connection', socket); 532 | }; 533 | 534 | // There are [3 possible signatures][1] of the `listen` function. Every arguments is forwarded to 535 | // the backing TCP or HTTPS server. 536 | // [1]: https://nodejs.org/api/http.html#http_server_listen_port_hostname_backlog_callback 537 | Server.prototype.listen = function listen(port, hostname) { 538 | this._log.info({ on: ((typeof hostname === 'string') ? (hostname + ':' + port) : port) }, 539 | 'Listening for incoming connections'); 540 | this._server.listen.apply(this._server, arguments); 541 | 542 | return this._server; 543 | }; 544 | 545 | Server.prototype.close = function close(callback) { 546 | this._log.info('Closing server'); 547 | this._server.close(callback); 548 | }; 549 | 550 | Server.prototype.setTimeout = function setTimeout(timeout, callback) { 551 | if (this._mode === 'tls') { 552 | this._server.setTimeout(timeout, callback); 553 | } 554 | }; 555 | 556 | Object.defineProperty(Server.prototype, 'timeout', { 557 | get: function getTimeout() { 558 | if (this._mode === 'tls') { 559 | return this._server.timeout; 560 | } else { 561 | return undefined; 562 | } 563 | }, 564 | set: function setTimeout(timeout) { 565 | if (this._mode === 'tls') { 566 | this._server.timeout = timeout; 567 | } 568 | } 569 | }); 570 | 571 | // Overriding `EventEmitter`'s `on(event, listener)` method to forward certain subscriptions to 572 | // `server`.There are events on the `http.Server` class where it makes difference whether someone is 573 | // listening on the event or not. In these cases, we can not simply forward the events from the 574 | // `server` to `this` since that means a listener. Instead, we forward the subscriptions. 575 | Server.prototype.on = function on(event, listener) { 576 | if ((event === 'upgrade') || (event === 'timeout')) { 577 | return this._server.on(event, listener && listener.bind(this)); 578 | } else { 579 | return EventEmitter.prototype.on.call(this, event, listener); 580 | } 581 | }; 582 | 583 | // `addContext` is used to add Server Name Indication contexts 584 | Server.prototype.addContext = function addContext(hostname, credentials) { 585 | if (this._mode === 'tls') { 586 | this._server.addContext(hostname, credentials); 587 | } 588 | }; 589 | 590 | Server.prototype.address = function address() { 591 | return this._server.address() 592 | }; 593 | 594 | function createServerRaw(options, requestListener) { 595 | if (typeof options === 'function') { 596 | requestListener = options; 597 | options = {}; 598 | } 599 | 600 | if (options.pfx || (options.key && options.cert)) { 601 | throw new Error('options.pfx, options.key, and options.cert are nonsensical!'); 602 | } 603 | 604 | options.plain = true; 605 | var server = new Server(options); 606 | 607 | if (requestListener) { 608 | server.on('request', requestListener); 609 | } 610 | 611 | return server; 612 | } 613 | 614 | function createServerTLS(options, requestListener) { 615 | if (typeof options === 'function') { 616 | throw new Error('options are required!'); 617 | } 618 | if (!options.pfx && !(options.key && options.cert)) { 619 | throw new Error('options.pfx or options.key and options.cert are required!'); 620 | } 621 | options.plain = false; 622 | 623 | var server = new Server(options); 624 | 625 | if (requestListener) { 626 | server.on('request', requestListener); 627 | } 628 | 629 | return server; 630 | } 631 | 632 | // Exposed main interfaces for HTTPS connections (the default) 633 | exports.https = {}; 634 | exports.createServer = exports.https.createServer = createServerTLS; 635 | exports.request = exports.https.request = requestTLS; 636 | exports.get = exports.https.get = getTLS; 637 | 638 | // Exposed main interfaces for raw TCP connections (not recommended) 639 | exports.raw = {}; 640 | exports.raw.createServer = createServerRaw; 641 | exports.raw.request = requestRaw; 642 | exports.raw.get = getRaw; 643 | 644 | // Exposed main interfaces for HTTP plaintext upgrade connections (not implemented) 645 | function notImplemented() { 646 | throw new Error('HTTP UPGRADE is not implemented!'); 647 | } 648 | 649 | exports.http = {}; 650 | exports.http.createServer = exports.http.request = exports.http.get = notImplemented; 651 | 652 | // IncomingRequest class 653 | // --------------------- 654 | 655 | function IncomingRequest(stream) { 656 | IncomingMessage.call(this, stream); 657 | } 658 | IncomingRequest.prototype = Object.create(IncomingMessage.prototype, { constructor: { value: IncomingRequest } }); 659 | 660 | // [Request Header Fields](https://tools.ietf.org/html/rfc7540#section-8.1.2.3) 661 | // * `headers` argument: HTTP/2.0 request and response header fields carry information as a series 662 | // of key-value pairs. This includes the target URI for the request, the status code for the 663 | // response, as well as HTTP header fields. 664 | IncomingRequest.prototype._onHeaders = function _onHeaders(headers) { 665 | // * The ":method" header field includes the HTTP method 666 | // * The ":scheme" header field includes the scheme portion of the target URI 667 | // * The ":authority" header field includes the authority portion of the target URI 668 | // * The ":path" header field includes the path and query parts of the target URI. 669 | // This field MUST NOT be empty; URIs that do not contain a path component MUST include a value 670 | // of '/', unless the request is an OPTIONS request for '*', in which case the ":path" header 671 | // field MUST include '*'. 672 | // * All HTTP/2.0 requests MUST include exactly one valid value for all of these header fields. A 673 | // server MUST treat the absence of any of these header fields, presence of multiple values, or 674 | // an invalid value as a stream error of type PROTOCOL_ERROR. 675 | this.method = this._checkSpecialHeader(':method' , headers[':method']); 676 | this.scheme = this._checkSpecialHeader(':scheme' , headers[':scheme']); 677 | this.host = this._checkSpecialHeader(':authority', headers[':authority'] ); 678 | this.url = this._checkSpecialHeader(':path' , headers[':path'] ); 679 | if (!this.method || !this.scheme || !this.host || !this.url) { 680 | // This is invalid, and we've sent a RST_STREAM, so don't continue processing 681 | return; 682 | } 683 | 684 | // * Host header is included in the headers object for backwards compatibility. 685 | this.headers.host = this.host; 686 | 687 | // * Handling regular headers. 688 | IncomingMessage.prototype._onHeaders.call(this, headers); 689 | 690 | // * Signaling that the headers arrived. 691 | this._log.info({ method: this.method, scheme: this.scheme, host: this.host, 692 | path: this.url, headers: this.headers }, 'Incoming request'); 693 | this.emit('ready'); 694 | }; 695 | 696 | // OutgoingResponse class 697 | // ---------------------- 698 | 699 | function OutgoingResponse(stream) { 700 | OutgoingMessage.call(this); 701 | 702 | this._log = stream._log.child({ component: 'http' }); 703 | 704 | this.stream = stream; 705 | this.statusCode = 200; 706 | this.sendDate = true; 707 | 708 | this.stream.once('headers', this._onRequestHeaders.bind(this)); 709 | } 710 | OutgoingResponse.prototype = Object.create(OutgoingMessage.prototype, { constructor: { value: OutgoingResponse } }); 711 | 712 | OutgoingResponse.prototype.writeHead = function writeHead(statusCode, reasonPhrase, headers) { 713 | if (this.headersSent) { 714 | return; 715 | } 716 | 717 | if (typeof reasonPhrase === 'string') { 718 | this._log.warn('Reason phrase argument was present but ignored by the writeHead method'); 719 | } else { 720 | headers = reasonPhrase; 721 | } 722 | 723 | for (var name in headers) { 724 | this.setHeader(name, headers[name]); 725 | } 726 | headers = this._headers; 727 | 728 | if (this.sendDate && !('date' in this._headers)) { 729 | headers.date = (new Date()).toUTCString(); 730 | } 731 | 732 | this._log.info({ status: statusCode, headers: this._headers }, 'Sending server response'); 733 | 734 | headers[':status'] = this.statusCode = statusCode; 735 | 736 | this.stream.headers(headers); 737 | this.headersSent = true; 738 | }; 739 | 740 | OutgoingResponse.prototype._implicitHeaders = function _implicitHeaders() { 741 | if (!this.headersSent) { 742 | this.writeHead(this.statusCode); 743 | } 744 | }; 745 | 746 | OutgoingResponse.prototype._implicitHeader = function() { 747 | this._implicitHeaders(); 748 | }; 749 | 750 | OutgoingResponse.prototype.write = function write() { 751 | this._implicitHeaders(); 752 | return OutgoingMessage.prototype.write.apply(this, arguments); 753 | }; 754 | 755 | OutgoingResponse.prototype.end = function end() { 756 | this.finshed = true; 757 | this._implicitHeaders(); 758 | return OutgoingMessage.prototype.end.apply(this, arguments); 759 | }; 760 | 761 | OutgoingResponse.prototype._onRequestHeaders = function _onRequestHeaders(headers) { 762 | this._requestHeaders = headers; 763 | }; 764 | 765 | OutgoingResponse.prototype.push = function push(options) { 766 | if (typeof options === 'string') { 767 | options = url.parse(options); 768 | } 769 | 770 | if (!options.path) { 771 | throw new Error('`path` option is mandatory.'); 772 | } 773 | 774 | var promise = util._extend({ 775 | ':method': (options.method || 'GET').toUpperCase(), 776 | ':scheme': (options.protocol && options.protocol.slice(0, -1)) || this._requestHeaders[':scheme'], 777 | ':authority': options.hostname || options.host || this._requestHeaders[':authority'], 778 | ':path': options.path 779 | }, options.headers); 780 | 781 | this._log.info({ method: promise[':method'], scheme: promise[':scheme'], 782 | authority: promise[':authority'], path: promise[':path'], 783 | headers: options.headers }, 'Promising push stream'); 784 | 785 | var pushStream = this.stream.promise(promise); 786 | 787 | return new OutgoingResponse(pushStream); 788 | }; 789 | 790 | OutgoingResponse.prototype.altsvc = function altsvc(host, port, protocolID, maxAge, origin) { 791 | if (origin === undefined) { 792 | origin = ""; 793 | } 794 | this.stream.altsvc(host, port, protocolID, maxAge, origin); 795 | }; 796 | 797 | // Overriding `EventEmitter`'s `on(event, listener)` method to forward certain subscriptions to 798 | // `request`. See `Server.prototype.on` for explanation. 799 | OutgoingResponse.prototype.on = function on(event, listener) { 800 | if (this.request && (event === 'timeout')) { 801 | this.request.on(event, listener && listener.bind(this)); 802 | } else { 803 | OutgoingMessage.prototype.on.call(this, event, listener); 804 | } 805 | }; 806 | 807 | // Client side 808 | // =========== 809 | 810 | exports.ClientRequest = OutgoingRequest; // for API compatibility 811 | exports.OutgoingRequest = OutgoingRequest; 812 | exports.IncomingResponse = IncomingResponse; 813 | exports.Agent = Agent; 814 | exports.globalAgent = undefined; 815 | 816 | function requestRaw(options, callback) { 817 | if (typeof options === "string") { 818 | options = url.parse(options); 819 | } 820 | options.plain = true; 821 | if (options.protocol && options.protocol !== "http:") { 822 | throw new Error('This interface only supports http-schemed URLs'); 823 | } 824 | if (options.agent && typeof(options.agent.request) === 'function') { 825 | var agentOptions = util._extend({}, options); 826 | delete agentOptions.agent; 827 | return options.agent.request(agentOptions, callback); 828 | } 829 | return exports.globalAgent.request(options, callback); 830 | } 831 | 832 | function requestTLS(options, callback) { 833 | if (typeof options === "string") { 834 | options = url.parse(options); 835 | } 836 | options.plain = false; 837 | if (options.protocol && options.protocol !== "https:") { 838 | throw new Error('This interface only supports https-schemed URLs'); 839 | } 840 | if (options.agent && typeof(options.agent.request) === 'function') { 841 | var agentOptions = util._extend({}, options); 842 | delete agentOptions.agent; 843 | return options.agent.request(agentOptions, callback); 844 | } 845 | return exports.globalAgent.request(options, callback); 846 | } 847 | 848 | function getRaw(options, callback) { 849 | if (typeof options === "string") { 850 | options = url.parse(options); 851 | } 852 | options.plain = true; 853 | if (options.protocol && options.protocol !== "http:") { 854 | throw new Error('This interface only supports http-schemed URLs'); 855 | } 856 | if (options.agent && typeof(options.agent.get) === 'function') { 857 | var agentOptions = util._extend({}, options); 858 | delete agentOptions.agent; 859 | return options.agent.get(agentOptions, callback); 860 | } 861 | return exports.globalAgent.get(options, callback); 862 | } 863 | 864 | function getTLS(options, callback) { 865 | if (typeof options === "string") { 866 | options = url.parse(options); 867 | } 868 | options.plain = false; 869 | if (options.protocol && options.protocol !== "https:") { 870 | throw new Error('This interface only supports https-schemed URLs'); 871 | } 872 | if (options.agent && typeof(options.agent.get) === 'function') { 873 | var agentOptions = util._extend({}, options); 874 | delete agentOptions.agent; 875 | return options.agent.get(agentOptions, callback); 876 | } 877 | return exports.globalAgent.get(options, callback); 878 | } 879 | 880 | // Agent class 881 | // ----------- 882 | 883 | function Agent(options) { 884 | EventEmitter.call(this); 885 | this.setMaxListeners(0); 886 | 887 | options = util._extend({}, options); 888 | 889 | this._settings = options.settings; 890 | this._log = (options.log || defaultLogger).child({ component: 'http' }); 891 | this.endpoints = {}; 892 | 893 | // * Using an own HTTPS agent, because the global agent does not look at `NPN/ALPNProtocols` when 894 | // generating the key identifying the connection, so we may get useless non-negotiated TLS 895 | // channels even if we ask for a negotiated one. This agent will contain only negotiated 896 | // channels. 897 | options.ALPNProtocols = supportedProtocols; 898 | options.NPNProtocols = supportedProtocols; 899 | this._httpsAgent = new https.Agent(options); 900 | 901 | this.sockets = this._httpsAgent.sockets; 902 | this.requests = this._httpsAgent.requests; 903 | } 904 | Agent.prototype = Object.create(EventEmitter.prototype, { constructor: { value: Agent } }); 905 | 906 | Agent.prototype.request = function request(options, callback) { 907 | if (typeof options === 'string') { 908 | options = url.parse(options); 909 | } else { 910 | options = util._extend({}, options); 911 | } 912 | 913 | options.method = (options.method || 'GET').toUpperCase(); 914 | options.protocol = options.protocol || 'https:'; 915 | options.host = options.hostname || options.host || 'localhost'; 916 | options.port = options.port || 443; 917 | options.path = options.path || '/'; 918 | 919 | if (!options.plain && options.protocol === 'http:') { 920 | this._log.error('Trying to negotiate client request with Upgrade from HTTP/1.1'); 921 | this.emit('error', new Error('HTTP1.1 -> HTTP2 upgrade is not yet supported.')); 922 | } 923 | 924 | var request = new OutgoingRequest(this._log); 925 | 926 | if (callback) { 927 | request.on('response', callback); 928 | } 929 | 930 | var key = [ 931 | !!options.plain, 932 | options.host, 933 | options.port, 934 | options.id 935 | ].join(':'); 936 | var self = this; 937 | 938 | // * There's an existing HTTP/2 connection to this host 939 | if (key in this.endpoints) { 940 | var endpoint = this.endpoints[key]; 941 | request._start(endpoint.createStream(), options); 942 | } 943 | 944 | // * HTTP/2 over plain TCP 945 | else if (options.plain) { 946 | endpoint = new Endpoint(this._log, 'CLIENT', this._settings); 947 | 948 | if(options.socket) { 949 | endpoint.socket = options.socket; 950 | } else { 951 | endpoint.socket = net.connect({ 952 | host: options.host, 953 | port: options.port, 954 | localAddress: options.localAddress 955 | }); 956 | } 957 | 958 | endpoint.socket.on('error', function (error) { 959 | self._log.error('Socket error: ' + error.toString()); 960 | request.emit('error', error); 961 | }); 962 | 963 | endpoint.on('error', function(error){ 964 | self._log.error('Connection error: ' + error.toString()); 965 | request.emit('error', error); 966 | }); 967 | 968 | this.endpoints[key] = endpoint; 969 | endpoint.pipe(endpoint.socket).pipe(endpoint); 970 | request._start(endpoint.createStream(), options); 971 | } 972 | 973 | // * HTTP/2 over TLS negotiated using NPN or ALPN, or fallback to HTTPS1 974 | else { 975 | var started = false; 976 | var createAgent = hasAgentOptions(options); 977 | options.ALPNProtocols = supportedProtocols; 978 | options.NPNProtocols = supportedProtocols; 979 | options.servername = options.host; // Server Name Indication 980 | options.ciphers = options.ciphers || cipherSuites; 981 | if (createAgent) { 982 | options.agent = new https.Agent(options); 983 | } else if (options.agent == null) { 984 | options.agent = this._httpsAgent; 985 | } 986 | var httpsRequest = https.request(options); 987 | 988 | httpsRequest.on('error', function (error) { 989 | self._log.error('Socket error: ' + error.toString()); 990 | self.removeAllListeners(key); 991 | request.emit('error', error); 992 | }); 993 | 994 | httpsRequest.on('socket', function(socket) { 995 | var negotiatedProtocol = socket.alpnProtocol || socket.npnProtocol; 996 | if (negotiatedProtocol != null) { // null in >=0.11.0, undefined in <0.11.0 997 | negotiated(); 998 | } else { 999 | socket.on('secureConnect', negotiated); 1000 | } 1001 | }); 1002 | 1003 | function negotiated() { 1004 | var endpoint; 1005 | var negotiatedProtocol = httpsRequest.socket.alpnProtocol || httpsRequest.socket.npnProtocol; 1006 | if (negotiatedProtocol === protocol.VERSION) { 1007 | httpsRequest.socket.emit('agentRemove'); 1008 | unbundleSocket(httpsRequest.socket); 1009 | endpoint = new Endpoint(self._log, 'CLIENT', self._settings); 1010 | endpoint.socket = httpsRequest.socket; 1011 | endpoint.pipe(endpoint.socket).pipe(endpoint); 1012 | } 1013 | if (started) { 1014 | // ** In the meantime, an other connection was made to the same host... 1015 | if (endpoint) { 1016 | // *** and it turned out to be HTTP2 and the request was multiplexed on that one, so we should close this one 1017 | endpoint.close(); 1018 | } 1019 | // *** otherwise, the fallback to HTTPS1 is already done. 1020 | } else { 1021 | if (endpoint) { 1022 | self._log.info({ e: endpoint, server: options.host + ':' + options.port }, 1023 | 'New outgoing HTTP/2 connection'); 1024 | self.endpoints[key] = endpoint; 1025 | self.emit(key, endpoint); 1026 | } else { 1027 | self.emit(key, undefined); 1028 | } 1029 | } 1030 | } 1031 | 1032 | this.once(key, function(endpoint) { 1033 | started = true; 1034 | if (endpoint) { 1035 | request._start(endpoint.createStream(), options); 1036 | } else { 1037 | request._fallback(httpsRequest); 1038 | } 1039 | }); 1040 | } 1041 | 1042 | return request; 1043 | }; 1044 | 1045 | Agent.prototype.get = function get(options, callback) { 1046 | var request = this.request(options, callback); 1047 | request.end(); 1048 | return request; 1049 | }; 1050 | 1051 | Agent.prototype.destroy = function(error) { 1052 | if (this._httpsAgent) { 1053 | this._httpsAgent.destroy(); 1054 | } 1055 | for (var key in this.endpoints) { 1056 | this.endpoints[key].close(error); 1057 | } 1058 | }; 1059 | 1060 | function unbundleSocket(socket) { 1061 | socket.removeAllListeners('data'); 1062 | socket.removeAllListeners('end'); 1063 | socket.removeAllListeners('readable'); 1064 | socket.removeAllListeners('close'); 1065 | socket.removeAllListeners('error'); 1066 | socket.unpipe(); 1067 | delete socket.ondata; 1068 | delete socket.onend; 1069 | } 1070 | 1071 | function hasAgentOptions(options) { 1072 | return options.pfx != null || 1073 | options.key != null || 1074 | options.passphrase != null || 1075 | options.cert != null || 1076 | options.ca != null || 1077 | options.ciphers != null || 1078 | options.rejectUnauthorized != null || 1079 | options.secureProtocol != null; 1080 | } 1081 | 1082 | Object.defineProperty(Agent.prototype, 'maxSockets', { 1083 | get: function getMaxSockets() { 1084 | return this._httpsAgent.maxSockets; 1085 | }, 1086 | set: function setMaxSockets(value) { 1087 | this._httpsAgent.maxSockets = value; 1088 | } 1089 | }); 1090 | 1091 | exports.globalAgent = new Agent(); 1092 | 1093 | // OutgoingRequest class 1094 | // --------------------- 1095 | 1096 | function OutgoingRequest() { 1097 | OutgoingMessage.call(this); 1098 | 1099 | this._log = undefined; 1100 | 1101 | this.stream = undefined; 1102 | } 1103 | OutgoingRequest.prototype = Object.create(OutgoingMessage.prototype, { constructor: { value: OutgoingRequest } }); 1104 | 1105 | OutgoingRequest.prototype._start = function _start(stream, options) { 1106 | this.stream = stream; 1107 | this.options = options; 1108 | 1109 | this._log = stream._log.child({ component: 'http' }); 1110 | 1111 | for (var key in options.headers) { 1112 | this.setHeader(key, options.headers[key]); 1113 | } 1114 | var headers = this._headers; 1115 | delete headers.host; 1116 | 1117 | if (options.auth) { 1118 | headers.authorization = 'Basic ' + new Buffer(options.auth).toString('base64'); 1119 | } 1120 | 1121 | headers[':scheme'] = options.protocol.slice(0, -1); 1122 | headers[':method'] = options.method; 1123 | headers[':authority'] = options.host; 1124 | headers[':path'] = options.path; 1125 | 1126 | this._log.info({ scheme: headers[':scheme'], method: headers[':method'], 1127 | authority: headers[':authority'], path: headers[':path'], 1128 | headers: (options.headers || {}) }, 'Sending request'); 1129 | this.stream.headers(headers); 1130 | this.headersSent = true; 1131 | 1132 | this.emit('socket', this.stream); 1133 | var response = new IncomingResponse(this.stream); 1134 | response.req = this; 1135 | response.once('ready', this.emit.bind(this, 'response', response)); 1136 | 1137 | this.stream.on('promise', this._onPromise.bind(this)); 1138 | }; 1139 | 1140 | OutgoingRequest.prototype._fallback = function _fallback(request) { 1141 | request.on('response', this.emit.bind(this, 'response')); 1142 | this.stream = this.request = request; 1143 | this.emit('socket', this.socket); 1144 | }; 1145 | 1146 | OutgoingRequest.prototype.setPriority = function setPriority(priority) { 1147 | if (this.stream) { 1148 | this.stream.priority(priority); 1149 | } else { 1150 | this.once('socket', this.setPriority.bind(this, priority)); 1151 | } 1152 | }; 1153 | 1154 | // Overriding `EventEmitter`'s `on(event, listener)` method to forward certain subscriptions to 1155 | // `request`. See `Server.prototype.on` for explanation. 1156 | OutgoingRequest.prototype.on = function on(event, listener) { 1157 | if (this.request && (event === 'upgrade')) { 1158 | this.request.on(event, listener && listener.bind(this)); 1159 | } else { 1160 | OutgoingMessage.prototype.on.call(this, event, listener); 1161 | } 1162 | }; 1163 | 1164 | // Methods only in fallback mode 1165 | OutgoingRequest.prototype.setNoDelay = function setNoDelay(noDelay) { 1166 | if (this.request) { 1167 | this.request.setNoDelay(noDelay); 1168 | } else if (!this.stream) { 1169 | this.on('socket', this.setNoDelay.bind(this, noDelay)); 1170 | } 1171 | }; 1172 | 1173 | OutgoingRequest.prototype.setSocketKeepAlive = function setSocketKeepAlive(enable, initialDelay) { 1174 | if (this.request) { 1175 | this.request.setSocketKeepAlive(enable, initialDelay); 1176 | } else if (!this.stream) { 1177 | this.on('socket', this.setSocketKeepAlive.bind(this, enable, initialDelay)); 1178 | } 1179 | }; 1180 | 1181 | OutgoingRequest.prototype.setTimeout = function setTimeout(timeout, callback) { 1182 | if (this.request) { 1183 | this.request.setTimeout(timeout, callback); 1184 | } else if (!this.stream) { 1185 | this.on('socket', this.setTimeout.bind(this, timeout, callback)); 1186 | } 1187 | }; 1188 | 1189 | // Aborting the request 1190 | OutgoingRequest.prototype.abort = function abort() { 1191 | if (this.request) { 1192 | this.request.abort(); 1193 | } else if (this.stream) { 1194 | this.stream.reset('CANCEL'); 1195 | } else { 1196 | this.on('socket', this.abort.bind(this)); 1197 | } 1198 | }; 1199 | 1200 | // Receiving push promises 1201 | OutgoingRequest.prototype._onPromise = function _onPromise(stream, headers) { 1202 | this._log.info({ push_stream: stream.id }, 'Receiving push promise'); 1203 | 1204 | var promise = new IncomingPromise(stream, headers); 1205 | 1206 | if (this.listeners('push').length > 0) { 1207 | this.emit('push', promise); 1208 | } else { 1209 | promise.cancel(); 1210 | } 1211 | }; 1212 | 1213 | // IncomingResponse class 1214 | // ---------------------- 1215 | 1216 | function IncomingResponse(stream) { 1217 | IncomingMessage.call(this, stream); 1218 | } 1219 | IncomingResponse.prototype = Object.create(IncomingMessage.prototype, { constructor: { value: IncomingResponse } }); 1220 | 1221 | // [Response Header Fields](https://tools.ietf.org/html/rfc7540#section-8.1.2.4) 1222 | // * `headers` argument: HTTP/2.0 request and response header fields carry information as a series 1223 | // of key-value pairs. This includes the target URI for the request, the status code for the 1224 | // response, as well as HTTP header fields. 1225 | IncomingResponse.prototype._onHeaders = function _onHeaders(headers) { 1226 | // * A single ":status" header field is defined that carries the HTTP status code field. This 1227 | // header field MUST be included in all responses. 1228 | // * A client MUST treat the absence of the ":status" header field, the presence of multiple 1229 | // values, or an invalid value as a stream error of type PROTOCOL_ERROR. 1230 | // Note: currently, we do not enforce it strictly: we accept any format, and parse it as int 1231 | // * HTTP/2.0 does not define a way to carry the reason phrase that is included in an HTTP/1.1 1232 | // status line. 1233 | this.statusCode = parseInt(this._checkSpecialHeader(':status', headers[':status'])); 1234 | 1235 | // * Handling regular headers. 1236 | IncomingMessage.prototype._onHeaders.call(this, headers); 1237 | 1238 | // * Signaling that the headers arrived. 1239 | this._log.info({ status: this.statusCode, headers: this.headers}, 'Incoming response'); 1240 | this.emit('ready'); 1241 | }; 1242 | 1243 | // IncomingPromise class 1244 | // ------------------------- 1245 | 1246 | function IncomingPromise(responseStream, promiseHeaders) { 1247 | var stream = new Readable(); 1248 | stream._read = noop; 1249 | stream.push(null); 1250 | stream._log = responseStream._log; 1251 | 1252 | IncomingRequest.call(this, stream); 1253 | 1254 | this._onHeaders(promiseHeaders); 1255 | 1256 | this._responseStream = responseStream; 1257 | 1258 | var response = new IncomingResponse(this._responseStream); 1259 | response.once('ready', this.emit.bind(this, 'response', response)); 1260 | 1261 | this.stream.on('promise', this._onPromise.bind(this)); 1262 | } 1263 | IncomingPromise.prototype = Object.create(IncomingRequest.prototype, { constructor: { value: IncomingPromise } }); 1264 | 1265 | IncomingPromise.prototype.cancel = function cancel() { 1266 | this._responseStream.reset('CANCEL'); 1267 | }; 1268 | 1269 | IncomingPromise.prototype.setPriority = function setPriority(priority) { 1270 | this._responseStream.priority(priority); 1271 | }; 1272 | 1273 | IncomingPromise.prototype._onPromise = OutgoingRequest.prototype._onPromise; 1274 | -------------------------------------------------------------------------------- /lib/node-http2/lib/index.js: -------------------------------------------------------------------------------- 1 | // [node-http2][homepage] is an [HTTP/2][http2] implementation for [node.js][node]. 2 | // 3 | // The core of the protocol is implemented in the protocol sub-directory. This directory provides 4 | // two important features on top of the protocol: 5 | // 6 | // * Implementation of different negotiation schemes that can be used to start a HTTP2 connection. 7 | // These include TLS ALPN, Upgrade and Plain TCP. 8 | // 9 | // * Providing an API very similar to the standard node.js [HTTPS module API][node-https] 10 | // (which is in turn very similar to the [HTTP module API][node-http]). 11 | // 12 | // [homepage]: https://github.com/molnarg/node-http2 13 | // [http2]: https://tools.ietf.org/html/rfc7540 14 | // [node]: https://nodejs.org/ 15 | // [node-https]: https://nodejs.org/api/https.html 16 | // [node-http]: https://nodejs.org/api/http.html 17 | 18 | module.exports = require('./http'); 19 | 20 | /* 21 | HTTP API 22 | 23 | | ^ 24 | | | 25 | +-------------|------------|------------------------------------------------------+ 26 | | | | Server/Agent | 27 | | v | | 28 | | +----------+ +----------+ | 29 | | | Outgoing | | Incoming | | 30 | | | req/res. | | req/res. | | 31 | | +----------+ +----------+ | 32 | | | ^ | 33 | | | | | 34 | | +---------|------------|-------------------------------------+ +----- | 35 | | | | | Endpoint | | | 36 | | | | | | | | 37 | | | v | | | | 38 | | | +-----------------------+ +-------------------- | | | 39 | | | | Stream | | Stream ... | | | 40 | | | +-----------------------+ +-------------------- | | | 41 | | | | | | 42 | | +------------------------------------------------------------+ +----- | 43 | | | | | 44 | | | | | 45 | | v | | 46 | | +------------------------------------------------------------+ +----- | 47 | | | TCP stream | | ... | 48 | | +------------------------------------------------------------+ +----- | 49 | | | 50 | +---------------------------------------------------------------------------------+ 51 | 52 | */ 53 | -------------------------------------------------------------------------------- /lib/node-http2/lib/protocol/connection.js: -------------------------------------------------------------------------------- 1 | var assert = require('assert'); 2 | 3 | // The Connection class 4 | // ==================== 5 | 6 | // The Connection class manages HTTP/2 connections. Each instance corresponds to one transport 7 | // stream (TCP stream). It operates by sending and receiving frames and is implemented as a 8 | // [Flow](flow.html) subclass. 9 | 10 | var Flow = require('./flow').Flow; 11 | 12 | exports.Connection = Connection; 13 | 14 | // Public API 15 | // ---------- 16 | 17 | // * **new Connection(log, firstStreamId, settings)**: create a new Connection 18 | // 19 | // * **Event: 'error' (type)**: signals a connection level error made by the other end 20 | // 21 | // * **Event: 'peerError' (type)**: signals the receipt of a GOAWAY frame that contains an error 22 | // code other than NO_ERROR 23 | // 24 | // * **Event: 'stream' (stream)**: signals that there's an incoming stream 25 | // 26 | // * **createStream(): stream**: initiate a new stream 27 | // 28 | // * **set(settings, callback)**: change the value of one or more settings according to the 29 | // key-value pairs of `settings`. The callback is called after the peer acknowledged the changes. 30 | // 31 | // * **ping([callback])**: send a ping and call callback when the answer arrives 32 | // 33 | // * **close([error])**: close the stream with an error code 34 | 35 | // Constructor 36 | // ----------- 37 | 38 | // The main aspects of managing the connection are: 39 | function Connection(log, firstStreamId, settings) { 40 | // * initializing the base class 41 | Flow.call(this, 0); 42 | 43 | // * logging: every method uses the common logger object 44 | this._log = log.child({ component: 'connection' }); 45 | 46 | // * stream management 47 | this._initializeStreamManagement(firstStreamId); 48 | 49 | // * lifecycle management 50 | this._initializeLifecycleManagement(); 51 | 52 | // * flow control 53 | this._initializeFlowControl(); 54 | 55 | // * settings management 56 | this._initializeSettingsManagement(settings); 57 | 58 | // * multiplexing 59 | this._initializeMultiplexing(); 60 | } 61 | Connection.prototype = Object.create(Flow.prototype, { constructor: { value: Connection } }); 62 | 63 | // Overview 64 | // -------- 65 | 66 | // | ^ | ^ 67 | // v | v | 68 | // +--------------+ +--------------+ 69 | // +---| stream1 |---| stream2 |---- .... ---+ 70 | // | | +----------+ | | +----------+ | | 71 | // | | | stream1. | | | | stream2. | | | 72 | // | +-| upstream |-+ +-| upstream |-+ | 73 | // | +----------+ +----------+ | 74 | // | | ^ | ^ | 75 | // | v | v | | 76 | // | +-----+-------------+-----+-------- .... | 77 | // | ^ | | | | 78 | // | | v | | | 79 | // | +--------------+ | | | 80 | // | | stream0 | | | | 81 | // | | connection | | | | 82 | // | | management | multiplexing | 83 | // | +--------------+ flow control | 84 | // | | ^ | 85 | // | _read() | | _write() | 86 | // | v | | 87 | // | +------------+ +-----------+ | 88 | // | |output queue| |input queue| | 89 | // +----------------+------------+-+-----------+-----------------+ 90 | // | ^ 91 | // read() | | write() 92 | // v | 93 | 94 | // Stream management 95 | // ----------------- 96 | 97 | var Stream = require('./stream').Stream; 98 | 99 | // Initialization: 100 | Connection.prototype._initializeStreamManagement = function _initializeStreamManagement(firstStreamId) { 101 | // * streams are stored in two data structures: 102 | // * `_streamIds` is an id -> stream map of the streams that are allowed to receive frames. 103 | // * `_streamPriorities` is a priority -> [stream] map of stream that allowed to send frames. 104 | this._streamIds = []; 105 | this._streamPriorities = []; 106 | 107 | // * The next outbound stream ID and the last inbound stream id 108 | this._nextStreamId = firstStreamId; 109 | this._lastIncomingStream = 0; 110 | 111 | // * Calling `_writeControlFrame` when there's an incoming stream with 0 as stream ID 112 | this._streamIds[0] = { upstream: { write: this._writeControlFrame.bind(this) } }; 113 | 114 | // * By default, the number of concurrent outbound streams is not limited. The `_streamLimit` can 115 | // be set by the SETTINGS_MAX_CONCURRENT_STREAMS setting. 116 | this._streamSlotsFree = Infinity; 117 | this._streamLimit = Infinity; 118 | this.on('RECEIVING_SETTINGS_MAX_CONCURRENT_STREAMS', this._updateStreamLimit); 119 | }; 120 | 121 | // `_writeControlFrame` is called when there's an incoming frame in the `_control` stream. It 122 | // broadcasts the message by creating an event on it. 123 | Connection.prototype._writeControlFrame = function _writeControlFrame(frame) { 124 | if ((frame.type === 'SETTINGS') || (frame.type === 'PING') || 125 | (frame.type === 'GOAWAY') || (frame.type === 'WINDOW_UPDATE') || 126 | (frame.type === 'ALTSVC')) { 127 | this._log.debug({ frame: frame }, 'Receiving connection level frame'); 128 | this.emit(frame.type, frame); 129 | } else { 130 | this._log.error({ frame: frame }, 'Invalid connection level frame'); 131 | this.emit('error', 'PROTOCOL_ERROR'); 132 | } 133 | }; 134 | 135 | // Methods to manage the stream slot pool: 136 | Connection.prototype._updateStreamLimit = function _updateStreamLimit(newStreamLimit) { 137 | var wakeup = (this._streamSlotsFree === 0) && (newStreamLimit > this._streamLimit); 138 | this._streamSlotsFree += newStreamLimit - this._streamLimit; 139 | this._streamLimit = newStreamLimit; 140 | if (wakeup) { 141 | this.emit('wakeup'); 142 | } 143 | }; 144 | 145 | Connection.prototype._changeStreamCount = function _changeStreamCount(change) { 146 | if (change) { 147 | this._log.trace({ free: this._streamSlotsFree, change: change }, 148 | 'Changing active stream count.'); 149 | var wakeup = (this._streamSlotsFree === 0) && (change < 0); 150 | this._streamSlotsFree -= change; 151 | if (wakeup) { 152 | this.emit('wakeup'); 153 | } 154 | } 155 | }; 156 | 157 | // Creating a new *inbound or outbound* stream with the given `id` (which is undefined in case of 158 | // an outbound stream) consists of three steps: 159 | // 160 | // 1. var stream = new Stream(this._log, this); 161 | // 2. this._allocateId(stream, id); 162 | // 2. this._allocatePriority(stream); 163 | 164 | // Allocating an ID to a stream 165 | Connection.prototype._allocateId = function _allocateId(stream, id) { 166 | // * initiated stream without definite ID 167 | if (id === undefined) { 168 | id = this._nextStreamId; 169 | this._nextStreamId += 2; 170 | } 171 | 172 | // * incoming stream with a legitim ID (larger than any previous and different parity than ours) 173 | else if ((id > this._lastIncomingStream) && ((id - this._nextStreamId) % 2 !== 0)) { 174 | this._lastIncomingStream = id; 175 | } 176 | 177 | // * incoming stream with invalid ID 178 | else { 179 | this._log.error({ stream_id: id, lastIncomingStream: this._lastIncomingStream }, 180 | 'Invalid incoming stream ID.'); 181 | this.emit('error', 'PROTOCOL_ERROR'); 182 | return undefined; 183 | } 184 | 185 | assert(!(id in this._streamIds)); 186 | 187 | // * adding to `this._streamIds` 188 | this._log.trace({ s: stream, stream_id: id }, 'Allocating ID for stream.'); 189 | this._streamIds[id] = stream; 190 | stream.id = id; 191 | this.emit('new_stream', stream, id); 192 | 193 | // * forwarding connection errors from streams 194 | stream.on('connectionError', this.emit.bind(this, 'error')); 195 | 196 | return id; 197 | }; 198 | 199 | // Allocating a priority to a stream, and managing priority changes 200 | Connection.prototype._allocatePriority = function _allocatePriority(stream) { 201 | this._log.trace({ s: stream }, 'Allocating priority for stream.'); 202 | this._insert(stream, stream._priority); 203 | stream.on('priority', this._reprioritize.bind(this, stream)); 204 | stream.upstream.on('readable', this.emit.bind(this, 'wakeup')); 205 | this.emit('wakeup'); 206 | }; 207 | 208 | Connection.prototype._insert = function _insert(stream, priority) { 209 | if (priority in this._streamPriorities) { 210 | this._streamPriorities[priority].push(stream); 211 | } else { 212 | this._streamPriorities[priority] = [stream]; 213 | } 214 | }; 215 | 216 | Connection.prototype._reprioritize = function _reprioritize(stream, priority) { 217 | var bucket = this._streamPriorities[stream._priority]; 218 | var index = bucket.indexOf(stream); 219 | assert(index !== -1); 220 | bucket.splice(index, 1); 221 | if (bucket.length === 0) { 222 | delete this._streamPriorities[stream._priority]; 223 | } 224 | 225 | this._insert(stream, priority); 226 | }; 227 | 228 | // Creating an *inbound* stream with the given ID. It is called when there's an incoming frame to 229 | // a previously nonexistent stream. 230 | Connection.prototype._createIncomingStream = function _createIncomingStream(id) { 231 | this._log.debug({ stream_id: id }, 'New incoming stream.'); 232 | 233 | var stream = new Stream(this._log, this); 234 | this._allocateId(stream, id); 235 | this._allocatePriority(stream); 236 | this.emit('stream', stream, id); 237 | 238 | return stream; 239 | }; 240 | 241 | // Creating an *outbound* stream 242 | Connection.prototype.createStream = function createStream() { 243 | this._log.trace('Creating new outbound stream.'); 244 | 245 | // * Receiving is enabled immediately, and an ID gets assigned to the stream 246 | var stream = new Stream(this._log, this); 247 | this._allocatePriority(stream); 248 | 249 | return stream; 250 | }; 251 | 252 | // Multiplexing 253 | // ------------ 254 | 255 | Connection.prototype._initializeMultiplexing = function _initializeMultiplexing() { 256 | this.on('window_update', this.emit.bind(this, 'wakeup')); 257 | this._sendScheduled = false; 258 | this._firstFrameReceived = false; 259 | }; 260 | 261 | // The `_send` method is a virtual method of the [Flow class](flow.html) that has to be implemented 262 | // by child classes. It reads frames from streams and pushes them to the output buffer. 263 | Connection.prototype._send = function _send(immediate) { 264 | // * Do not do anything if the connection is already closed 265 | if (this._closed) { 266 | return; 267 | } 268 | 269 | // * Collapsing multiple calls in a turn into a single deferred call 270 | if (immediate) { 271 | this._sendScheduled = false; 272 | } else { 273 | if (!this._sendScheduled) { 274 | this._sendScheduled = true; 275 | setImmediate(this._send.bind(this, true)); 276 | } 277 | return; 278 | } 279 | 280 | this._log.trace('Starting forwarding frames from streams.'); 281 | 282 | // * Looping through priority `bucket`s in priority order. 283 | priority_loop: 284 | for (var priority in this._streamPriorities) { 285 | var bucket = this._streamPriorities[priority]; 286 | var nextBucket = []; 287 | 288 | // * Forwarding frames from buckets with round-robin scheduling. 289 | // 1. pulling out frame 290 | // 2. if there's no frame, skip this stream 291 | // 3. if forwarding this frame would make `streamCount` greater than `streamLimit`, skip 292 | // this stream 293 | // 4. adding stream to the bucket of the next round 294 | // 5. assigning an ID to the frame (allocating an ID to the stream if there isn't already) 295 | // 6. if forwarding a PUSH_PROMISE, allocate ID to the promised stream 296 | // 7. forwarding the frame, changing `streamCount` as appropriate 297 | // 8. stepping to the next stream if there's still more frame needed in the output buffer 298 | // 9. switching to the bucket of the next round 299 | while (bucket.length > 0) { 300 | for (var index = 0; index < bucket.length; index++) { 301 | var stream = bucket[index]; 302 | var frame = stream.upstream.read((this._window > 0) ? this._window : -1); 303 | 304 | if (!frame) { 305 | continue; 306 | } else if (frame.count_change > this._streamSlotsFree) { 307 | stream.upstream.unshift(frame); 308 | continue; 309 | } 310 | 311 | nextBucket.push(stream); 312 | 313 | if (frame.stream === undefined) { 314 | frame.stream = stream.id || this._allocateId(stream); 315 | } 316 | 317 | if (frame.type === 'PUSH_PROMISE') { 318 | this._allocatePriority(frame.promised_stream); 319 | frame.promised_stream = this._allocateId(frame.promised_stream); 320 | } 321 | 322 | this._log.trace({ s: stream, frame: frame }, 'Forwarding outgoing frame'); 323 | var moreNeeded = this.push(frame); 324 | this._changeStreamCount(frame.count_change); 325 | 326 | assert(moreNeeded !== null); // The frame shouldn't be unforwarded 327 | if (moreNeeded === false) { 328 | break priority_loop; 329 | } 330 | } 331 | 332 | bucket = nextBucket; 333 | nextBucket = []; 334 | } 335 | } 336 | 337 | // * if we couldn't forward any frame, then sleep until window update, or some other wakeup event 338 | if (moreNeeded === undefined) { 339 | this.once('wakeup', this._send.bind(this)); 340 | } 341 | 342 | this._log.trace({ moreNeeded: moreNeeded }, 'Stopping forwarding frames from streams.'); 343 | }; 344 | 345 | // The `_receive` method is another virtual method of the [Flow class](flow.html) that has to be 346 | // implemented by child classes. It forwards the given frame to the appropriate stream: 347 | Connection.prototype._receive = function _receive(frame, done) { 348 | this._log.trace({ frame: frame }, 'Forwarding incoming frame'); 349 | 350 | // * first frame needs to be checked by the `_onFirstFrameReceived` method 351 | if (!this._firstFrameReceived) { 352 | this._firstFrameReceived = true; 353 | this._onFirstFrameReceived(frame); 354 | } 355 | 356 | // Do some sanity checking here before we create a stream 357 | if ((frame.type == 'SETTINGS' || 358 | frame.type == 'PING' || 359 | frame.type == 'GOAWAY') && 360 | frame.stream != 0) { 361 | // Got connection-level frame on a stream - EEP! 362 | this.close('PROTOCOL_ERROR'); 363 | return; 364 | } else if ((frame.type == 'DATA' || 365 | frame.type == 'HEADERS' || 366 | frame.type == 'PRIORITY' || 367 | frame.type == 'RST_STREAM' || 368 | frame.type == 'PUSH_PROMISE' || 369 | frame.type == 'CONTINUATION') && 370 | frame.stream == 0) { 371 | // Got stream-level frame on connection - EEP! 372 | this.close('PROTOCOL_ERROR'); 373 | return; 374 | } 375 | // WINDOW_UPDATE can be on either stream or connection 376 | 377 | // * gets the appropriate stream from the stream registry 378 | var stream = this._streamIds[frame.stream]; 379 | 380 | // * or creates one if it's not in `this.streams` 381 | if (!stream) { 382 | stream = this._createIncomingStream(frame.stream); 383 | } 384 | 385 | // * in case of PUSH_PROMISE, replaces the promised stream id with a new incoming stream 386 | if (frame.type === 'PUSH_PROMISE') { 387 | frame.promised_stream = this._createIncomingStream(frame.promised_stream); 388 | } 389 | 390 | frame.count_change = this._changeStreamCount.bind(this); 391 | 392 | // * and writes it to the `stream`'s `upstream` 393 | stream.upstream.write(frame); 394 | 395 | done(); 396 | }; 397 | 398 | // Settings management 399 | // ------------------- 400 | 401 | var defaultSettings = { 402 | }; 403 | 404 | // Settings management initialization: 405 | Connection.prototype._initializeSettingsManagement = function _initializeSettingsManagement(settings) { 406 | // * Setting up the callback queue for setting acknowledgements 407 | this._settingsAckCallbacks = []; 408 | 409 | // * Sending the initial settings. 410 | this._log.debug({ settings: settings }, 411 | 'Sending the first SETTINGS frame as part of the connection header.'); 412 | this.set(settings || defaultSettings); 413 | 414 | // * Forwarding SETTINGS frames to the `_receiveSettings` method 415 | this.on('SETTINGS', this._receiveSettings); 416 | this.on('RECEIVING_SETTINGS_MAX_FRAME_SIZE', this._sanityCheckMaxFrameSize); 417 | }; 418 | 419 | // * Checking that the first frame the other endpoint sends is SETTINGS 420 | Connection.prototype._onFirstFrameReceived = function _onFirstFrameReceived(frame) { 421 | if ((frame.stream === 0) && (frame.type === 'SETTINGS')) { 422 | this._log.debug('Receiving the first SETTINGS frame as part of the connection header.'); 423 | } else { 424 | this._log.fatal({ frame: frame }, 'Invalid connection header: first frame is not SETTINGS.'); 425 | this.emit('error', 'PROTOCOL_ERROR'); 426 | } 427 | }; 428 | 429 | // Handling of incoming SETTINGS frames. 430 | Connection.prototype._receiveSettings = function _receiveSettings(frame) { 431 | // * If it's an ACK, call the appropriate callback 432 | if (frame.flags.ACK) { 433 | var callback = this._settingsAckCallbacks.shift(); 434 | if (callback) { 435 | callback(); 436 | } 437 | } 438 | 439 | // * If it's a setting change request, then send an ACK and change the appropriate settings 440 | else { 441 | if (!this._closed) { 442 | this.push({ 443 | type: 'SETTINGS', 444 | flags: { ACK: true }, 445 | stream: 0, 446 | settings: {} 447 | }); 448 | } 449 | for (var name in frame.settings) { 450 | this.emit('RECEIVING_' + name, frame.settings[name]); 451 | } 452 | } 453 | }; 454 | 455 | Connection.prototype._sanityCheckMaxFrameSize = function _sanityCheckMaxFrameSize(value) { 456 | if ((value < 0x4000) || (value >= 0x01000000)) { 457 | this._log.fatal('Received invalid value for max frame size: ' + value); 458 | this.emit('error'); 459 | } 460 | }; 461 | 462 | // Changing one or more settings value and sending out a SETTINGS frame 463 | Connection.prototype.set = function set(settings, callback) { 464 | // * Calling the callback and emitting event when the change is acknowledges 465 | var self = this; 466 | this._settingsAckCallbacks.push(function() { 467 | for (var name in settings) { 468 | self.emit('ACKNOWLEDGED_' + name, settings[name]); 469 | } 470 | if (callback) { 471 | callback(); 472 | } 473 | }); 474 | 475 | // * Sending out the SETTINGS frame 476 | this.push({ 477 | type: 'SETTINGS', 478 | flags: { ACK: false }, 479 | stream: 0, 480 | settings: settings 481 | }); 482 | for (var name in settings) { 483 | this.emit('SENDING_' + name, settings[name]); 484 | } 485 | }; 486 | 487 | // Lifecycle management 488 | // -------------------- 489 | 490 | // The main responsibilities of lifecycle management code: 491 | // 492 | // * keeping the connection alive by 493 | // * sending PINGs when the connection is idle 494 | // * answering PINGs 495 | // * ending the connection 496 | 497 | Connection.prototype._initializeLifecycleManagement = function _initializeLifecycleManagement() { 498 | this._pings = {}; 499 | this.on('PING', this._receivePing); 500 | this.on('GOAWAY', this._receiveGoaway); 501 | this._closed = false; 502 | }; 503 | 504 | // Generating a string of length 16 with random hexadecimal digits 505 | Connection.prototype._generatePingId = function _generatePingId() { 506 | do { 507 | var id = ''; 508 | for (var i = 0; i < 16; i++) { 509 | id += Math.floor(Math.random()*16).toString(16); 510 | } 511 | } while(id in this._pings); 512 | return id; 513 | }; 514 | 515 | // Sending a ping and calling `callback` when the answer arrives 516 | Connection.prototype.ping = function ping(callback) { 517 | var id = this._generatePingId(); 518 | var data = new Buffer(id, 'hex'); 519 | this._pings[id] = callback; 520 | 521 | this._log.debug({ data: data }, 'Sending PING.'); 522 | this.push({ 523 | type: 'PING', 524 | flags: { 525 | ACK: false 526 | }, 527 | stream: 0, 528 | data: data 529 | }); 530 | }; 531 | 532 | // Answering pings 533 | Connection.prototype._receivePing = function _receivePing(frame) { 534 | if (frame.flags.ACK) { 535 | var id = frame.data.toString('hex'); 536 | if (id in this._pings) { 537 | this._log.debug({ data: frame.data }, 'Receiving answer for a PING.'); 538 | var callback = this._pings[id]; 539 | if (callback) { 540 | callback(); 541 | } 542 | delete this._pings[id]; 543 | } else { 544 | this._log.warn({ data: frame.data }, 'Unsolicited PING answer.'); 545 | } 546 | 547 | } else { 548 | this._log.debug({ data: frame.data }, 'Answering PING.'); 549 | this.push({ 550 | type: 'PING', 551 | flags: { 552 | ACK: true 553 | }, 554 | stream: 0, 555 | data: frame.data 556 | }); 557 | } 558 | }; 559 | 560 | // Terminating the connection 561 | Connection.prototype.close = function close(error) { 562 | if (this._closed) { 563 | this._log.warn('Trying to close an already closed connection'); 564 | return; 565 | } 566 | 567 | this._log.debug({ error: error }, 'Closing the connection'); 568 | this.push({ 569 | type: 'GOAWAY', 570 | flags: {}, 571 | stream: 0, 572 | last_stream: this._lastIncomingStream, 573 | error: error || 'NO_ERROR' 574 | }); 575 | this.push(null); 576 | this._closed = true; 577 | }; 578 | 579 | Connection.prototype._receiveGoaway = function _receiveGoaway(frame) { 580 | this._log.debug({ error: frame.error }, 'Other end closed the connection'); 581 | this.push(null); 582 | this._closed = true; 583 | if (frame.error !== 'NO_ERROR') { 584 | this.emit('peerError', frame.error); 585 | } 586 | }; 587 | 588 | // Flow control 589 | // ------------ 590 | 591 | Connection.prototype._initializeFlowControl = function _initializeFlowControl() { 592 | // Handling of initial window size of individual streams. 593 | this._initialStreamWindowSize = INITIAL_STREAM_WINDOW_SIZE; 594 | this.on('new_stream', function(stream) { 595 | stream.upstream.setInitialWindow(this._initialStreamWindowSize); 596 | }); 597 | this.on('RECEIVING_SETTINGS_INITIAL_WINDOW_SIZE', this._setInitialStreamWindowSize); 598 | this._streamIds[0].upstream.setInitialWindow = function noop() {}; 599 | }; 600 | 601 | // The initial connection flow control window is 65535 bytes. 602 | var INITIAL_STREAM_WINDOW_SIZE = 65535; 603 | 604 | // A SETTINGS frame can alter the initial flow control window size for all current streams. When the 605 | // value of SETTINGS_INITIAL_WINDOW_SIZE changes, a receiver MUST adjust the window size of all 606 | // stream by calling the `setInitialStreamWindowSize` method. The window size has to be modified by 607 | // the difference between the new value and the old value. 608 | Connection.prototype._setInitialStreamWindowSize = function _setInitialStreamWindowSize(size) { 609 | if ((this._initialStreamWindowSize === Infinity) && (size !== Infinity)) { 610 | this._log.error('Trying to manipulate initial flow control window size after flow control was turned off.'); 611 | this.emit('error', 'FLOW_CONTROL_ERROR'); 612 | } else { 613 | this._log.debug({ size: size }, 'Changing stream initial window size.'); 614 | this._initialStreamWindowSize = size; 615 | this._streamIds.forEach(function(stream) { 616 | stream.upstream.setInitialWindow(size); 617 | }); 618 | } 619 | }; 620 | -------------------------------------------------------------------------------- /lib/node-http2/lib/protocol/endpoint.js: -------------------------------------------------------------------------------- 1 | var assert = require('assert'); 2 | 3 | var Serializer = require('./framer').Serializer; 4 | var Deserializer = require('./framer').Deserializer; 5 | var Compressor = require('./compressor').Compressor; 6 | var Decompressor = require('./compressor').Decompressor; 7 | var Connection = require('./connection').Connection; 8 | var Duplex = require('stream').Duplex; 9 | var Transform = require('stream').Transform; 10 | 11 | exports.Endpoint = Endpoint; 12 | 13 | // The Endpoint class 14 | // ================== 15 | 16 | // Public API 17 | // ---------- 18 | 19 | // - **new Endpoint(log, role, settings, filters)**: create a new Endpoint. 20 | // 21 | // - `log`: bunyan logger of the parent 22 | // - `role`: 'CLIENT' or 'SERVER' 23 | // - `settings`: initial HTTP/2 settings 24 | // - `filters`: a map of functions that filter the traffic between components (for debugging or 25 | // intentional failure injection). 26 | // 27 | // Filter functions get three arguments: 28 | // 1. `frame`: the current frame 29 | // 2. `forward(frame)`: function that can be used to forward a frame to the next component 30 | // 3. `done()`: callback to signal the end of the filter process 31 | // 32 | // Valid filter names and their position in the stack: 33 | // - `beforeSerialization`: after compression, before serialization 34 | // - `beforeCompression`: after multiplexing, before compression 35 | // - `afterDeserialization`: after deserialization, before decompression 36 | // - `afterDecompression`: after decompression, before multiplexing 37 | // 38 | // * **Event: 'stream' (Stream)**: 'stream' event forwarded from the underlying Connection 39 | // 40 | // * **Event: 'error' (type)**: signals an error 41 | // 42 | // * **createStream(): Stream**: initiate a new stream (forwarded to the underlying Connection) 43 | // 44 | // * **close([error])**: close the connection with an error code 45 | 46 | // Constructor 47 | // ----------- 48 | 49 | // The process of initialization: 50 | function Endpoint(log, role, settings, filters) { 51 | Duplex.call(this); 52 | 53 | // * Initializing logging infrastructure 54 | this._log = log.child({ component: 'endpoint', e: this }); 55 | 56 | // * First part of the handshake process: sending and receiving the client connection header 57 | // prelude. 58 | assert((role === 'CLIENT') || role === 'SERVER'); 59 | if (role === 'CLIENT') { 60 | this._writePrelude(); 61 | } else { 62 | this._readPrelude(); 63 | } 64 | 65 | // * Initialization of component. This includes the second part of the handshake process: 66 | // sending the first SETTINGS frame. This is done by the connection class right after 67 | // initialization. 68 | this._initializeDataFlow(role, settings, filters || {}); 69 | 70 | // * Initialization of management code. 71 | this._initializeManagement(); 72 | 73 | // * Initializing error handling. 74 | this._initializeErrorHandling(); 75 | } 76 | Endpoint.prototype = Object.create(Duplex.prototype, { constructor: { value: Endpoint } }); 77 | 78 | // Handshake 79 | // --------- 80 | 81 | var CLIENT_PRELUDE = new Buffer('PRI * HTTP/2.0\r\n\r\nSM\r\n\r\n'); 82 | 83 | // Writing the client header is simple and synchronous. 84 | Endpoint.prototype._writePrelude = function _writePrelude() { 85 | this._log.debug('Sending the client connection header prelude.'); 86 | this.push(CLIENT_PRELUDE); 87 | }; 88 | 89 | // The asynchronous process of reading the client header: 90 | Endpoint.prototype._readPrelude = function _readPrelude() { 91 | // * progress in the header is tracker using a `cursor` 92 | var cursor = 0; 93 | 94 | // * `_write` is temporarily replaced by the comparator function 95 | this._write = function _temporalWrite(chunk, encoding, done) { 96 | // * which compares the stored header with the current `chunk` byte by byte and emits the 97 | // 'error' event if there's a byte that doesn't match 98 | var offset = cursor; 99 | while(cursor < CLIENT_PRELUDE.length && (cursor - offset) < chunk.length) { 100 | if (CLIENT_PRELUDE[cursor] !== chunk[cursor - offset]) { 101 | this._log.fatal({ cursor: cursor, offset: offset, chunk: chunk }, 102 | 'Client connection header prelude does not match.'); 103 | this._error('handshake', 'PROTOCOL_ERROR'); 104 | return; 105 | } 106 | cursor += 1; 107 | } 108 | 109 | // * if the whole header is over, and there were no error then restore the original `_write` 110 | // and call it with the remaining part of the current chunk 111 | if (cursor === CLIENT_PRELUDE.length) { 112 | this._log.debug('Successfully received the client connection header prelude.'); 113 | delete this._write; 114 | chunk = chunk.slice(cursor - offset); 115 | this._write(chunk, encoding, done); 116 | } 117 | }; 118 | }; 119 | 120 | // Data flow 121 | // --------- 122 | 123 | // +---------------------------------------------+ 124 | // | | 125 | // | +-------------------------------------+ | 126 | // | | +---------+ +---------+ +---------+ | | 127 | // | | | stream1 | | stream2 | | ... | | | 128 | // | | +---------+ +---------+ +---------+ | | 129 | // | | connection | | 130 | // | +-------------------------------------+ | 131 | // | | ^ | 132 | // | pipe | | pipe | 133 | // | v | | 134 | // | +------------------+------------------+ | 135 | // | | compressor | decompressor | | 136 | // | +------------------+------------------+ | 137 | // | | ^ | 138 | // | pipe | | pipe | 139 | // | v | | 140 | // | +------------------+------------------+ | 141 | // | | serializer | deserializer | | 142 | // | +------------------+------------------+ | 143 | // | | ^ | 144 | // | _read() | | _write() | 145 | // | v | | 146 | // | +------------+ +-----------+ | 147 | // | |output queue| |input queue| | 148 | // +------+------------+-----+-----------+-------+ 149 | // | ^ 150 | // read() | | write() 151 | // v | 152 | 153 | function createTransformStream(filter) { 154 | var transform = new Transform({ objectMode: true }); 155 | var push = transform.push.bind(transform); 156 | transform._transform = function(frame, encoding, done) { 157 | filter(frame, push, done); 158 | }; 159 | return transform; 160 | } 161 | 162 | function pipeAndFilter(stream1, stream2, filter) { 163 | if (filter) { 164 | stream1.pipe(createTransformStream(filter)).pipe(stream2); 165 | } else { 166 | stream1.pipe(stream2); 167 | } 168 | } 169 | 170 | Endpoint.prototype._initializeDataFlow = function _initializeDataFlow(role, settings, filters) { 171 | var firstStreamId, compressorRole, decompressorRole; 172 | if (role === 'CLIENT') { 173 | firstStreamId = 1; 174 | compressorRole = 'REQUEST'; 175 | decompressorRole = 'RESPONSE'; 176 | } else { 177 | firstStreamId = 2; 178 | compressorRole = 'RESPONSE'; 179 | decompressorRole = 'REQUEST'; 180 | } 181 | 182 | this._serializer = new Serializer(this._log); 183 | this._deserializer = new Deserializer(this._log); 184 | this._compressor = new Compressor(this._log, compressorRole); 185 | this._decompressor = new Decompressor(this._log, decompressorRole); 186 | this._connection = new Connection(this._log, firstStreamId, settings); 187 | 188 | pipeAndFilter(this._connection, this._compressor, filters.beforeCompression); 189 | pipeAndFilter(this._compressor, this._serializer, filters.beforeSerialization); 190 | pipeAndFilter(this._deserializer, this._decompressor, filters.afterDeserialization); 191 | pipeAndFilter(this._decompressor, this._connection, filters.afterDecompression); 192 | 193 | this._connection.on('ACKNOWLEDGED_SETTINGS_HEADER_TABLE_SIZE', 194 | this._decompressor.setTableSizeLimit.bind(this._decompressor)); 195 | this._connection.on('RECEIVING_SETTINGS_HEADER_TABLE_SIZE', 196 | this._compressor.setTableSizeLimit.bind(this._compressor)); 197 | }; 198 | 199 | var noread = {}; 200 | Endpoint.prototype._read = function _read() { 201 | this._readableState.sync = true; 202 | var moreNeeded = noread, chunk; 203 | while (moreNeeded && (chunk = this._serializer.read())) { 204 | moreNeeded = this.push(chunk); 205 | } 206 | if (moreNeeded === noread) { 207 | this._serializer.once('readable', this._read.bind(this)); 208 | } 209 | this._readableState.sync = false; 210 | }; 211 | 212 | Endpoint.prototype._write = function _write(chunk, encoding, done) { 213 | this._deserializer.write(chunk, encoding, done); 214 | }; 215 | 216 | // Management 217 | // -------------- 218 | 219 | Endpoint.prototype._initializeManagement = function _initializeManagement() { 220 | this._connection.on('stream', this.emit.bind(this, 'stream')); 221 | }; 222 | 223 | Endpoint.prototype.createStream = function createStream() { 224 | return this._connection.createStream(); 225 | }; 226 | 227 | // Error handling 228 | // -------------- 229 | 230 | Endpoint.prototype._initializeErrorHandling = function _initializeErrorHandling() { 231 | this._serializer.on('error', this._error.bind(this, 'serializer')); 232 | this._deserializer.on('error', this._error.bind(this, 'deserializer')); 233 | this._compressor.on('error', this._error.bind(this, 'compressor')); 234 | this._decompressor.on('error', this._error.bind(this, 'decompressor')); 235 | this._connection.on('error', this._error.bind(this, 'connection')); 236 | 237 | this._connection.on('peerError', this.emit.bind(this, 'peerError')); 238 | }; 239 | 240 | Endpoint.prototype._error = function _error(component, error) { 241 | this._log.fatal({ source: component, message: error }, 'Fatal error, closing connection'); 242 | this.close(error); 243 | setImmediate(this.emit.bind(this, 'error', error)); 244 | }; 245 | 246 | Endpoint.prototype.close = function close(error) { 247 | this._connection.close(error); 248 | }; 249 | 250 | // Bunyan serializers 251 | // ------------------ 252 | 253 | exports.serializers = {}; 254 | 255 | var nextId = 0; 256 | exports.serializers.e = function(endpoint) { 257 | if (!('id' in endpoint)) { 258 | endpoint.id = nextId; 259 | nextId += 1; 260 | } 261 | return endpoint.id; 262 | }; 263 | -------------------------------------------------------------------------------- /lib/node-http2/lib/protocol/flow.js: -------------------------------------------------------------------------------- 1 | var assert = require('assert'); 2 | 3 | // The Flow class 4 | // ============== 5 | 6 | // Flow is a [Duplex stream][1] subclass which implements HTTP/2 flow control. It is designed to be 7 | // subclassed by [Connection](connection.html) and the `upstream` component of [Stream](stream.html). 8 | // [1]: https://nodejs.org/api/stream.html#stream_class_stream_duplex 9 | 10 | var Duplex = require('stream').Duplex; 11 | 12 | exports.Flow = Flow; 13 | 14 | // Public API 15 | // ---------- 16 | 17 | // * **Event: 'error' (type)**: signals an error 18 | // 19 | // * **setInitialWindow(size)**: the initial flow control window size can be changed *any time* 20 | // ([as described in the standard][1]) using this method 21 | // 22 | // [1]: https://tools.ietf.org/html/rfc7540#section-6.9.2 23 | 24 | // API for child classes 25 | // --------------------- 26 | 27 | // * **new Flow([flowControlId])**: creating a new flow that will listen for WINDOW_UPDATES frames 28 | // with the given `flowControlId` (or every update frame if not given) 29 | // 30 | // * **_send()**: called when more frames should be pushed. The child class is expected to override 31 | // this (instead of the `_read` method of the Duplex class). 32 | // 33 | // * **_receive(frame, readyCallback)**: called when there's an incoming frame. The child class is 34 | // expected to override this (instead of the `_write` method of the Duplex class). 35 | // 36 | // * **push(frame): bool**: schedules `frame` for sending. 37 | // 38 | // Returns `true` if it needs more frames in the output queue, `false` if the output queue is 39 | // full, and `null` if did not push the frame into the output queue (instead, it pushed it into 40 | // the flow control queue). 41 | // 42 | // * **read(limit): frame**: like the regular `read`, but the 'flow control size' (0 for non-DATA 43 | // frames, length of the payload for DATA frames) of the returned frame will be under `limit`. 44 | // Small exception: pass -1 as `limit` if the max. flow control size is 0. `read(0)` means the 45 | // same thing as [in the original API](https://nodejs.org/api/stream.html#stream_stream_read_0). 46 | // 47 | // * **getLastQueuedFrame(): frame**: returns the last frame in output buffers 48 | // 49 | // * **_log**: the Flow class uses the `_log` object of the parent 50 | 51 | // Constructor 52 | // ----------- 53 | 54 | // When a HTTP/2.0 connection is first established, new streams are created with an initial flow 55 | // control window size of 65535 bytes. 56 | var INITIAL_WINDOW_SIZE = 65535; 57 | 58 | // `flowControlId` is needed if only specific WINDOW_UPDATEs should be watched. 59 | function Flow(flowControlId) { 60 | Duplex.call(this, { objectMode: true }); 61 | 62 | this._window = this._initialWindow = INITIAL_WINDOW_SIZE; 63 | this._flowControlId = flowControlId; 64 | this._queue = []; 65 | this._ended = false; 66 | this._received = 0; 67 | this._blocked = false; 68 | } 69 | Flow.prototype = Object.create(Duplex.prototype, { constructor: { value: Flow } }); 70 | 71 | // Incoming frames 72 | // --------------- 73 | 74 | // `_receive` is called when there's an incoming frame. 75 | Flow.prototype._receive = function _receive(frame, callback) { 76 | throw new Error('The _receive(frame, callback) method has to be overridden by the child class!'); 77 | }; 78 | 79 | // `_receive` is called by `_write` which in turn is [called by Duplex][1] when someone `write()`s 80 | // to the flow. It emits the 'receiving' event and notifies the window size tracking code if the 81 | // incoming frame is a WINDOW_UPDATE. 82 | // [1]: https://nodejs.org/api/stream.html#stream_writable_write_chunk_encoding_callback_1 83 | Flow.prototype._write = function _write(frame, encoding, callback) { 84 | var sentToUs = (this._flowControlId === undefined) || (frame.stream === this._flowControlId); 85 | 86 | if (sentToUs && (frame.flags.END_STREAM || (frame.type === 'RST_STREAM'))) { 87 | this._ended = true; 88 | } 89 | 90 | if ((frame.type === 'DATA') && (frame.data.length > 0)) { 91 | this._receive(frame, function() { 92 | this._received += frame.data.length; 93 | if (!this._restoreWindowTimer) { 94 | this._restoreWindowTimer = setImmediate(this._restoreWindow.bind(this)); 95 | } 96 | callback(); 97 | }.bind(this)); 98 | } 99 | 100 | else { 101 | this._receive(frame, callback); 102 | } 103 | 104 | if (sentToUs && (frame.type === 'WINDOW_UPDATE')) { 105 | this._updateWindow(frame); 106 | } 107 | }; 108 | 109 | // `_restoreWindow` basically acknowledges the DATA frames received since it's last call. It sends 110 | // a WINDOW_UPDATE that restores the flow control window of the remote end. 111 | // TODO: push this directly into the output queue. No need to wait for DATA frames in the queue. 112 | Flow.prototype._restoreWindow = function _restoreWindow() { 113 | delete this._restoreWindowTimer; 114 | if (!this._ended && (this._received > 0)) { 115 | this.push({ 116 | type: 'WINDOW_UPDATE', 117 | flags: {}, 118 | stream: this._flowControlId, 119 | window_size: this._received 120 | }); 121 | this._received = 0; 122 | } 123 | }; 124 | 125 | // Outgoing frames - sending procedure 126 | // ----------------------------------- 127 | 128 | // flow 129 | // +-------------------------------------------------+ 130 | // | | 131 | // +--------+ +---------+ | 132 | // read() | output | _read() | flow | _send() | 133 | // <----------| |<----------| control |<------------- | 134 | // | buffer | | buffer | | 135 | // +--------+ +---------+ | 136 | // | input | | 137 | // ---------->| |-----------------------------------> | 138 | // write() | buffer | _write() _receive() | 139 | // +--------+ | 140 | // | | 141 | // +-------------------------------------------------+ 142 | 143 | // `_send` is called when more frames should be pushed to the output buffer. 144 | Flow.prototype._send = function _send() { 145 | throw new Error('The _send() method has to be overridden by the child class!'); 146 | }; 147 | 148 | // `_send` is called by `_read` which is in turn [called by Duplex][1] when it wants to have more 149 | // items in the output queue. 150 | // [1]: https://nodejs.org/api/stream.html#stream_writable_write_chunk_encoding_callback_1 151 | Flow.prototype._read = function _read() { 152 | // * if the flow control queue is empty, then let the user push more frames 153 | if (this._queue.length === 0) { 154 | this._send(); 155 | } 156 | 157 | // * if there are items in the flow control queue, then let's put them into the output queue (to 158 | // the extent it is possible with respect to the window size and output queue feedback) 159 | else if (this._window > 0) { 160 | this._blocked = false; 161 | this._readableState.sync = true; // to avoid reentrant calls 162 | do { 163 | var moreNeeded = this._push(this._queue[0]); 164 | if (moreNeeded !== null) { 165 | this._queue.shift(); 166 | } 167 | } while (moreNeeded && (this._queue.length > 0)); 168 | this._readableState.sync = false; 169 | 170 | assert((!moreNeeded) || // * output queue is full 171 | (this._queue.length === 0) || // * flow control queue is empty 172 | (!this._window && (this._queue[0].type === 'DATA'))); // * waiting for window update 173 | } 174 | 175 | // * otherwise, come back when the flow control window is positive 176 | else if (!this._blocked) { 177 | this._parentPush({ 178 | type: 'BLOCKED', 179 | flags: {}, 180 | stream: this._flowControlId 181 | }); 182 | this.once('window_update', this._read); 183 | this._blocked = true; 184 | } 185 | }; 186 | 187 | var MAX_PAYLOAD_SIZE = 4096; // Must not be greater than MAX_HTTP_PAYLOAD_SIZE which is 16383 188 | 189 | // `read(limit)` is like the `read` of the Readable class, but it guarantess that the 'flow control 190 | // size' (0 for non-DATA frames, length of the payload for DATA frames) of the returned frame will 191 | // be under `limit`. 192 | Flow.prototype.read = function read(limit) { 193 | if (limit === 0) { 194 | return Duplex.prototype.read.call(this, 0); 195 | } else if (limit === -1) { 196 | limit = 0; 197 | } else if ((limit === undefined) || (limit > MAX_PAYLOAD_SIZE)) { 198 | limit = MAX_PAYLOAD_SIZE; 199 | } 200 | 201 | // * Looking at the first frame in the queue without pulling it out if possible. 202 | var frame = this._readableState.buffer[0]; 203 | if (!frame && !this._readableState.ended) { 204 | this._read(); 205 | frame = this._readableState.buffer[0]; 206 | } 207 | 208 | if (frame && (frame.type === 'DATA')) { 209 | // * If the frame is DATA, then there's two special cases: 210 | // * if the limit is 0, we shouldn't return anything 211 | // * if the size of the frame is larger than limit, then the frame should be split 212 | if (limit === 0) { 213 | return Duplex.prototype.read.call(this, 0); 214 | } 215 | 216 | else if (frame.data.length > limit) { 217 | this._log.trace({ frame: frame, size: frame.data.length, forwardable: limit }, 218 | 'Splitting out forwardable part of a DATA frame.'); 219 | this.unshift({ 220 | type: 'DATA', 221 | flags: {}, 222 | stream: frame.stream, 223 | data: frame.data.slice(0, limit) 224 | }); 225 | frame.data = frame.data.slice(limit); 226 | } 227 | } 228 | 229 | return Duplex.prototype.read.call(this); 230 | }; 231 | 232 | // `_parentPush` pushes the given `frame` into the output queue 233 | Flow.prototype._parentPush = function _parentPush(frame) { 234 | this._log.trace({ frame: frame }, 'Pushing frame into the output queue'); 235 | 236 | if (frame && (frame.type === 'DATA') && (this._window !== Infinity)) { 237 | this._log.trace({ window: this._window, by: frame.data.length }, 238 | 'Decreasing flow control window size.'); 239 | this._window -= frame.data.length; 240 | assert(this._window >= 0); 241 | } 242 | 243 | return Duplex.prototype.push.call(this, frame); 244 | }; 245 | 246 | // `_push(frame)` pushes `frame` into the output queue and decreases the flow control window size. 247 | // It is capable of splitting DATA frames into smaller parts, if the window size is not enough to 248 | // push the whole frame. The return value is similar to `push` except that it returns `null` if it 249 | // did not push the whole frame to the output queue (but maybe it did push part of the frame). 250 | Flow.prototype._push = function _push(frame) { 251 | var data = frame && (frame.type === 'DATA') && frame.data; 252 | var maxFrameLength = (this._window < 16384) ? this._window : 16384; 253 | 254 | if (!data || (data.length <= maxFrameLength)) { 255 | return this._parentPush(frame); 256 | } 257 | 258 | else if (this._window <= 0) { 259 | return null; 260 | } 261 | 262 | else { 263 | this._log.trace({ frame: frame, size: frame.data.length, forwardable: this._window }, 264 | 'Splitting out forwardable part of a DATA frame.'); 265 | frame.data = data.slice(maxFrameLength); 266 | this._parentPush({ 267 | type: 'DATA', 268 | flags: {}, 269 | stream: frame.stream, 270 | data: data.slice(0, maxFrameLength) 271 | }); 272 | return null; 273 | } 274 | }; 275 | 276 | // Push `frame` into the flow control queue, or if it's empty, then directly into the output queue 277 | Flow.prototype.push = function push(frame) { 278 | if (frame === null) { 279 | this._log.debug('Enqueueing outgoing End Of Stream'); 280 | } else { 281 | this._log.debug({ frame: frame }, 'Enqueueing outgoing frame'); 282 | } 283 | 284 | var moreNeeded = null; 285 | if (this._queue.length === 0) { 286 | moreNeeded = this._push(frame); 287 | } 288 | 289 | if (moreNeeded === null) { 290 | this._queue.push(frame); 291 | } 292 | 293 | return moreNeeded; 294 | }; 295 | 296 | // `getLastQueuedFrame` returns the last frame in output buffers. This is primarily used by the 297 | // [Stream](stream.html) class to mark the last frame with END_STREAM flag. 298 | Flow.prototype.getLastQueuedFrame = function getLastQueuedFrame() { 299 | var readableQueue = this._readableState.buffer; 300 | return this._queue[this._queue.length - 1] || readableQueue[readableQueue.length - 1]; 301 | }; 302 | 303 | // Outgoing frames - managing the window size 304 | // ------------------------------------------ 305 | 306 | // Flow control window size is manipulated using the `_increaseWindow` method. 307 | // 308 | // * Invoking it with `Infinite` means turning off flow control. Flow control cannot be enabled 309 | // again once disabled. Any attempt to re-enable flow control MUST be rejected with a 310 | // FLOW_CONTROL_ERROR error code. 311 | // * A sender MUST NOT allow a flow control window to exceed 2^31 - 1 bytes. The action taken 312 | // depends on it being a stream or the connection itself. 313 | 314 | var WINDOW_SIZE_LIMIT = Math.pow(2, 31) - 1; 315 | 316 | Flow.prototype._increaseWindow = function _increaseWindow(size) { 317 | if ((this._window === Infinity) && (size !== Infinity)) { 318 | this._log.error('Trying to increase flow control window after flow control was turned off.'); 319 | this.emit('error', 'FLOW_CONTROL_ERROR'); 320 | } else { 321 | this._log.trace({ window: this._window, by: size }, 'Increasing flow control window size.'); 322 | this._window += size; 323 | if ((this._window !== Infinity) && (this._window > WINDOW_SIZE_LIMIT)) { 324 | this._log.error('Flow control window grew too large.'); 325 | this.emit('error', 'FLOW_CONTROL_ERROR'); 326 | } else { 327 | if (size != 0) { 328 | this.emit('window_update'); 329 | } 330 | } 331 | } 332 | }; 333 | 334 | // The `_updateWindow` method gets called every time there's an incoming WINDOW_UPDATE frame. It 335 | // modifies the flow control window: 336 | // 337 | // * Flow control can be disabled for an individual stream by sending a WINDOW_UPDATE with the 338 | // END_FLOW_CONTROL flag set. The payload of a WINDOW_UPDATE frame that has the END_FLOW_CONTROL 339 | // flag set is ignored. 340 | // * A sender that receives a WINDOW_UPDATE frame updates the corresponding window by the amount 341 | // specified in the frame. 342 | Flow.prototype._updateWindow = function _updateWindow(frame) { 343 | this._increaseWindow(frame.flags.END_FLOW_CONTROL ? Infinity : frame.window_size); 344 | }; 345 | 346 | // A SETTINGS frame can alter the initial flow control window size for all current streams. When the 347 | // value of SETTINGS_INITIAL_WINDOW_SIZE changes, a receiver MUST adjust the size of all stream by 348 | // calling the `setInitialWindow` method. The window size has to be modified by the difference 349 | // between the new value and the old value. 350 | Flow.prototype.setInitialWindow = function setInitialWindow(initialWindow) { 351 | this._increaseWindow(initialWindow - this._initialWindow); 352 | this._initialWindow = initialWindow; 353 | }; 354 | -------------------------------------------------------------------------------- /lib/node-http2/lib/protocol/framer.js: -------------------------------------------------------------------------------- 1 | // The framer consists of two [Transform Stream][1] subclasses that operate in [object mode][2]: 2 | // the Serializer and the Deserializer 3 | // [1]: https://nodejs.org/api/stream.html#stream_class_stream_transform 4 | // [2]: https://nodejs.org/api/stream.html#stream_new_stream_readable_options 5 | var assert = require('assert'); 6 | 7 | var Transform = require('stream').Transform; 8 | 9 | exports.Serializer = Serializer; 10 | exports.Deserializer = Deserializer; 11 | 12 | var logData = Boolean(process.env.HTTP2_LOG_DATA); 13 | 14 | var MAX_PAYLOAD_SIZE = 16384; 15 | var WINDOW_UPDATE_PAYLOAD_SIZE = 4; 16 | 17 | // Serializer 18 | // ---------- 19 | // 20 | // Frame Objects 21 | // * * * * * * * --+--------------------------- 22 | // | | 23 | // v v Buffers 24 | // [] -----> Payload Ser. --[buffers]--> Header Ser. --> * * * * 25 | // empty adds payload adds header 26 | // array buffers buffer 27 | 28 | function Serializer(log) { 29 | this._log = log.child({ component: 'serializer' }); 30 | Transform.call(this, { objectMode: true }); 31 | } 32 | Serializer.prototype = Object.create(Transform.prototype, { constructor: { value: Serializer } }); 33 | 34 | // When there's an incoming frame object, it first generates the frame type specific part of the 35 | // frame (payload), and then then adds the header part which holds fields that are common to all 36 | // frame types (like the length of the payload). 37 | Serializer.prototype._transform = function _transform(frame, encoding, done) { 38 | this._log.trace({ frame: frame }, 'Outgoing frame'); 39 | 40 | assert(frame.type in Serializer, 'Unknown frame type: ' + frame.type); 41 | 42 | var buffers = []; 43 | Serializer[frame.type](frame, buffers); 44 | var length = Serializer.commonHeader(frame, buffers); 45 | 46 | assert(length <= MAX_PAYLOAD_SIZE, 'Frame too large!'); 47 | 48 | for (var i = 0; i < buffers.length; i++) { 49 | if (logData) { 50 | this._log.trace({ data: buffers[i] }, 'Outgoing data'); 51 | } 52 | this.push(buffers[i]); 53 | } 54 | 55 | done(); 56 | }; 57 | 58 | // Deserializer 59 | // ------------ 60 | // 61 | // Buffers 62 | // * * * * --------+------------------------- 63 | // | | 64 | // v v Frame Objects 65 | // {} -----> Header Des. --{frame}--> Payload Des. --> * * * * * * * 66 | // empty adds parsed adds parsed 67 | // object header properties payload properties 68 | 69 | function Deserializer(log, role) { 70 | this._role = role; 71 | this._log = log.child({ component: 'deserializer' }); 72 | Transform.call(this, { objectMode: true }); 73 | this._next(COMMON_HEADER_SIZE); 74 | } 75 | Deserializer.prototype = Object.create(Transform.prototype, { constructor: { value: Deserializer } }); 76 | 77 | // The Deserializer is stateful, and it's two main alternating states are: *waiting for header* and 78 | // *waiting for payload*. The state is stored in the boolean property `_waitingForHeader`. 79 | // 80 | // When entering a new state, a `_buffer` is created that will hold the accumulated data (header or 81 | // payload). The `_cursor` is used to track the progress. 82 | Deserializer.prototype._next = function(size) { 83 | this._cursor = 0; 84 | this._buffer = new Buffer(size); 85 | this._waitingForHeader = !this._waitingForHeader; 86 | if (this._waitingForHeader) { 87 | this._frame = {}; 88 | } 89 | }; 90 | 91 | // Parsing an incoming buffer is an iterative process because it can hold multiple frames if it's 92 | // large enough. A `cursor` is used to track the progress in parsing the incoming `chunk`. 93 | Deserializer.prototype._transform = function _transform(chunk, encoding, done) { 94 | var cursor = 0; 95 | 96 | if (logData) { 97 | this._log.trace({ data: chunk }, 'Incoming data'); 98 | } 99 | 100 | while(cursor < chunk.length) { 101 | // The content of an incoming buffer is first copied to `_buffer`. If it can't hold the full 102 | // chunk, then only a part of it is copied. 103 | var toCopy = Math.min(chunk.length - cursor, this._buffer.length - this._cursor); 104 | chunk.copy(this._buffer, this._cursor, cursor, cursor + toCopy); 105 | this._cursor += toCopy; 106 | cursor += toCopy; 107 | 108 | // When `_buffer` is full, it's content gets parsed either as header or payload depending on 109 | // the actual state. 110 | 111 | // If it's header then the parsed data is stored in a temporary variable and then the 112 | // deserializer waits for the specified length payload. 113 | if ((this._cursor === this._buffer.length) && this._waitingForHeader) { 114 | var payloadSize = Deserializer.commonHeader(this._buffer, this._frame); 115 | if (payloadSize <= MAX_PAYLOAD_SIZE) { 116 | this._next(payloadSize); 117 | } else { 118 | this.emit('error', 'FRAME_SIZE_ERROR'); 119 | return; 120 | } 121 | } 122 | 123 | // If it's payload then the the frame object is finalized and then gets pushed out. 124 | // Unknown frame types are ignored. 125 | // 126 | // Note: If we just finished the parsing of a header and the payload length is 0, this branch 127 | // will also run. 128 | if ((this._cursor === this._buffer.length) && !this._waitingForHeader) { 129 | if (this._frame.type) { 130 | var error = Deserializer[this._frame.type](this._buffer, this._frame, this._role); 131 | if (error) { 132 | this._log.error('Incoming frame parsing error: ' + error); 133 | this.emit('error', error); 134 | } else { 135 | this._log.trace({ frame: this._frame }, 'Incoming frame'); 136 | this.push(this._frame); 137 | } 138 | } else { 139 | this._log.error('Unknown type incoming frame'); 140 | // Ignore it other than logging 141 | } 142 | this._next(COMMON_HEADER_SIZE); 143 | } 144 | } 145 | 146 | done(); 147 | }; 148 | 149 | // [Frame Header](https://tools.ietf.org/html/rfc7540#section-4.1) 150 | // -------------------------------------------------------------- 151 | // 152 | // HTTP/2 frames share a common base format consisting of a 9-byte header followed by 0 to 2^24 - 1 153 | // bytes of data. 154 | // 155 | // Additional size limits can be set by specific application uses. HTTP limits the frame size to 156 | // 16,384 octets by default, though this can be increased by a receiver. 157 | // 158 | // 0 1 2 3 159 | // 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 160 | // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ 161 | // | Length (24) | 162 | // +---------------+---------------+---------------+ 163 | // | Type (8) | Flags (8) | 164 | // +-+-----------------------------+---------------+---------------+ 165 | // |R| Stream Identifier (31) | 166 | // +-+-------------------------------------------------------------+ 167 | // | Frame Data (0...) ... 168 | // +---------------------------------------------------------------+ 169 | // 170 | // The fields of the frame header are defined as: 171 | // 172 | // * Length: 173 | // The length of the frame data expressed as an unsigned 24-bit integer. The 9 bytes of the frame 174 | // header are not included in this value. 175 | // 176 | // * Type: 177 | // The 8-bit type of the frame. The frame type determines how the remainder of the frame header 178 | // and data are interpreted. Implementations MUST ignore unsupported and unrecognized frame types. 179 | // 180 | // * Flags: 181 | // An 8-bit field reserved for frame-type specific boolean flags. 182 | // 183 | // Flags are assigned semantics specific to the indicated frame type. Flags that have no defined 184 | // semantics for a particular frame type MUST be ignored, and MUST be left unset (0) when sending. 185 | // 186 | // * R: 187 | // A reserved 1-bit field. The semantics of this bit are undefined and the bit MUST remain unset 188 | // (0) when sending and MUST be ignored when receiving. 189 | // 190 | // * Stream Identifier: 191 | // A 31-bit stream identifier. The value 0 is reserved for frames that are associated with the 192 | // connection as a whole as opposed to an individual stream. 193 | // 194 | // The structure and content of the remaining frame data is dependent entirely on the frame type. 195 | 196 | var COMMON_HEADER_SIZE = 9; 197 | 198 | var frameTypes = []; 199 | 200 | var frameFlags = {}; 201 | 202 | var genericAttributes = ['type', 'flags', 'stream']; 203 | 204 | var typeSpecificAttributes = {}; 205 | 206 | Serializer.commonHeader = function writeCommonHeader(frame, buffers) { 207 | var headerBuffer = new Buffer(COMMON_HEADER_SIZE); 208 | 209 | var size = 0; 210 | for (var i = 0; i < buffers.length; i++) { 211 | size += buffers[i].length; 212 | } 213 | headerBuffer.writeUInt8(0, 0); 214 | headerBuffer.writeUInt16BE(size, 1); 215 | 216 | var typeId = frameTypes.indexOf(frame.type); // If we are here then the type is valid for sure 217 | headerBuffer.writeUInt8(typeId, 3); 218 | 219 | var flagByte = 0; 220 | for (var flag in frame.flags) { 221 | var position = frameFlags[frame.type].indexOf(flag); 222 | assert(position !== -1, 'Unknown flag for frame type ' + frame.type + ': ' + flag); 223 | if (frame.flags[flag]) { 224 | flagByte |= (1 << position); 225 | } 226 | } 227 | headerBuffer.writeUInt8(flagByte, 4); 228 | 229 | assert((0 <= frame.stream) && (frame.stream < 0x7fffffff), frame.stream); 230 | headerBuffer.writeUInt32BE(frame.stream || 0, 5); 231 | 232 | buffers.unshift(headerBuffer); 233 | 234 | return size; 235 | }; 236 | 237 | Deserializer.commonHeader = function readCommonHeader(buffer, frame) { 238 | if (buffer.length < 9) { 239 | return 'FRAME_SIZE_ERROR'; 240 | } 241 | 242 | var totallyWastedByte = buffer.readUInt8(0); 243 | var length = buffer.readUInt16BE(1); 244 | // We do this just for sanity checking later on, to make sure no one sent us a 245 | // frame that's super large. 246 | length += totallyWastedByte << 16; 247 | 248 | frame.type = frameTypes[buffer.readUInt8(3)]; 249 | if (!frame.type) { 250 | // We are required to ignore unknown frame types 251 | return length; 252 | } 253 | 254 | frame.flags = {}; 255 | var flagByte = buffer.readUInt8(4); 256 | var definedFlags = frameFlags[frame.type]; 257 | for (var i = 0; i < definedFlags.length; i++) { 258 | frame.flags[definedFlags[i]] = Boolean(flagByte & (1 << i)); 259 | } 260 | 261 | frame.stream = buffer.readUInt32BE(5) & 0x7fffffff; 262 | 263 | return length; 264 | }; 265 | 266 | // Frame types 267 | // =========== 268 | 269 | // Every frame type is registered in the following places: 270 | // 271 | // * `frameTypes`: a register of frame type codes (used by `commonHeader()`) 272 | // * `frameFlags`: a register of valid flags for frame types (used by `commonHeader()`) 273 | // * `typeSpecificAttributes`: a register of frame specific frame object attributes (used by 274 | // logging code and also serves as documentation for frame objects) 275 | 276 | // [DATA Frames](https://tools.ietf.org/html/rfc7540#section-6.1) 277 | // ------------------------------------------------------------ 278 | // 279 | // DATA frames (type=0x0) convey arbitrary, variable-length sequences of octets associated with a 280 | // stream. 281 | // 282 | // The DATA frame defines the following flags: 283 | // 284 | // * END_STREAM (0x1): 285 | // Bit 1 being set indicates that this frame is the last that the endpoint will send for the 286 | // identified stream. 287 | // * PADDED (0x08): 288 | // Bit 4 being set indicates that the Pad Length field is present. 289 | 290 | frameTypes[0x0] = 'DATA'; 291 | 292 | frameFlags.DATA = ['END_STREAM', 'RESERVED2', 'RESERVED4', 'PADDED']; 293 | 294 | typeSpecificAttributes.DATA = ['data']; 295 | 296 | Serializer.DATA = function writeData(frame, buffers) { 297 | buffers.push(frame.data); 298 | }; 299 | 300 | Deserializer.DATA = function readData(buffer, frame) { 301 | var dataOffset = 0; 302 | var paddingLength = 0; 303 | if (frame.flags.PADDED) { 304 | if (buffer.length < 1) { 305 | // We must have at least one byte for padding control, but we don't. Bad peer! 306 | return 'FRAME_SIZE_ERROR'; 307 | } 308 | paddingLength = (buffer.readUInt8(dataOffset) & 0xff); 309 | dataOffset = 1; 310 | } 311 | 312 | if (paddingLength) { 313 | if (paddingLength >= (buffer.length - 1)) { 314 | // We don't have enough room for the padding advertised - bad peer! 315 | return 'FRAME_SIZE_ERROR'; 316 | } 317 | frame.data = buffer.slice(dataOffset, -1 * paddingLength); 318 | } else { 319 | frame.data = buffer.slice(dataOffset); 320 | } 321 | }; 322 | 323 | // [HEADERS](https://tools.ietf.org/html/rfc7540#section-6.2) 324 | // -------------------------------------------------------------- 325 | // 326 | // The HEADERS frame (type=0x1) allows the sender to create a stream. 327 | // 328 | // The HEADERS frame defines the following flags: 329 | // 330 | // * END_STREAM (0x1): 331 | // Bit 1 being set indicates that this frame is the last that the endpoint will send for the 332 | // identified stream. 333 | // * END_HEADERS (0x4): 334 | // The END_HEADERS bit indicates that this frame contains the entire payload necessary to provide 335 | // a complete set of headers. 336 | // * PADDED (0x08): 337 | // Bit 4 being set indicates that the Pad Length field is present. 338 | // * PRIORITY (0x20): 339 | // Bit 6 being set indicates that the Exlusive Flag (E), Stream Dependency, and Weight fields are 340 | // present. 341 | 342 | frameTypes[0x1] = 'HEADERS'; 343 | 344 | frameFlags.HEADERS = ['END_STREAM', 'RESERVED2', 'END_HEADERS', 'PADDED', 'RESERVED5', 'PRIORITY']; 345 | 346 | typeSpecificAttributes.HEADERS = ['priorityDependency', 'priorityWeight', 'exclusiveDependency', 'headers', 'data']; 347 | 348 | // 0 1 2 3 349 | // 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 350 | // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ 351 | // |Pad Length? (8)| 352 | // +-+-------------+---------------+-------------------------------+ 353 | // |E| Stream Dependency? (31) | 354 | // +-+-------------+-----------------------------------------------+ 355 | // | Weight? (8) | 356 | // +-+-------------+-----------------------------------------------+ 357 | // | Header Block Fragment (*) ... 358 | // +---------------------------------------------------------------+ 359 | // | Padding (*) ... 360 | // +---------------------------------------------------------------+ 361 | // 362 | // The payload of a HEADERS frame contains a Headers Block 363 | 364 | Serializer.HEADERS = function writeHeadersPriority(frame, buffers) { 365 | if (frame.flags.PRIORITY) { 366 | var buffer = new Buffer(5); 367 | assert((0 <= frame.priorityDependency) && (frame.priorityDependency <= 0x7fffffff), frame.priorityDependency); 368 | buffer.writeUInt32BE(frame.priorityDependency, 0); 369 | if (frame.exclusiveDependency) { 370 | buffer[0] |= 0x80; 371 | } 372 | assert((0 <= frame.priorityWeight) && (frame.priorityWeight <= 0xff), frame.priorityWeight); 373 | buffer.writeUInt8(frame.priorityWeight, 4); 374 | buffers.push(buffer); 375 | } 376 | buffers.push(frame.data); 377 | }; 378 | 379 | Deserializer.HEADERS = function readHeadersPriority(buffer, frame) { 380 | var minFrameLength = 0; 381 | if (frame.flags.PADDED) { 382 | minFrameLength += 1; 383 | } 384 | if (frame.flags.PRIORITY) { 385 | minFrameLength += 5; 386 | } 387 | if (buffer.length < minFrameLength) { 388 | // Peer didn't send enough data - bad peer! 389 | return 'FRAME_SIZE_ERROR'; 390 | } 391 | 392 | var dataOffset = 0; 393 | var paddingLength = 0; 394 | if (frame.flags.PADDED) { 395 | paddingLength = (buffer.readUInt8(dataOffset) & 0xff); 396 | dataOffset = 1; 397 | } 398 | 399 | if (frame.flags.PRIORITY) { 400 | var dependencyData = new Buffer(4); 401 | buffer.copy(dependencyData, 0, dataOffset, dataOffset + 4); 402 | dataOffset += 4; 403 | frame.exclusiveDependency = !!(dependencyData[0] & 0x80); 404 | dependencyData[0] &= 0x7f; 405 | frame.priorityDependency = dependencyData.readUInt32BE(0); 406 | frame.priorityWeight = buffer.readUInt8(dataOffset); 407 | dataOffset += 1; 408 | } 409 | 410 | if (paddingLength) { 411 | if ((buffer.length - dataOffset) < paddingLength) { 412 | // Not enough data left to satisfy the advertised padding - bad peer! 413 | return 'FRAME_SIZE_ERROR'; 414 | } 415 | frame.data = buffer.slice(dataOffset, -1 * paddingLength); 416 | } else { 417 | frame.data = buffer.slice(dataOffset); 418 | } 419 | }; 420 | 421 | // [PRIORITY](https://tools.ietf.org/html/rfc7540#section-6.3) 422 | // ------------------------------------------------------- 423 | // 424 | // The PRIORITY frame (type=0x2) specifies the sender-advised priority of a stream. 425 | // 426 | // The PRIORITY frame does not define any flags. 427 | 428 | frameTypes[0x2] = 'PRIORITY'; 429 | 430 | frameFlags.PRIORITY = []; 431 | 432 | typeSpecificAttributes.PRIORITY = ['priorityDependency', 'priorityWeight', 'exclusiveDependency']; 433 | 434 | // 0 1 2 3 435 | // 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 436 | // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ 437 | // |E| Stream Dependency? (31) | 438 | // +-+-------------+-----------------------------------------------+ 439 | // | Weight? (8) | 440 | // +-+-------------+ 441 | // 442 | // The payload of a PRIORITY frame contains an exclusive bit, a 31-bit dependency, and an 8-bit weight 443 | 444 | Serializer.PRIORITY = function writePriority(frame, buffers) { 445 | var buffer = new Buffer(5); 446 | assert((0 <= frame.priorityDependency) && (frame.priorityDependency <= 0x7fffffff), frame.priorityDependency); 447 | buffer.writeUInt32BE(frame.priorityDependency, 0); 448 | if (frame.exclusiveDependency) { 449 | buffer[0] |= 0x80; 450 | } 451 | assert((0 <= frame.priorityWeight) && (frame.priorityWeight <= 0xff), frame.priorityWeight); 452 | buffer.writeUInt8(frame.priorityWeight, 4); 453 | 454 | buffers.push(buffer); 455 | }; 456 | 457 | Deserializer.PRIORITY = function readPriority(buffer, frame) { 458 | if (buffer.length < 5) { 459 | // PRIORITY frames are 5 bytes long. Bad peer! 460 | return 'FRAME_SIZE_ERROR'; 461 | } 462 | var dependencyData = new Buffer(4); 463 | buffer.copy(dependencyData, 0, 0, 4); 464 | frame.exclusiveDependency = !!(dependencyData[0] & 0x80); 465 | dependencyData[0] &= 0x7f; 466 | frame.priorityDependency = dependencyData.readUInt32BE(0); 467 | frame.priorityWeight = buffer.readUInt8(4); 468 | }; 469 | 470 | // [RST_STREAM](https://tools.ietf.org/html/rfc7540#section-6.4) 471 | // ----------------------------------------------------------- 472 | // 473 | // The RST_STREAM frame (type=0x3) allows for abnormal termination of a stream. 474 | // 475 | // No type-flags are defined. 476 | 477 | frameTypes[0x3] = 'RST_STREAM'; 478 | 479 | frameFlags.RST_STREAM = []; 480 | 481 | typeSpecificAttributes.RST_STREAM = ['error']; 482 | 483 | // 0 1 2 3 484 | // 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 485 | // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ 486 | // | Error Code (32) | 487 | // +---------------------------------------------------------------+ 488 | // 489 | // The RST_STREAM frame contains a single unsigned, 32-bit integer identifying the error 490 | // code (see Error Codes). The error code indicates why the stream is being terminated. 491 | 492 | Serializer.RST_STREAM = function writeRstStream(frame, buffers) { 493 | var buffer = new Buffer(4); 494 | var code = errorCodes.indexOf(frame.error); 495 | assert((0 <= code) && (code <= 0xffffffff), code); 496 | buffer.writeUInt32BE(code, 0); 497 | buffers.push(buffer); 498 | }; 499 | 500 | Deserializer.RST_STREAM = function readRstStream(buffer, frame) { 501 | if (buffer.length < 4) { 502 | // RST_STREAM is 4 bytes long. Bad peer! 503 | return 'FRAME_SIZE_ERROR'; 504 | } 505 | frame.error = errorCodes[buffer.readUInt32BE(0)]; 506 | if (!frame.error) { 507 | // Unknown error codes are considered equivalent to INTERNAL_ERROR 508 | frame.error = 'INTERNAL_ERROR'; 509 | } 510 | }; 511 | 512 | // [SETTINGS](https://tools.ietf.org/html/rfc7540#section-6.5) 513 | // ------------------------------------------------------- 514 | // 515 | // The SETTINGS frame (type=0x4) conveys configuration parameters that affect how endpoints 516 | // communicate. 517 | // 518 | // The SETTINGS frame defines the following flag: 519 | 520 | // * ACK (0x1): 521 | // Bit 1 being set indicates that this frame acknowledges receipt and application of the peer's 522 | // SETTINGS frame. 523 | frameTypes[0x4] = 'SETTINGS'; 524 | 525 | frameFlags.SETTINGS = ['ACK']; 526 | 527 | typeSpecificAttributes.SETTINGS = ['settings']; 528 | 529 | // The payload of a SETTINGS frame consists of zero or more settings. Each setting consists of a 530 | // 16-bit identifier, and an unsigned 32-bit value. 531 | // 532 | // 0 1 2 3 533 | // 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 534 | // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ 535 | // | Identifier(16) | Value (32) | 536 | // +-----------------+---------------------------------------------+ 537 | // ...Value | 538 | // +---------------------------------+ 539 | // 540 | // Each setting in a SETTINGS frame replaces the existing value for that setting. Settings are 541 | // processed in the order in which they appear, and a receiver of a SETTINGS frame does not need to 542 | // maintain any state other than the current value of settings. Therefore, the value of a setting 543 | // is the last value that is seen by a receiver. This permits the inclusion of the same settings 544 | // multiple times in the same SETTINGS frame, though doing so does nothing other than waste 545 | // connection capacity. 546 | 547 | Serializer.SETTINGS = function writeSettings(frame, buffers) { 548 | var settings = [], settingsLeft = Object.keys(frame.settings); 549 | definedSettings.forEach(function(setting, id) { 550 | if (setting.name in frame.settings) { 551 | settingsLeft.splice(settingsLeft.indexOf(setting.name), 1); 552 | var value = frame.settings[setting.name]; 553 | settings.push({ id: id, value: setting.flag ? Boolean(value) : value }); 554 | } 555 | }); 556 | assert(settingsLeft.length === 0, 'Unknown settings: ' + settingsLeft.join(', ')); 557 | 558 | var buffer = new Buffer(settings.length * 6); 559 | for (var i = 0; i < settings.length; i++) { 560 | buffer.writeUInt16BE(settings[i].id & 0xffff, i*6); 561 | buffer.writeUInt32BE(settings[i].value, i*6 + 2); 562 | } 563 | 564 | buffers.push(buffer); 565 | }; 566 | 567 | Deserializer.SETTINGS = function readSettings(buffer, frame, role) { 568 | frame.settings = {}; 569 | 570 | // Receipt of a SETTINGS frame with the ACK flag set and a length 571 | // field value other than 0 MUST be treated as a connection error 572 | // (Section 5.4.1) of type FRAME_SIZE_ERROR. 573 | if(frame.flags.ACK && buffer.length != 0) { 574 | return 'FRAME_SIZE_ERROR'; 575 | } 576 | 577 | if (buffer.length % 6 !== 0) { 578 | return 'PROTOCOL_ERROR'; 579 | } 580 | for (var i = 0; i < buffer.length / 6; i++) { 581 | var id = buffer.readUInt16BE(i*6) & 0xffff; 582 | var setting = definedSettings[id]; 583 | if (setting) { 584 | if (role == 'CLIENT' && setting.name == 'SETTINGS_ENABLE_PUSH') { 585 | return 'SETTINGS frame on client got SETTINGS_ENABLE_PUSH'; 586 | } 587 | var value = buffer.readUInt32BE(i*6 + 2); 588 | frame.settings[setting.name] = setting.flag ? Boolean(value & 0x1) : value; 589 | } 590 | } 591 | }; 592 | 593 | // The following settings are defined: 594 | var definedSettings = []; 595 | 596 | // * SETTINGS_HEADER_TABLE_SIZE (1): 597 | // Allows the sender to inform the remote endpoint of the size of the header compression table 598 | // used to decode header blocks. 599 | definedSettings[1] = { name: 'SETTINGS_HEADER_TABLE_SIZE', flag: false }; 600 | 601 | // * SETTINGS_ENABLE_PUSH (2): 602 | // This setting can be use to disable server push. An endpoint MUST NOT send a PUSH_PROMISE frame 603 | // if it receives this setting set to a value of 0. The default value is 1, which indicates that 604 | // push is permitted. 605 | definedSettings[2] = { name: 'SETTINGS_ENABLE_PUSH', flag: true }; 606 | 607 | // * SETTINGS_MAX_CONCURRENT_STREAMS (3): 608 | // indicates the maximum number of concurrent streams that the sender will allow. 609 | definedSettings[3] = { name: 'SETTINGS_MAX_CONCURRENT_STREAMS', flag: false }; 610 | 611 | // * SETTINGS_INITIAL_WINDOW_SIZE (4): 612 | // indicates the sender's initial stream window size (in bytes) for new streams. 613 | definedSettings[4] = { name: 'SETTINGS_INITIAL_WINDOW_SIZE', flag: false }; 614 | 615 | // * SETTINGS_MAX_FRAME_SIZE (5): 616 | // indicates the maximum size of a frame the receiver will allow. 617 | definedSettings[5] = { name: 'SETTINGS_MAX_FRAME_SIZE', flag: false }; 618 | 619 | // [PUSH_PROMISE](https://tools.ietf.org/html/rfc7540#section-6.6) 620 | // --------------------------------------------------------------- 621 | // 622 | // The PUSH_PROMISE frame (type=0x5) is used to notify the peer endpoint in advance of streams the 623 | // sender intends to initiate. 624 | // 625 | // The PUSH_PROMISE frame defines the following flags: 626 | // 627 | // * END_PUSH_PROMISE (0x4): 628 | // The END_PUSH_PROMISE bit indicates that this frame contains the entire payload necessary to 629 | // provide a complete set of headers. 630 | 631 | frameTypes[0x5] = 'PUSH_PROMISE'; 632 | 633 | frameFlags.PUSH_PROMISE = ['RESERVED1', 'RESERVED2', 'END_PUSH_PROMISE', 'PADDED']; 634 | 635 | typeSpecificAttributes.PUSH_PROMISE = ['promised_stream', 'headers', 'data']; 636 | 637 | // 0 1 2 3 638 | // 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 639 | // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ 640 | // |Pad Length? (8)| 641 | // +-+-------------+-----------------------------------------------+ 642 | // |X| Promised-Stream-ID (31) | 643 | // +-+-------------------------------------------------------------+ 644 | // | Header Block Fragment (*) ... 645 | // +---------------------------------------------------------------+ 646 | // | Padding (*) ... 647 | // +---------------------------------------------------------------+ 648 | // 649 | // The PUSH_PROMISE frame includes the unsigned 31-bit identifier of 650 | // the stream the endpoint plans to create along with a minimal set of headers that provide 651 | // additional context for the stream. 652 | 653 | Serializer.PUSH_PROMISE = function writePushPromise(frame, buffers) { 654 | var buffer = new Buffer(4); 655 | 656 | var promised_stream = frame.promised_stream; 657 | assert((0 <= promised_stream) && (promised_stream <= 0x7fffffff), promised_stream); 658 | buffer.writeUInt32BE(promised_stream, 0); 659 | 660 | buffers.push(buffer); 661 | buffers.push(frame.data); 662 | }; 663 | 664 | Deserializer.PUSH_PROMISE = function readPushPromise(buffer, frame) { 665 | if (buffer.length < 4) { 666 | return 'FRAME_SIZE_ERROR'; 667 | } 668 | var dataOffset = 0; 669 | var paddingLength = 0; 670 | if (frame.flags.PADDED) { 671 | if (buffer.length < 5) { 672 | return 'FRAME_SIZE_ERROR'; 673 | } 674 | paddingLength = (buffer.readUInt8(dataOffset) & 0xff); 675 | dataOffset = 1; 676 | } 677 | frame.promised_stream = buffer.readUInt32BE(dataOffset) & 0x7fffffff; 678 | dataOffset += 4; 679 | if (paddingLength) { 680 | if ((buffer.length - dataOffset) < paddingLength) { 681 | return 'FRAME_SIZE_ERROR'; 682 | } 683 | frame.data = buffer.slice(dataOffset, -1 * paddingLength); 684 | } else { 685 | frame.data = buffer.slice(dataOffset); 686 | } 687 | }; 688 | 689 | // [PING](https://tools.ietf.org/html/rfc7540#section-6.7) 690 | // ----------------------------------------------- 691 | // 692 | // The PING frame (type=0x6) is a mechanism for measuring a minimal round-trip time from the 693 | // sender, as well as determining whether an idle connection is still functional. 694 | // 695 | // The PING frame defines one type-specific flag: 696 | // 697 | // * ACK (0x1): 698 | // Bit 1 being set indicates that this PING frame is a PING response. 699 | 700 | frameTypes[0x6] = 'PING'; 701 | 702 | frameFlags.PING = ['ACK']; 703 | 704 | typeSpecificAttributes.PING = ['data']; 705 | 706 | // In addition to the frame header, PING frames MUST contain 8 additional octets of opaque data. 707 | 708 | Serializer.PING = function writePing(frame, buffers) { 709 | buffers.push(frame.data); 710 | }; 711 | 712 | Deserializer.PING = function readPing(buffer, frame) { 713 | if (buffer.length !== 8) { 714 | return 'FRAME_SIZE_ERROR'; 715 | } 716 | frame.data = buffer; 717 | }; 718 | 719 | // [GOAWAY](https://tools.ietf.org/html/rfc7540#section-6.8) 720 | // --------------------------------------------------- 721 | // 722 | // The GOAWAY frame (type=0x7) informs the remote peer to stop creating streams on this connection. 723 | // 724 | // The GOAWAY frame does not define any flags. 725 | 726 | frameTypes[0x7] = 'GOAWAY'; 727 | 728 | frameFlags.GOAWAY = []; 729 | 730 | typeSpecificAttributes.GOAWAY = ['last_stream', 'error']; 731 | 732 | // 0 1 2 3 733 | // 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 734 | // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ 735 | // |X| Last-Stream-ID (31) | 736 | // +-+-------------------------------------------------------------+ 737 | // | Error Code (32) | 738 | // +---------------------------------------------------------------+ 739 | // 740 | // The last stream identifier in the GOAWAY frame contains the highest numbered stream identifier 741 | // for which the sender of the GOAWAY frame has received frames on and might have taken some action 742 | // on. 743 | // 744 | // The GOAWAY frame also contains a 32-bit error code (see Error Codes) that contains the reason for 745 | // closing the connection. 746 | 747 | Serializer.GOAWAY = function writeGoaway(frame, buffers) { 748 | var buffer = new Buffer(8); 749 | 750 | var last_stream = frame.last_stream; 751 | assert((0 <= last_stream) && (last_stream <= 0x7fffffff), last_stream); 752 | buffer.writeUInt32BE(last_stream, 0); 753 | 754 | var code = errorCodes.indexOf(frame.error); 755 | assert((0 <= code) && (code <= 0xffffffff), code); 756 | buffer.writeUInt32BE(code, 4); 757 | 758 | buffers.push(buffer); 759 | }; 760 | 761 | Deserializer.GOAWAY = function readGoaway(buffer, frame) { 762 | if (buffer.length !== 8) { 763 | // GOAWAY must have 8 bytes 764 | return 'FRAME_SIZE_ERROR'; 765 | } 766 | frame.last_stream = buffer.readUInt32BE(0) & 0x7fffffff; 767 | frame.error = errorCodes[buffer.readUInt32BE(4)]; 768 | if (!frame.error) { 769 | // Unknown error types are to be considered equivalent to INTERNAL ERROR 770 | frame.error = 'INTERNAL_ERROR'; 771 | } 772 | }; 773 | 774 | // [WINDOW_UPDATE](https://tools.ietf.org/html/rfc7540#section-6.9) 775 | // ----------------------------------------------------------------- 776 | // 777 | // The WINDOW_UPDATE frame (type=0x8) is used to implement flow control. 778 | // 779 | // The WINDOW_UPDATE frame does not define any flags. 780 | 781 | frameTypes[0x8] = 'WINDOW_UPDATE'; 782 | 783 | frameFlags.WINDOW_UPDATE = []; 784 | 785 | typeSpecificAttributes.WINDOW_UPDATE = ['window_size']; 786 | 787 | // The payload of a WINDOW_UPDATE frame is a 32-bit value indicating the additional number of bytes 788 | // that the sender can transmit in addition to the existing flow control window. The legal range 789 | // for this field is 1 to 2^31 - 1 (0x7fffffff) bytes; the most significant bit of this value is 790 | // reserved. 791 | 792 | Serializer.WINDOW_UPDATE = function writeWindowUpdate(frame, buffers) { 793 | var buffer = new Buffer(4); 794 | 795 | var window_size = frame.window_size; 796 | assert((0 < window_size) && (window_size <= 0x7fffffff), window_size); 797 | buffer.writeUInt32BE(window_size, 0); 798 | 799 | buffers.push(buffer); 800 | }; 801 | 802 | Deserializer.WINDOW_UPDATE = function readWindowUpdate(buffer, frame) { 803 | if (buffer.length !== WINDOW_UPDATE_PAYLOAD_SIZE) { 804 | return 'FRAME_SIZE_ERROR'; 805 | } 806 | frame.window_size = buffer.readUInt32BE(0) & 0x7fffffff; 807 | if (frame.window_size === 0) { 808 | return 'PROTOCOL_ERROR'; 809 | } 810 | }; 811 | 812 | // [CONTINUATION](https://tools.ietf.org/html/rfc7540#section-6.10) 813 | // ------------------------------------------------------------ 814 | // 815 | // The CONTINUATION frame (type=0x9) is used to continue a sequence of header block fragments. 816 | // 817 | // The CONTINUATION frame defines the following flag: 818 | // 819 | // * END_HEADERS (0x4): 820 | // The END_HEADERS bit indicates that this frame ends the sequence of header block fragments 821 | // necessary to provide a complete set of headers. 822 | 823 | frameTypes[0x9] = 'CONTINUATION'; 824 | 825 | frameFlags.CONTINUATION = ['RESERVED1', 'RESERVED2', 'END_HEADERS']; 826 | 827 | typeSpecificAttributes.CONTINUATION = ['headers', 'data']; 828 | 829 | Serializer.CONTINUATION = function writeContinuation(frame, buffers) { 830 | buffers.push(frame.data); 831 | }; 832 | 833 | Deserializer.CONTINUATION = function readContinuation(buffer, frame) { 834 | frame.data = buffer; 835 | }; 836 | 837 | // [ALTSVC](https://tools.ietf.org/html/rfc7838#section-4) 838 | // ------------------------------------------------------------ 839 | // 840 | // The ALTSVC frame (type=0xA) advertises the availability of an alternative service to the client. 841 | // 842 | // The ALTSVC frame does not define any flags. 843 | 844 | frameTypes[0xA] = 'ALTSVC'; 845 | 846 | frameFlags.ALTSVC = []; 847 | 848 | // 0 1 2 3 849 | // 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 850 | // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ 851 | // | Origin-Len (16) | Origin? (*) ... 852 | // +-------------------------------+----------------+--------------+ 853 | // | Alt-Svc-Field-Value (*) ... 854 | // +---------------------------------------------------------------+ 855 | // 856 | // The ALTSVC frame contains the following fields: 857 | // 858 | // Origin-Len: An unsigned, 16-bit integer indicating the length, in 859 | // octets, of the Origin field. 860 | // 861 | // Origin: An OPTIONAL sequence of characters containing ASCII 862 | // serialisation of an origin ([RFC6454](https://tools.ietf.org/html/rfc6454), 863 | // Section 6.2) that the alternate service is applicable to. 864 | // 865 | // Alt-Svc-Field-Value: A sequence of octets (length determined by 866 | // subtracting the length of all preceding fields from the frame 867 | // length) containing a value identical to the Alt-Svc field value 868 | // defined in (Section 3)[https://tools.ietf.org/html/rfc7838#section-3] 869 | // (ABNF production "Alt-Svc"). 870 | 871 | typeSpecificAttributes.ALTSVC = ['maxAge', 'port', 'protocolID', 'host', 872 | 'origin']; 873 | 874 | function istchar(c) { 875 | return ('!#$&\'*+-.^_`|~1234567890ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz'.indexOf(c) > -1); 876 | } 877 | 878 | function hexencode(s) { 879 | var t = ''; 880 | for (var i = 0; i < s.length; i++) { 881 | if (!istchar(s[i])) { 882 | t += '%'; 883 | t += new Buffer(s[i]).toString('hex'); 884 | } else { 885 | t += s[i]; 886 | } 887 | } 888 | return t; 889 | } 890 | 891 | Serializer.ALTSVC = function writeAltSvc(frame, buffers) { 892 | var buffer = new Buffer(2); 893 | buffer.writeUInt16BE(frame.origin.length, 0); 894 | buffers.push(buffer); 895 | buffers.push(new Buffer(frame.origin, 'ascii')); 896 | 897 | var fieldValue = hexencode(frame.protocolID) + '="' + frame.host + ':' + frame.port + '"'; 898 | if (frame.maxAge !== 86400) { // 86400 is the default 899 | fieldValue += "; ma=" + frame.maxAge; 900 | } 901 | 902 | buffers.push(new Buffer(fieldValue, 'ascii')); 903 | }; 904 | 905 | function stripquotes(s) { 906 | var start = 0; 907 | var end = s.length; 908 | while ((start < end) && (s[start] === '"')) { 909 | start++; 910 | } 911 | while ((end > start) && (s[end - 1] === '"')) { 912 | end--; 913 | } 914 | if (start >= end) { 915 | return ""; 916 | } 917 | return s.substring(start, end); 918 | } 919 | 920 | function splitNameValue(nvpair) { 921 | var eq = -1; 922 | var inQuotes = false; 923 | 924 | for (var i = 0; i < nvpair.length; i++) { 925 | if (nvpair[i] === '"') { 926 | inQuotes = !inQuotes; 927 | continue; 928 | } 929 | if (inQuotes) { 930 | continue; 931 | } 932 | if (nvpair[i] === '=') { 933 | eq = i; 934 | break; 935 | } 936 | } 937 | 938 | if (eq === -1) { 939 | return {'name': nvpair, 'value': null}; 940 | } 941 | 942 | var name = stripquotes(nvpair.substring(0, eq).trim()); 943 | var value = stripquotes(nvpair.substring(eq + 1).trim()); 944 | return {'name': name, 'value': value}; 945 | } 946 | 947 | function splitHeaderParameters(hv) { 948 | return parseHeaderValue(hv, ';', splitNameValue); 949 | } 950 | 951 | function parseHeaderValue(hv, separator, callback) { 952 | var start = 0; 953 | var inQuotes = false; 954 | var values = []; 955 | 956 | for (var i = 0; i < hv.length; i++) { 957 | if (hv[i] === '"') { 958 | inQuotes = !inQuotes; 959 | continue; 960 | } 961 | if (inQuotes) { 962 | // Just skip this 963 | continue; 964 | } 965 | if (hv[i] === separator) { 966 | var newValue = hv.substring(start, i).trim(); 967 | if (newValue.length > 0) { 968 | newValue = callback(newValue); 969 | values.push(newValue); 970 | } 971 | start = i + 1; 972 | } 973 | } 974 | 975 | var newValue = hv.substring(start).trim(); 976 | if (newValue.length > 0) { 977 | newValue = callback(newValue); 978 | values.push(newValue); 979 | } 980 | 981 | return values; 982 | } 983 | 984 | function rsplit(s, delim, count) { 985 | var nsplits = 0; 986 | var end = s.length; 987 | var rval = []; 988 | for (var i = s.length - 1; i >= 0; i--) { 989 | if (s[i] === delim) { 990 | var t = s.substring(i + 1, end); 991 | end = i; 992 | rval.unshift(t); 993 | nsplits++; 994 | if (nsplits === count) { 995 | break; 996 | } 997 | } 998 | } 999 | if (end !== 0) { 1000 | rval.unshift(s.substring(0, end)); 1001 | } 1002 | return rval; 1003 | } 1004 | 1005 | function ishex(c) { 1006 | return ('0123456789ABCDEFabcdef'.indexOf(c) > -1); 1007 | } 1008 | 1009 | function unescape(s) { 1010 | var i = 0; 1011 | var t = ''; 1012 | while (i < s.length) { 1013 | if (s[i] != '%' || !ishex(s[i + 1]) || !ishex(s[i + 2])) { 1014 | t += s[i]; 1015 | } else { 1016 | ++i; 1017 | var hexvalue = ''; 1018 | if (i < s.length) { 1019 | hexvalue += s[i]; 1020 | ++i; 1021 | } 1022 | if (i < s.length) { 1023 | hexvalue += s[i]; 1024 | } 1025 | if (hexvalue.length > 0) { 1026 | t += new Buffer(hexvalue, 'hex').toString(); 1027 | } else { 1028 | t += '%'; 1029 | } 1030 | } 1031 | 1032 | ++i; 1033 | } 1034 | return t; 1035 | } 1036 | 1037 | Deserializer.ALTSVC = function readAltSvc(buffer, frame) { 1038 | if (buffer.length < 2) { 1039 | return 'FRAME_SIZE_ERROR'; 1040 | } 1041 | var originLength = buffer.readUInt16BE(0); 1042 | if ((buffer.length - 2) < originLength) { 1043 | return 'FRAME_SIZE_ERROR'; 1044 | } 1045 | frame.origin = buffer.toString('ascii', 2, 2 + originLength); 1046 | var fieldValue = buffer.toString('ascii', 2 + originLength); 1047 | var values = parseHeaderValue(fieldValue, ',', splitHeaderParameters); 1048 | if (values.length > 1) { 1049 | // TODO - warn that we only use one here 1050 | } 1051 | if (values.length === 0) { 1052 | // Well that's a malformed frame. Just ignore it. 1053 | return; 1054 | } 1055 | 1056 | var chosenAltSvc = values[0]; 1057 | frame.maxAge = 86400; // Default 1058 | for (var i = 0; i < chosenAltSvc.length; i++) { 1059 | if (i === 0) { 1060 | // This corresponds to the protocolID=":" item 1061 | frame.protocolID = unescape(chosenAltSvc[i].name); 1062 | var hostport = rsplit(chosenAltSvc[i].value, ':', 1); 1063 | frame.host = hostport[0]; 1064 | frame.port = parseInt(hostport[1], 10); 1065 | } else if (chosenAltSvc[i].name == 'ma') { 1066 | frame.maxAge = parseInt(chosenAltSvc[i].value, 10); 1067 | } 1068 | // Otherwise, we just ignore this 1069 | } 1070 | }; 1071 | 1072 | // BLOCKED 1073 | // ------------------------------------------------------------ 1074 | // 1075 | // The BLOCKED frame (type=0xB) indicates that the sender is unable to send data 1076 | // due to a closed flow control window. 1077 | // 1078 | // The BLOCKED frame does not define any flags and contains no payload. 1079 | 1080 | frameTypes[0xB] = 'BLOCKED'; 1081 | 1082 | frameFlags.BLOCKED = []; 1083 | 1084 | typeSpecificAttributes.BLOCKED = []; 1085 | 1086 | Serializer.BLOCKED = function writeBlocked(frame, buffers) { 1087 | }; 1088 | 1089 | Deserializer.BLOCKED = function readBlocked(buffer, frame) { 1090 | }; 1091 | 1092 | // [Error Codes](https://tools.ietf.org/html/rfc7540#section-7) 1093 | // ------------------------------------------------------------ 1094 | 1095 | var errorCodes = [ 1096 | 'NO_ERROR', 1097 | 'PROTOCOL_ERROR', 1098 | 'INTERNAL_ERROR', 1099 | 'FLOW_CONTROL_ERROR', 1100 | 'SETTINGS_TIMEOUT', 1101 | 'STREAM_CLOSED', 1102 | 'FRAME_SIZE_ERROR', 1103 | 'REFUSED_STREAM', 1104 | 'CANCEL', 1105 | 'COMPRESSION_ERROR', 1106 | 'CONNECT_ERROR', 1107 | 'ENHANCE_YOUR_CALM', 1108 | 'INADEQUATE_SECURITY', 1109 | 'HTTP_1_1_REQUIRED' 1110 | ]; 1111 | 1112 | // Logging 1113 | // ------- 1114 | 1115 | // [Bunyan serializers](https://github.com/trentm/node-bunyan#serializers) to improve logging output 1116 | // for debug messages emitted in this component. 1117 | exports.serializers = {}; 1118 | 1119 | // * `frame` serializer: it transforms data attributes from Buffers to hex strings and filters out 1120 | // flags that are not present. 1121 | var frameCounter = 0; 1122 | exports.serializers.frame = function(frame) { 1123 | if (!frame) { 1124 | return null; 1125 | } 1126 | 1127 | if ('id' in frame) { 1128 | return frame.id; 1129 | } 1130 | 1131 | frame.id = frameCounter; 1132 | frameCounter += 1; 1133 | 1134 | var logEntry = { id: frame.id }; 1135 | genericAttributes.concat(typeSpecificAttributes[frame.type]).forEach(function(name) { 1136 | logEntry[name] = frame[name]; 1137 | }); 1138 | 1139 | if (frame.data instanceof Buffer) { 1140 | if (logEntry.data.length > 50) { 1141 | logEntry.data = frame.data.slice(0, 47).toString('hex') + '...'; 1142 | } else { 1143 | logEntry.data = frame.data.toString('hex'); 1144 | } 1145 | 1146 | if (!('length' in logEntry)) { 1147 | logEntry.length = frame.data.length; 1148 | } 1149 | } 1150 | 1151 | if (frame.promised_stream instanceof Object) { 1152 | logEntry.promised_stream = 'stream-' + frame.promised_stream.id; 1153 | } 1154 | 1155 | logEntry.flags = Object.keys(frame.flags || {}).filter(function(name) { 1156 | return frame.flags[name] === true; 1157 | }); 1158 | 1159 | return logEntry; 1160 | }; 1161 | 1162 | // * `data` serializer: it simply transforms a buffer to a hex string. 1163 | exports.serializers.data = function(data) { 1164 | return data.toString('hex'); 1165 | }; 1166 | -------------------------------------------------------------------------------- /lib/node-http2/lib/protocol/index.js: -------------------------------------------------------------------------------- 1 | // This is an implementation of the [HTTP/2][http2] 2 | // framing layer for [node.js][node]. 3 | // 4 | // The main building blocks are [node.js streams][node-stream] that are connected through pipes. 5 | // 6 | // The main components are: 7 | // 8 | // * [Endpoint](endpoint.html): represents an HTTP/2 endpoint (client or server). It's 9 | // responsible for the the first part of the handshake process (sending/receiving the 10 | // [connection header][http2-connheader]) and manages other components (framer, compressor, 11 | // connection, streams) that make up a client or server. 12 | // 13 | // * [Connection](connection.html): multiplexes the active HTTP/2 streams, manages connection 14 | // lifecycle and settings, and responsible for enforcing the connection level limits (flow 15 | // control, initiated stream limit) 16 | // 17 | // * [Stream](stream.html): implementation of the [HTTP/2 stream concept][http2-stream]. 18 | // Implements the [stream state machine][http2-streamstate] defined by the standard, provides 19 | // management methods and events for using the stream (sending/receiving headers, data, etc.), 20 | // and enforces stream level constraints (flow control, sending only legal frames). 21 | // 22 | // * [Flow](flow.html): implements flow control for Connection and Stream as parent class. 23 | // 24 | // * [Compressor and Decompressor](compressor.html): compression and decompression of HEADER and 25 | // PUSH_PROMISE frames 26 | // 27 | // * [Serializer and Deserializer](framer.html): the lowest layer in the stack that transforms 28 | // between the binary and the JavaScript object representation of HTTP/2 frames 29 | // 30 | // [http2]: https://tools.ietf.org/html/rfc7540 31 | // [http2-connheader]: https://tools.ietf.org/html/rfc7540#section-3.5 32 | // [http2-stream]: https://tools.ietf.org/html/rfc7540#section-5 33 | // [http2-streamstate]: https://tools.ietf.org/html/rfc7540#section-5.1 34 | // [node]: https://nodejs.org/ 35 | // [node-stream]: https://nodejs.org/api/stream.html 36 | // [node-https]: https://nodejs.org/api/https.html 37 | // [node-http]: https://nodejs.org/api/http.html 38 | 39 | exports.VERSION = 'h2'; 40 | 41 | exports.Endpoint = require('./endpoint').Endpoint; 42 | 43 | /* Bunyan serializers exported by submodules that are worth adding when creating a logger. */ 44 | exports.serializers = {}; 45 | var modules = ['./framer', './compressor', './flow', './connection', './stream', './endpoint']; 46 | modules.map(require).forEach(function(module) { 47 | for (var name in module.serializers) { 48 | exports.serializers[name] = module.serializers[name]; 49 | } 50 | }); 51 | 52 | /* 53 | Stream API Endpoint API 54 | Stream data 55 | 56 | | ^ | ^ 57 | | | | | 58 | | | | | 59 | +-----------|------------|---------------------------------------+ 60 | | | | Endpoint | 61 | | | | | 62 | | +-------|------------|-----------------------------------+ | 63 | | | | | Connection | | 64 | | | v | | | 65 | | | +-----------------------+ +-------------------- | | 66 | | | | Stream | | Stream ... | | 67 | | | +-----------------------+ +-------------------- | | 68 | | | | ^ | ^ | | 69 | | | v | v | | | 70 | | | +------------+--+--------+--+------------+- ... | | 71 | | | | ^ | | 72 | | | | | | | 73 | | +-----------------------|--------|-----------------------+ | 74 | | | | | 75 | | v | | 76 | | +--------------------------+ +--------------------------+ | 77 | | | Compressor | | Decompressor | | 78 | | +--------------------------+ +--------------------------+ | 79 | | | ^ | 80 | | v | | 81 | | +--------------------------+ +--------------------------+ | 82 | | | Serializer | | Deserializer | | 83 | | +--------------------------+ +--------------------------+ | 84 | | | ^ | 85 | +---------------------------|--------|---------------------------+ 86 | | | 87 | v | 88 | 89 | Raw data 90 | 91 | */ 92 | -------------------------------------------------------------------------------- /lib/node-http2/lib/protocol/stream.js: -------------------------------------------------------------------------------- 1 | var assert = require('assert'); 2 | 3 | // The Stream class 4 | // ================ 5 | 6 | // Stream is a [Duplex stream](https://nodejs.org/api/stream.html#stream_class_stream_duplex) 7 | // subclass that implements the [HTTP/2 Stream](https://tools.ietf.org/html/rfc7540#section-5) 8 | // concept. It has two 'sides': one that is used by the user to send/receive data (the `stream` 9 | // object itself) and one that is used by a Connection to read/write frames to/from the other peer 10 | // (`stream.upstream`). 11 | 12 | var Duplex = require('stream').Duplex; 13 | 14 | exports.Stream = Stream; 15 | 16 | // Public API 17 | // ---------- 18 | 19 | // * **new Stream(log, connection)**: create a new Stream 20 | // 21 | // * **Event: 'headers' (headers)**: signals incoming headers 22 | // 23 | // * **Event: 'promise' (stream, headers)**: signals an incoming push promise 24 | // 25 | // * **Event: 'priority' (priority)**: signals a priority change. `priority` is a number between 0 26 | // (highest priority) and 2^31-1 (lowest priority). Default value is 2^30. 27 | // 28 | // * **Event: 'error' (type)**: signals an error 29 | // 30 | // * **headers(headers)**: send headers 31 | // 32 | // * **promise(headers): Stream**: promise a stream 33 | // 34 | // * **priority(priority)**: set the priority of the stream. Priority can be changed by the peer 35 | // too, but once it is set locally, it can not be changed remotely. 36 | // 37 | // * **reset(error)**: reset the stream with an error code 38 | // 39 | // * **upstream**: a [Flow](flow.js) that is used by the parent connection to write/read frames 40 | // that are to be sent/arrived to/from the peer and are related to this stream. 41 | // 42 | // Headers are always in the [regular node.js header format][1]. 43 | // [1]: https://nodejs.org/api/http.html#http_message_headers 44 | 45 | // Constructor 46 | // ----------- 47 | 48 | // The main aspects of managing the stream are: 49 | function Stream(log, connection) { 50 | Duplex.call(this); 51 | 52 | // * logging 53 | this._log = log.child({ component: 'stream', s: this }); 54 | 55 | // * receiving and sending stream management commands 56 | this._initializeManagement(); 57 | 58 | // * sending and receiving frames to/from the upstream connection 59 | this._initializeDataFlow(); 60 | 61 | // * maintaining the state of the stream (idle, open, closed, etc.) and error detection 62 | this._initializeState(); 63 | 64 | this.connection = connection; 65 | } 66 | 67 | Stream.prototype = Object.create(Duplex.prototype, { constructor: { value: Stream } }); 68 | 69 | // Managing the stream 70 | // ------------------- 71 | 72 | // the default stream priority is 2^30 73 | var DEFAULT_PRIORITY = Math.pow(2, 30); 74 | var MAX_PRIORITY = Math.pow(2, 31) - 1; 75 | 76 | // PUSH_PROMISE and HEADERS are forwarded to the user through events. 77 | Stream.prototype._initializeManagement = function _initializeManagement() { 78 | this._resetSent = false; 79 | this._priority = DEFAULT_PRIORITY; 80 | this._letPeerPrioritize = true; 81 | }; 82 | 83 | Stream.prototype.promise = function promise(headers) { 84 | var stream = new Stream(this._log, this.connection); 85 | stream._priority = Math.min(this._priority + 1, MAX_PRIORITY); 86 | this._pushUpstream({ 87 | type: 'PUSH_PROMISE', 88 | flags: {}, 89 | stream: this.id, 90 | promised_stream: stream, 91 | headers: headers 92 | }); 93 | return stream; 94 | }; 95 | 96 | Stream.prototype._onPromise = function _onPromise(frame) { 97 | this.emit('promise', frame.promised_stream, frame.headers); 98 | }; 99 | 100 | Stream.prototype.headers = function headers(headers) { 101 | this._pushUpstream({ 102 | type: 'HEADERS', 103 | flags: {}, 104 | stream: this.id, 105 | headers: headers 106 | }); 107 | }; 108 | 109 | Stream.prototype._onHeaders = function _onHeaders(frame) { 110 | if (frame.priority !== undefined) { 111 | this.priority(frame.priority, true); 112 | } 113 | this.emit('headers', frame.headers); 114 | }; 115 | 116 | Stream.prototype.priority = function priority(priority, peer) { 117 | if ((peer && this._letPeerPrioritize) || !peer) { 118 | if (!peer) { 119 | this._letPeerPrioritize = false; 120 | 121 | var lastFrame = this.upstream.getLastQueuedFrame(); 122 | if (lastFrame && ((lastFrame.type === 'HEADERS') || (lastFrame.type === 'PRIORITY'))) { 123 | lastFrame.priority = priority; 124 | } else { 125 | this._pushUpstream({ 126 | type: 'PRIORITY', 127 | flags: {}, 128 | stream: this.id, 129 | priority: priority 130 | }); 131 | } 132 | } 133 | 134 | this._log.debug({ priority: priority }, 'Changing priority'); 135 | this.emit('priority', priority); 136 | this._priority = priority; 137 | } 138 | }; 139 | 140 | Stream.prototype._onPriority = function _onPriority(frame) { 141 | this.priority(frame.priority, true); 142 | }; 143 | 144 | // Resetting the stream. Normally, an endpoint SHOULD NOT send more than one RST_STREAM frame for 145 | // any stream. 146 | Stream.prototype.reset = function reset(error) { 147 | if (!this._resetSent) { 148 | this._resetSent = true; 149 | this._pushUpstream({ 150 | type: 'RST_STREAM', 151 | flags: {}, 152 | stream: this.id, 153 | error: error 154 | }); 155 | } 156 | }; 157 | 158 | // Specify an alternate service for the origin of this stream 159 | Stream.prototype.altsvc = function altsvc(host, port, protocolID, maxAge, origin) { 160 | var stream; 161 | if (origin) { 162 | stream = 0; 163 | } else { 164 | stream = this.id; 165 | } 166 | this._pushUpstream({ 167 | type: 'ALTSVC', 168 | flags: {}, 169 | stream: stream, 170 | host: host, 171 | port: port, 172 | protocolID: protocolID, 173 | origin: origin, 174 | maxAge: maxAge 175 | }); 176 | }; 177 | 178 | // Data flow 179 | // --------- 180 | 181 | // The incoming and the generated outgoing frames are received/transmitted on the `this.upstream` 182 | // [Flow](flow.html). The [Connection](connection.html) object instantiating the stream will read 183 | // and write frames to/from it. The stream itself is a regular [Duplex stream][1], and is used by 184 | // the user to write or read the body of the request. 185 | // [1]: https://nodejs.org/api/stream.html#stream_class_stream_duplex 186 | 187 | // upstream side stream user side 188 | // 189 | // +------------------------------------+ 190 | // | | 191 | // +------------------+ | 192 | // | upstream | | 193 | // | | | 194 | // +--+ | +--| 195 | // read() | | _send() | _write() | | write(buf) 196 | // <--------------|B |<--------------|--------------| B|<------------ 197 | // | | | | | 198 | // frames +--+ | +--| buffers 199 | // | | | | | 200 | // -------------->|B |---------------|------------->| B|------------> 201 | // write(frame) | | _receive() | _read() | | read() 202 | // +--+ | +--| 203 | // | | | 204 | // | | | 205 | // +------------------+ | 206 | // | | 207 | // +------------------------------------+ 208 | // 209 | // B: input or output buffer 210 | 211 | var Flow = require('./flow').Flow; 212 | 213 | Stream.prototype._initializeDataFlow = function _initializeDataFlow() { 214 | this.id = undefined; 215 | 216 | this._ended = false; 217 | 218 | this.upstream = new Flow(); 219 | this.upstream._log = this._log; 220 | this.upstream._send = this._send.bind(this); 221 | this.upstream._receive = this._receive.bind(this); 222 | this.upstream.write = this._writeUpstream.bind(this); 223 | this.upstream.on('error', this.emit.bind(this, 'error')); 224 | 225 | this.on('finish', this._finishing); 226 | }; 227 | 228 | Stream.prototype._pushUpstream = function _pushUpstream(frame) { 229 | this.upstream.push(frame); 230 | this._transition(true, frame); 231 | }; 232 | 233 | // Overriding the upstream's `write` allows us to act immediately instead of waiting for the input 234 | // queue to empty. This is important in case of control frames. 235 | Stream.prototype._writeUpstream = function _writeUpstream(frame) { 236 | this._log.debug({ frame: frame }, 'Receiving frame'); 237 | 238 | var moreNeeded = Flow.prototype.write.call(this.upstream, frame); 239 | 240 | // * Transition to a new state if that's the effect of receiving the frame 241 | this._transition(false, frame); 242 | 243 | // * If it's a control frame. Call the appropriate handler method. 244 | if (frame.type === 'HEADERS') { 245 | if (this._processedHeaders && !frame.flags['END_STREAM']) { 246 | this.emit('error', 'PROTOCOL_ERROR'); 247 | } 248 | this._processedHeaders = true; 249 | this._onHeaders(frame); 250 | } else if (frame.type === 'PUSH_PROMISE') { 251 | this._onPromise(frame); 252 | } else if (frame.type === 'PRIORITY') { 253 | this._onPriority(frame); 254 | } else if (frame.type === 'ALTSVC') { 255 | // TODO 256 | } else if (frame.type === 'BLOCKED') { 257 | // TODO 258 | } 259 | 260 | // * If it's an invalid stream level frame, emit error 261 | else if ((frame.type !== 'DATA') && 262 | (frame.type !== 'WINDOW_UPDATE') && 263 | (frame.type !== 'RST_STREAM')) { 264 | this._log.error({ frame: frame }, 'Invalid stream level frame'); 265 | this.emit('error', 'PROTOCOL_ERROR'); 266 | } 267 | 268 | return moreNeeded; 269 | }; 270 | 271 | // The `_receive` method (= `upstream._receive`) gets called when there's an incoming frame. 272 | Stream.prototype._receive = function _receive(frame, ready) { 273 | // * If it's a DATA frame, then push the payload into the output buffer on the other side. 274 | // Call ready when the other side is ready to receive more. 275 | if (!this._ended && (frame.type === 'DATA')) { 276 | var moreNeeded = this.push(frame.data); 277 | if (!moreNeeded) { 278 | this._receiveMore = ready; 279 | } 280 | } 281 | 282 | // * Any frame may signal the end of the stream with the END_STREAM flag 283 | if (!this._ended && (frame.flags.END_STREAM || (frame.type === 'RST_STREAM'))) { 284 | this.push(null); 285 | this._ended = true; 286 | } 287 | 288 | // * Postpone calling `ready` if `push()` returned a falsy value 289 | if (this._receiveMore !== ready) { 290 | ready(); 291 | } 292 | }; 293 | 294 | // The `_read` method is called when the user side is ready to receive more data. If there's a 295 | // pending write on the upstream, then call its pending ready callback to receive more frames. 296 | Stream.prototype._read = function _read() { 297 | if (this._receiveMore) { 298 | var receiveMore = this._receiveMore; 299 | delete this._receiveMore; 300 | receiveMore(); 301 | } 302 | }; 303 | 304 | // The `write` method gets called when there's a write request from the user. 305 | Stream.prototype._write = function _write(buffer, encoding, ready) { 306 | // * Chunking is done by the upstream Flow. 307 | var moreNeeded = this._pushUpstream({ 308 | type: 'DATA', 309 | flags: {}, 310 | stream: this.id, 311 | data: buffer 312 | }); 313 | 314 | // * Call ready when upstream is ready to receive more frames. 315 | if (moreNeeded) { 316 | ready(); 317 | } else { 318 | this._sendMore = ready; 319 | } 320 | }; 321 | 322 | // The `_send` (= `upstream._send`) method is called when upstream is ready to receive more frames. 323 | // If there's a pending write on the user side, then call its pending ready callback to receive more 324 | // writes. 325 | Stream.prototype._send = function _send() { 326 | if (this._sendMore) { 327 | var sendMore = this._sendMore; 328 | delete this._sendMore; 329 | sendMore(); 330 | } 331 | }; 332 | 333 | // When the stream is finishing (the user calls `end()` on it), then we have to set the `END_STREAM` 334 | // flag on the last frame. If there's no frame in the queue, or if it doesn't support this flag, 335 | // then we create a 0 length DATA frame. We could do this all the time, but putting the flag on an 336 | // existing frame is a nice optimization. 337 | var emptyBuffer = new Buffer(0); 338 | Stream.prototype._finishing = function _finishing() { 339 | var endFrame = { 340 | type: 'DATA', 341 | flags: { END_STREAM: true }, 342 | stream: this.id, 343 | data: emptyBuffer 344 | }; 345 | var lastFrame = this.upstream.getLastQueuedFrame(); 346 | if (lastFrame && ((lastFrame.type === 'DATA') || (lastFrame.type === 'HEADERS'))) { 347 | this._log.debug({ frame: lastFrame }, 'Marking last frame with END_STREAM flag.'); 348 | lastFrame.flags.END_STREAM = true; 349 | this._transition(true, endFrame); 350 | } else { 351 | this._pushUpstream(endFrame); 352 | } 353 | }; 354 | 355 | // [Stream States](https://tools.ietf.org/html/rfc7540#section-5.1) 356 | // ---------------- 357 | // 358 | // +--------+ 359 | // PP | | PP 360 | // ,--------| idle |--------. 361 | // / | | \ 362 | // v +--------+ v 363 | // +----------+ | +----------+ 364 | // | | | H | | 365 | // ,---| reserved | | | reserved |---. 366 | // | | (local) | v | (remote) | | 367 | // | +----------+ +--------+ +----------+ | 368 | // | | ES | | ES | | 369 | // | | H ,-------| open |-------. | H | 370 | // | | / | | \ | | 371 | // | v v +--------+ v v | 372 | // | +----------+ | +----------+ | 373 | // | | half | | | half | | 374 | // | | closed | | R | closed | | 375 | // | | (remote) | | | (local) | | 376 | // | +----------+ | +----------+ | 377 | // | | v | | 378 | // | | ES / R +--------+ ES / R | | 379 | // | `----------->| |<-----------' | 380 | // | R | closed | R | 381 | // `-------------------->| |<--------------------' 382 | // +--------+ 383 | 384 | // Streams begin in the IDLE state and transitions happen when there's an incoming or outgoing frame 385 | Stream.prototype._initializeState = function _initializeState() { 386 | this.state = 'IDLE'; 387 | this._initiated = undefined; 388 | this._closedByUs = undefined; 389 | this._closedWithRst = undefined; 390 | this._processedHeaders = false; 391 | }; 392 | 393 | // Only `_setState` should change `this.state` directly. It also logs the state change and notifies 394 | // interested parties using the 'state' event. 395 | Stream.prototype._setState = function transition(state) { 396 | assert(this.state !== state); 397 | this._log.debug({ from: this.state, to: state }, 'State transition'); 398 | this.state = state; 399 | this.emit('state', state); 400 | }; 401 | 402 | // A state is 'active' if the stream in that state counts towards the concurrency limit. Streams 403 | // that are in the "open" state, or either of the "half closed" states count toward this limit. 404 | function activeState(state) { 405 | return ((state === 'HALF_CLOSED_LOCAL') || (state === 'HALF_CLOSED_REMOTE') || (state === 'OPEN')); 406 | } 407 | 408 | // `_transition` is called every time there's an incoming or outgoing frame. It manages state 409 | // transitions, and detects stream errors. A stream error is always caused by a frame that is not 410 | // allowed in the current state. 411 | Stream.prototype._transition = function transition(sending, frame) { 412 | var receiving = !sending; 413 | var connectionError; 414 | var streamError; 415 | 416 | var DATA = false, HEADERS = false, PRIORITY = false, ALTSVC = false, BLOCKED = false; 417 | var RST_STREAM = false, PUSH_PROMISE = false, WINDOW_UPDATE = false; 418 | switch(frame.type) { 419 | case 'DATA' : DATA = true; break; 420 | case 'HEADERS' : HEADERS = true; break; 421 | case 'PRIORITY' : PRIORITY = true; break; 422 | case 'RST_STREAM' : RST_STREAM = true; break; 423 | case 'PUSH_PROMISE' : PUSH_PROMISE = true; break; 424 | case 'WINDOW_UPDATE': WINDOW_UPDATE = true; break; 425 | case 'ALTSVC' : ALTSVC = true; break; 426 | case 'BLOCKED' : BLOCKED = true; break; 427 | } 428 | 429 | var previousState = this.state; 430 | 431 | switch (this.state) { 432 | // All streams start in the **idle** state. In this state, no frames have been exchanged. 433 | // 434 | // * Sending or receiving a HEADERS frame causes the stream to become "open". 435 | // 436 | // When the HEADERS frame contains the END_STREAM flags, then two state transitions happen. 437 | case 'IDLE': 438 | if (HEADERS) { 439 | this._setState('OPEN'); 440 | if (frame.flags.END_STREAM) { 441 | this._setState(sending ? 'HALF_CLOSED_LOCAL' : 'HALF_CLOSED_REMOTE'); 442 | } 443 | this._initiated = sending; 444 | } else if (sending && RST_STREAM) { 445 | this._setState('CLOSED'); 446 | } else if (PRIORITY) { 447 | /* No state change */ 448 | } else { 449 | connectionError = 'PROTOCOL_ERROR'; 450 | } 451 | break; 452 | 453 | // A stream in the **reserved (local)** state is one that has been promised by sending a 454 | // PUSH_PROMISE frame. 455 | // 456 | // * The endpoint can send a HEADERS frame. This causes the stream to open in a "half closed 457 | // (remote)" state. 458 | // * Either endpoint can send a RST_STREAM frame to cause the stream to become "closed". This 459 | // releases the stream reservation. 460 | // * An endpoint may receive PRIORITY frame in this state. 461 | // * An endpoint MUST NOT send any other type of frame in this state. 462 | case 'RESERVED_LOCAL': 463 | if (sending && HEADERS) { 464 | this._setState('HALF_CLOSED_REMOTE'); 465 | } else if (RST_STREAM) { 466 | this._setState('CLOSED'); 467 | } else if (PRIORITY) { 468 | /* No state change */ 469 | } else { 470 | connectionError = 'PROTOCOL_ERROR'; 471 | } 472 | break; 473 | 474 | // A stream in the **reserved (remote)** state has been reserved by a remote peer. 475 | // 476 | // * Either endpoint can send a RST_STREAM frame to cause the stream to become "closed". This 477 | // releases the stream reservation. 478 | // * Receiving a HEADERS frame causes the stream to transition to "half closed (local)". 479 | // * An endpoint MAY send PRIORITY frames in this state to reprioritize the stream. 480 | // * Receiving any other type of frame MUST be treated as a stream error of type PROTOCOL_ERROR. 481 | case 'RESERVED_REMOTE': 482 | if (RST_STREAM) { 483 | this._setState('CLOSED'); 484 | } else if (receiving && HEADERS) { 485 | this._setState('HALF_CLOSED_LOCAL'); 486 | } else if (BLOCKED || PRIORITY) { 487 | /* No state change */ 488 | } else { 489 | connectionError = 'PROTOCOL_ERROR'; 490 | } 491 | break; 492 | 493 | // The **open** state is where both peers can send frames. In this state, sending peers observe 494 | // advertised stream level flow control limits. 495 | // 496 | // * From this state either endpoint can send a frame with a END_STREAM flag set, which causes 497 | // the stream to transition into one of the "half closed" states: an endpoint sending a 498 | // END_STREAM flag causes the stream state to become "half closed (local)"; an endpoint 499 | // receiving a END_STREAM flag causes the stream state to become "half closed (remote)". 500 | // * Either endpoint can send a RST_STREAM frame from this state, causing it to transition 501 | // immediately to "closed". 502 | case 'OPEN': 503 | if (frame.flags.END_STREAM) { 504 | this._setState(sending ? 'HALF_CLOSED_LOCAL' : 'HALF_CLOSED_REMOTE'); 505 | } else if (RST_STREAM) { 506 | this._setState('CLOSED'); 507 | } else { 508 | /* No state change */ 509 | } 510 | break; 511 | 512 | // A stream that is **half closed (local)** cannot be used for sending frames. 513 | // 514 | // * A stream transitions from this state to "closed" when a frame that contains a END_STREAM 515 | // flag is received, or when either peer sends a RST_STREAM frame. 516 | // * An endpoint MAY send or receive PRIORITY frames in this state to reprioritize the stream. 517 | // * WINDOW_UPDATE can be sent by a peer that has sent a frame bearing the END_STREAM flag. 518 | case 'HALF_CLOSED_LOCAL': 519 | if (RST_STREAM || (receiving && frame.flags.END_STREAM)) { 520 | this._setState('CLOSED'); 521 | } else if (BLOCKED || ALTSVC || receiving || PRIORITY || (sending && WINDOW_UPDATE)) { 522 | /* No state change */ 523 | } else { 524 | connectionError = 'PROTOCOL_ERROR'; 525 | } 526 | break; 527 | 528 | // A stream that is **half closed (remote)** is no longer being used by the peer to send frames. 529 | // In this state, an endpoint is no longer obligated to maintain a receiver flow control window 530 | // if it performs flow control. 531 | // 532 | // * If an endpoint receives additional frames for a stream that is in this state it MUST 533 | // respond with a stream error of type STREAM_CLOSED. 534 | // * A stream can transition from this state to "closed" by sending a frame that contains a 535 | // END_STREAM flag, or when either peer sends a RST_STREAM frame. 536 | // * An endpoint MAY send or receive PRIORITY frames in this state to reprioritize the stream. 537 | // * A receiver MAY receive a WINDOW_UPDATE frame on a "half closed (remote)" stream. 538 | case 'HALF_CLOSED_REMOTE': 539 | if (RST_STREAM || (sending && frame.flags.END_STREAM)) { 540 | this._setState('CLOSED'); 541 | } else if (BLOCKED || ALTSVC || sending || PRIORITY || (receiving && WINDOW_UPDATE)) { 542 | /* No state change */ 543 | } else { 544 | connectionError = 'PROTOCOL_ERROR'; 545 | } 546 | break; 547 | 548 | // The **closed** state is the terminal state. 549 | // 550 | // * An endpoint MUST NOT send frames on a closed stream. An endpoint that receives a frame 551 | // after receiving a RST_STREAM or a frame containing a END_STREAM flag on that stream MUST 552 | // treat that as a stream error of type STREAM_CLOSED. 553 | // * WINDOW_UPDATE, PRIORITY or RST_STREAM frames can be received in this state for a short 554 | // period after a frame containing an END_STREAM flag is sent. Until the remote peer receives 555 | // and processes the frame bearing the END_STREAM flag, it might send either frame type. 556 | // Endpoints MUST ignore WINDOW_UPDATE frames received in this state, though endpoints MAY 557 | // choose to treat WINDOW_UPDATE frames that arrive a significant time after sending 558 | // END_STREAM as a connection error of type PROTOCOL_ERROR. 559 | // * If this state is reached as a result of sending a RST_STREAM frame, the peer that receives 560 | // the RST_STREAM might have already sent - or enqueued for sending - frames on the stream 561 | // that cannot be withdrawn. An endpoint that sends a RST_STREAM frame MUST ignore frames that 562 | // it receives on closed streams after it has sent a RST_STREAM frame. An endpoint MAY choose 563 | // to limit the period over which it ignores frames and treat frames that arrive after this 564 | // time as being in error. 565 | // * An endpoint might receive a PUSH_PROMISE frame after it sends RST_STREAM. PUSH_PROMISE 566 | // causes a stream to become "reserved". If promised streams are not desired, a RST_STREAM 567 | // can be used to close any of those streams. 568 | case 'CLOSED': 569 | if (PRIORITY || (sending && RST_STREAM) || 570 | (receiving && WINDOW_UPDATE) || 571 | (receiving && this._closedByUs && 572 | (this._closedWithRst || RST_STREAM || ALTSVC))) { 573 | /* No state change */ 574 | } else { 575 | streamError = 'STREAM_CLOSED'; 576 | } 577 | break; 578 | } 579 | 580 | // Noting that the connection was closed by the other endpoint. It may be important in edge cases. 581 | // For example, when the peer tries to cancel a promised stream, but we already sent every data 582 | // on it, then the stream is in CLOSED state, yet we want to ignore the incoming RST_STREAM. 583 | if ((this.state === 'CLOSED') && (previousState !== 'CLOSED')) { 584 | this._closedByUs = sending; 585 | this._closedWithRst = RST_STREAM; 586 | } 587 | 588 | // Sending/receiving a PUSH_PROMISE 589 | // 590 | // * Sending a PUSH_PROMISE frame marks the associated stream for later use. The stream state 591 | // for the reserved stream transitions to "reserved (local)". 592 | // * Receiving a PUSH_PROMISE frame marks the associated stream as reserved by the remote peer. 593 | // The state of the stream becomes "reserved (remote)". 594 | if (PUSH_PROMISE && !connectionError && !streamError) { 595 | /* This assertion must hold, because _transition is called immediately when a frame is written 596 | to the stream. If it would be called when a frame gets out of the input queue, the state 597 | of the reserved could have been changed by then. */ 598 | assert(frame.promised_stream.state === 'IDLE', frame.promised_stream.state); 599 | frame.promised_stream._setState(sending ? 'RESERVED_LOCAL' : 'RESERVED_REMOTE'); 600 | frame.promised_stream._initiated = sending; 601 | } 602 | 603 | // Signaling how sending/receiving this frame changes the active stream count (-1, 0 or +1) 604 | if (this._initiated) { 605 | var change = (activeState(this.state) - activeState(previousState)); 606 | if (sending) { 607 | frame.count_change = change; 608 | } else { 609 | frame.count_change(change); 610 | } 611 | } else if (sending) { 612 | frame.count_change = 0; 613 | } 614 | 615 | // Common error handling. 616 | if (connectionError || streamError) { 617 | var info = { 618 | error: connectionError, 619 | frame: frame, 620 | state: this.state, 621 | closedByUs: this._closedByUs, 622 | closedWithRst: this._closedWithRst 623 | }; 624 | 625 | // * When sending something invalid, throwing an exception, since it is probably a bug. 626 | if (sending) { 627 | this._log.error(info, 'Sending illegal frame.'); 628 | return this.emit('error', new Error('Sending illegal frame (' + frame.type + ') in ' + this.state + ' state.')); 629 | } 630 | 631 | // * In case of a serious problem, emitting and error and letting someone else handle it 632 | // (e.g. closing the connection) 633 | // * When receiving something invalid, sending an RST_STREAM using the `reset` method. 634 | // This will automatically cause a transition to the CLOSED state. 635 | else { 636 | this._log.error(info, 'Received illegal frame.'); 637 | if (connectionError) { 638 | this.emit('connectionError', connectionError); 639 | } else { 640 | this.reset(streamError); 641 | this.emit('error', streamError); 642 | } 643 | } 644 | } 645 | }; 646 | 647 | // Bunyan serializers 648 | // ------------------ 649 | 650 | exports.serializers = {}; 651 | 652 | var nextId = 0; 653 | exports.serializers.s = function(stream) { 654 | if (!('_id' in stream)) { 655 | stream._id = nextId; 656 | nextId += 1; 657 | } 658 | return stream._id; 659 | }; 660 | -------------------------------------------------------------------------------- /lib/node-http2/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "http2", 3 | "version": "3.3.6", 4 | "description": "An HTTP/2 client and server implementation", 5 | "main": "lib/index.js", 6 | "engines" : { 7 | "node" : ">=0.12.0" 8 | }, 9 | "devDependencies": { 10 | "istanbul": "*", 11 | "chai": "*", 12 | "mocha": "*", 13 | "docco": "*", 14 | "bunyan": "*" 15 | }, 16 | "scripts": { 17 | "test": "istanbul test _mocha -- --reporter spec --slow 500 --timeout 15000", 18 | "doc": "docco lib/* --output doc --layout parallel --template root.jst --css doc/docco.css && docco lib/protocol/* --output doc/protocol --layout parallel --template protocol.jst --css doc/docco.css" 19 | }, 20 | "repository": { 21 | "type": "git", 22 | "url": "https://github.com/molnarg/node-http2.git" 23 | }, 24 | "homepage": "https://github.com/molnarg/node-http2", 25 | "bugs": { 26 | "url": "https://github.com/molnarg/node-http2/issues" 27 | }, 28 | "keywords": [ 29 | "http", 30 | "http2", 31 | "client", 32 | "server" 33 | ], 34 | "author": "Gábor Molnár (http://gabor.molnar.es)", 35 | "contributors": [ 36 | "Nick Hurley", 37 | "Mike Belshe", 38 | "Yoshihiro Iwanaga", 39 | "Igor Novikov", 40 | "James Willcox", 41 | "David Björklund", 42 | "Patrick McManus" 43 | ], 44 | "license": "MIT", 45 | "readmeFilename": "README.md" 46 | } 47 | -------------------------------------------------------------------------------- /lib/server.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var mixin = require('node-mixin'); 4 | 5 | var net = require('net'); 6 | var http = require('http'); 7 | var url = require('url'); 8 | var http2 = require('./node-http2'); 9 | var removeDeprecatedHeaders = require('./header').removeDeprecatedHeaders; 10 | 11 | var httpServers = {}; 12 | 13 | function createHTTPServer(remoteSock, httpServerID, port) { 14 | 15 | //Use httpServers[port] to cache httpServer 16 | //We must do this, because HTTP 1.1 might keep-alive 17 | //When keep-alive, it reuses http connections with a single port after TCP socket reconnected. 18 | 19 | var server = httpServers[port] || http.createServer(function(req, res) { 20 | 21 | var u = url.parse(req.url); 22 | var headers = removeDeprecatedHeaders(req.headers); 23 | 24 | headers['x-real-ip'] = req.connection.remoteAddress; 25 | 26 | var pReq = http2.raw.request({ 27 | id : server._id, 28 | plain : true, 29 | socket : server.remoteSock, 30 | //port : u.port || 443, 31 | path : u.path, 32 | method : req.method, 33 | headers : headers 34 | }, function(pRes) { 35 | res.writeHead(pRes.statusCode, pRes.headers); 36 | pRes.pipe(res); 37 | }); 38 | 39 | pReq.on('error', function(e) { 40 | res.writeHead(200); 41 | res.write('something was wrong.'); 42 | res.end(); 43 | return; 44 | }); 45 | 46 | req.pipe(pReq); 47 | }); 48 | 49 | server.remoteSock = remoteSock; 50 | server._id = httpServerID; 51 | 52 | server.listen(port, '0.0.0.0', function() { 53 | console.log('HTTPServer is running at port', port, '...'); 54 | }); 55 | 56 | return server; 57 | } 58 | 59 | function createTCPServer(options) { 60 | var httpServerID = 0; 61 | options = mixin(options,{port: 10000, httpConnects: 99}); 62 | 63 | var tcpPort = options.port; 64 | var count = options.httpConnects; 65 | var timeout = options.timeout * 1000; 66 | 67 | if(count <= 0){ 68 | count = 1; 69 | } 70 | 71 | var ports = []; 72 | for(var i = 0; i < count; i++){ 73 | ports.push(tcpPort + i + 1); 74 | } 75 | 76 | var server = net.createServer(function(sock) { 77 | var port = ports.shift(); 78 | if(!port){ 79 | var err = 'Cannot start http server, not valid ports.'; 80 | console.error(err); 81 | sock.write('error!' + err); 82 | sock.end(); 83 | return; 84 | } 85 | 86 | sock.setTimeout(timeout, function(){ 87 | var err = 'Connection timeout.'; 88 | console.error(err); 89 | sock.write('error!' + err); 90 | sock.end(); 91 | }); 92 | 93 | sock.on('data', function(data){ 94 | data = data.toString('utf-8'); 95 | 96 | if(data && data === 'port?'){ 97 | sock.write('port:' + port); 98 | }else if(data && data === 'start server'){ 99 | httpServers[port] = createHTTPServer(sock, httpServerID++, port); 100 | httpServers[port].on('close', function(){ 101 | console.log('HTTP connection closed.'); 102 | if(ports.indexOf(port) < 0){ 103 | ports.unshift(port); 104 | } 105 | delete httpServers[port]; 106 | }); 107 | 108 | sock.on('close', function() { 109 | ports.unshift(port); 110 | 111 | httpServers[port] && httpServers[port].close(); 112 | 113 | console.log('Client disconnected!'); 114 | }); 115 | console.log('Client connected...'); 116 | } 117 | }); 118 | }); 119 | 120 | server.listen(tcpPort, '0.0.0.0', function() { 121 | console.log('TCPServer is running at port', tcpPort, 122 | ', accept', count, 'http clients'); 123 | }); 124 | 125 | server.on('error', function(e) { 126 | console.error('Can not start server! ' + e.errno); 127 | }); 128 | } 129 | 130 | module.exports = createTCPServer; -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "pangolin", 3 | "version": "1.0.11", 4 | "description": "Introspected tunnels to localhost HTTP/1.1", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1", 8 | "start": "node bin/index.js server" 9 | }, 10 | "repository": { 11 | "type": "git", 12 | "url": "git+https://github.com/qgy18/pangolin.git" 13 | }, 14 | "dependencies": { 15 | "node-mixin": "*", 16 | "commander": "~2.9.0" 17 | }, 18 | "bin": { 19 | "pangolin": "./bin/index.js" 20 | }, 21 | "author": { 22 | "name": "qgy18", 23 | "email": "quguangyu@gmail.com" 24 | }, 25 | "contributors": [{ 26 | "name": "qgy18", 27 | "email": "quguangyu@gmail.com" 28 | }, 29 | { 30 | "name": "akira-cn", 31 | "email": "akira.cn@gmail.com" 32 | }], 33 | "license": "MIT", 34 | "bugs": { 35 | "url": "https://github.com/qgy18/pangolin/issues" 36 | }, 37 | "homepage": "https://github.com/qgy18/pangolin#readme" 38 | } -------------------------------------------------------------------------------- /test.js: -------------------------------------------------------------------------------- 1 | var client = require('./index'); 2 | 3 | client.connect({ 4 | remoteHost: '123.59.74.81' 5 | }); 6 | --------------------------------------------------------------------------------