From 5e5dc6f7865bc67c7d6eb78cbca3bcf0e83d7f64 Mon Sep 17 00:00:00 2001 From: David Witherspoon Date: Thu, 16 Feb 2017 16:46:21 -0800 Subject: [PATCH 01/28] Initial working experiment with http2 over ws --- integration-test/http2-ws-itest.js | 20 + integration-test/k3po/pom.xml | 41 + .../src/test/scripts/http2.get.over.ws.rpt | 114 +++ lib/http.js | 6 +- lib/http2-ws.js | 896 ++++++++++++++++++ lib/protocol/compressor.js | 2 +- lib/protocol/endpoint.js | 4 +- lib/protocol/flow.js | 2 +- lib/protocol/framer.js | 2 +- lib/protocol/stream.js | 2 +- package.json | 11 + 11 files changed, 1091 insertions(+), 9 deletions(-) create mode 100644 integration-test/http2-ws-itest.js create mode 100644 integration-test/k3po/pom.xml create mode 100644 integration-test/k3po/src/test/scripts/http2.get.over.ws.rpt create mode 100644 lib/http2-ws.js diff --git a/integration-test/http2-ws-itest.js b/integration-test/http2-ws-itest.js new file mode 100644 index 00000000..c995a9b9 --- /dev/null +++ b/integration-test/http2-ws-itest.js @@ -0,0 +1,20 @@ +var expect = require('chai').expect; +var http2ws = require('../lib/http2-ws'); + +describe('http2-cache', function () { + + // TODO consider doing browser testing + // if (browserConfig) { + // browserConfig.origin('http://localhost:8080').addResource("http://chaijs.com/chai.js"); + // } + + it('http2.get.over.ws', function (done) { + // DPW TODO proper handling of URL + http2ws.get({'host': 'localhost', 'port': 8080, 'transport':'ws://localhost:8080/echo'}, function(response){ + console.log(response); + done(); + }) + }); + +}); + diff --git a/integration-test/k3po/pom.xml b/integration-test/k3po/pom.xml new file mode 100644 index 00000000..13b79a23 --- /dev/null +++ b/integration-test/k3po/pom.xml @@ -0,0 +1,41 @@ + + + 4.0.0 + org.reaktivity + nukleus-http2-test + pom + develop-SNAPSHOT + Runs K3PO with dependencies for HTTP 2 Spec + + + + + org.kaazing + k3po-maven-plugin + 3.0.0-alpha-54 + + + + start + stop + + + + + + org.kaazing + specification.ws + 3.0.0-alpha-54 + + + + + + + + + + diff --git a/integration-test/k3po/src/test/scripts/http2.get.over.ws.rpt b/integration-test/k3po/src/test/scripts/http2.get.over.ws.rpt new file mode 100644 index 00000000..dbac8412 --- /dev/null +++ b/integration-test/k3po/src/test/scripts/http2.get.over.ws.rpt @@ -0,0 +1,114 @@ +# +# Copyright 2007-2015, Kaazing Corporation. All rights reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +property location 'http://localhost:8080/echo' +accept ${location} +accepted +connected + +read method "GET" +read version "HTTP/1.1" +read header "Host" "localhost:8080" +read header "Upgrade" /(?i:websocket)/ +read header "Connection" /(?i:Upgrade)/ +read header "Sec-WebSocket-Key" /(?[a-zA-Z0-9+\/=]{24})/ +read header "Sec-WebSocket-Version" "13" + +write status "101" "Switching Protocols" +write version "HTTP/1.1" +write header "Upgrade" "websocket" +write header "Connection" "Upgrade" +write header "Sec-WebSocket-Accept" ${ws:handshakeHash(key)} + +# HTTP 2 Priority +read [0x82 0x98] ([0..4] :readMask) +read option mask ${readMask} +read "PRI * HTTP/2.0\r\n\r\nSM\r\n\r\n" +read option mask [0x00 0x00 0x00 0x00] + +# HTTP 2 Settings +read [0x82 0x89] ([0..4] :readMask) +read option mask ${readMask} +read [0x00 0x00 0x00] +read [0x04] +read [0x00] +read [0x00 0x00 0x00 0x00] +read option mask [0x00 0x00 0x00 0x00] + +# Ack Settings +write [0x82 0x09] +write [0x00 0x00 0x00] +write [0x04] +write [0x01] +write [0x00 0x00 0x00 0x00] + +# HTTP 2 Headers +read [0x82 0x89] ([0..4] :readMask) +read option mask ${readMask} +read [0x00 0x00 0x0b] # length = 11 +read [0x01] # type = headers +read [0x04] # flags +read [0x00 0x00 0x00 0x01] # stream id = 1 +read option mask [0x00 0x00 0x00 0x00] + + # Authority +read [0x82 0x8b] ([0..4] :readMask) +read option mask ${readMask} +read [0x86 0x82 0x41 0x86 0xa0 0xe4 0x1d 0x13 0x9d 0x09 0x84] +read option mask [0x00 0x00 0x00 0x00] + +# Content (0 length) +read [0x82 0x89] ([0..4] :readMask) +read option mask ${readMask} +read [0x00 0x00 0x00] # length = 0 +read [0x00] # type = data +read [0x01] +read [0x00 0x00 0x00 0x01] # stream id = 1 +read option mask [0x00 0x00 0x00 0x00] + + +### Response HEADERS + +# HEADERS frame +# ; END_HEADERS +# (padlen=0) +# ; First response header +# :status: 404 +# server: nghttpd nghttp2/1.19.0 +# date: Wed, 01 Feb 2017 19:12:46 GMT +# content-type: text/html; charset=UTF-8 +# content-length: 147 +write [0x82 0x4E] +write [0x00 0x00 0x45 0x01 0x04 0x00 0x00 0x00 0x01 0x8d 0x76 0x90 0xaa 0x69 0xd2 0x9a] + [0xe4 0x52 0xa9 0xa7 0x4a 0x6b 0x13 0x01 0x5c 0x2f 0xae 0x0f 0x61 0x96 0xe4 0x59] + [0x3e 0x94 0x00 0x54 0xc2 0x58 0xd4 0x10 0x02 0xea 0x81 0x7e 0xe0 0x45 0x71 0xa7] + [0x14 0xc5 0xa3 0x7f 0x5f 0x92 0x49 0x7c 0xa5 0x89 0xd3 0x4d 0x1f 0x6a 0x12 0x71] + [0xd8 0x82 0xa6 0x0e 0x1b 0xf0 0xac 0xf7 0x0f 0x0d 0x03 0x31 0x34 0x37] + +# DATA frame +# ; END_STREAM +# 404 Not Found

404 Not Found


nghttpd nghttp2/1.19.0 at port 8080
+write [0x82 0x9a] +write [0x93 0x00 0x01 0x00 0x00 0x00 0x01 0x3c 0x68 0x74 0x6d 0x6c 0x3e 0x3c 0x68 0x65] + [0x61 0x64 0x3e 0x3c 0x74 0x69 0x74 0x6c 0x65 0x3e 0x34 0x30 0x34 0x20 0x4e 0x6f] + [0x74 0x20 0x46 0x6f 0x75 0x6e 0x64 0x3c 0x2f 0x74 0x69 0x74 0x6c 0x65 0x3e 0x3c] + [0x2f 0x68 0x65 0x61 0x64 0x3e 0x3c 0x62 0x6f 0x64 0x79 0x3e 0x3c 0x68 0x31 0x3e] + [0x34 0x30 0x34 0x20 0x4e 0x6f 0x74 0x20 0x46 0x6f 0x75 0x6e 0x64 0x3c 0x2f 0x68] + [0x31 0x3e 0x3c 0x68 0x72 0x3e 0x3c 0x61 0x64 0x64 0x72 0x65 0x73 0x73 0x3e 0x6e] + [0x67 0x68 0x74 0x74 0x70 0x64 0x20 0x6e 0x67 0x68 0x74 0x74 0x70 0x32 0x2f 0x31] + [0x2e 0x31 0x39 0x2e 0x30 0x20 0x61 0x74 0x20 0x70 0x6f 0x72 0x74 0x20 0x38 0x30] + [0x38 0x30 0x3c 0x2f 0x61 0x64 0x64 0x72 0x65 0x73 0x73 0x3e 0x3c 0x2f 0x62 0x6f] + [0x64 0x79 0x3e 0x3c 0x2f 0x68 0x74 0x6d 0x6c 0x3e] \ No newline at end of file diff --git a/lib/http.js b/lib/http.js index 4c4234c5..3dab8451 100644 --- a/lib/http.js +++ b/lib/http.js @@ -130,9 +130,9 @@ var net = require('net'); var url = require('url'); var util = require('util'); var EventEmitter = require('events').EventEmitter; -var PassThrough = require('stream').PassThrough; -var Readable = require('stream').Readable; -var Writable = require('stream').Writable; +var PassThrough = require('readable-stream').PassThrough; +var Readable = require('readable-stream').Readable; +var Writable = require('readable-stream').Writable; var protocol = require('./protocol'); var Endpoint = protocol.Endpoint; var http = require('http'); diff --git a/lib/http2-ws.js b/lib/http2-ws.js new file mode 100644 index 00000000..e46738aa --- /dev/null +++ b/lib/http2-ws.js @@ -0,0 +1,896 @@ +if (typeof WebSocket === 'undefined') { + WebSocket = require('websocket-stream'); +} + +function http2ws(){ + +} + +var PassThrough = require('readable-stream').PassThrough; +var Readable = require('readable-stream').Readable; +var Writable = require('readable-stream').Writable; +var protocol = require('./protocol'); +var Endpoint = protocol.Endpoint; + + +// var EventEmitter = require('events').EventEmitter; +var indexOf; + +if (typeof Array.prototype.indexOf === 'function') { + indexOf = function (haystack, needle) { + return haystack.indexOf(needle); + }; +} else { + indexOf = function (haystack, needle) { + var i = 0, length = haystack.length, idx = -1, found = false; + + while (i < length && !found) { + if (haystack[i] === needle) { + idx = i; + found = true; + } + + i++; + } + + return idx; + }; +}; + + +/* Polyfill EventEmitter. */ +var EventEmitter = function () { + this.events = {}; +}; + +EventEmitter.prototype.on = function (event, listener) { + if (typeof this.events[event] !== 'object') { + this.events[event] = []; + } + + this.events[event].push(listener); +}; + +EventEmitter.prototype.removeListener = function (event, listener) { + var idx; + + if (typeof this.events[event] === 'object') { + idx = indexOf(this.events[event], listener); + + if (idx > -1) { + this.events[event].splice(idx, 1); + } + } +}; + +EventEmitter.prototype.emit = function (event) { + var i, listeners, length, args = [].slice.call(arguments, 1); + + if (typeof this.events[event] === 'object') { + listeners = this.events[event].slice(); + length = listeners.length; + + for (i = 0; i < length; i++) { + listeners[i].apply(this, args); + } + } +}; + +EventEmitter.prototype.once = function (event, listener) { + this.on(event, function g () { + this.removeListener(event, g); + listener.apply(this, arguments); + }); +}; + + +var statusCodes = {}; +statusCodes[exports.ACCEPTED = 202] = "Accepted"; +statusCodes[exports.BAD_GATEWAY = 502] = "Bad Gateway"; +statusCodes[exports.BAD_REQUEST = 400] = "Bad Request"; +statusCodes[exports.CONFLICT = 409] = "Conflict"; +statusCodes[exports.CONTINUE = 100] = "Continue"; +statusCodes[exports.CREATED = 201] = "Created"; +statusCodes[exports.EXPECTATION_FAILED = 417] = "Expectation Failed"; +statusCodes[exports.FAILED_DEPENDENCY = 424] = "Failed Dependency"; +statusCodes[exports.FORBIDDEN = 403] = "Forbidden"; +statusCodes[exports.GATEWAY_TIMEOUT = 504] = "Gateway Timeout"; +statusCodes[exports.GONE = 410] = "Gone"; +statusCodes[exports.HTTP_VERSION_NOT_SUPPORTED = 505] = "HTTP Version Not Supported"; +statusCodes[exports.INSUFFICIENT_SPACE_ON_RESOURCE = 419] = "Insufficient Space on Resource"; +statusCodes[exports.INSUFFICIENT_STORAGE = 507] = "Insufficient Storage"; +statusCodes[exports.INTERNAL_SERVER_ERROR = 500] = "Server Error"; +statusCodes[exports.LENGTH_REQUIRED = 411] = "Length Required"; +statusCodes[exports.LOCKED = 423] = "Locked"; +statusCodes[exports.METHOD_FAILURE = 420] = "Method Failure"; +statusCodes[exports.METHOD_NOT_ALLOWED = 405] = "Method Not Allowed"; +statusCodes[exports.MOVED_PERMANENTLY = 301] = "Moved Permanently"; +statusCodes[exports.MOVED_TEMPORARILY = 302] = "Moved Temporarily"; +statusCodes[exports.MULTI_STATUS = 207] = "Multi-Status"; +statusCodes[exports.MULTIPLE_CHOICES = 300] = "Multiple Choices"; +statusCodes[exports.NETWORK_AUTHENTICATION_REQUIRED = 511] = "Network Authentication Required"; +statusCodes[exports.NO_CONTENT = 204] = "No Content"; +statusCodes[exports.NON_AUTHORITATIVE_INFORMATION = 203] = "Non Authoritative Information"; +statusCodes[exports.NOT_ACCEPTABLE = 406] = "Not Acceptable"; +statusCodes[exports.NOT_FOUND = 404] = "Not Found"; +statusCodes[exports.NOT_IMPLEMENTED = 501] = "Not Implemented"; +statusCodes[exports.NOT_MODIFIED = 304] = "Not Modified"; +statusCodes[exports.OK = 200] = "OK"; +statusCodes[exports.PARTIAL_CONTENT = 206] = "Partial Content"; +statusCodes[exports.PAYMENT_REQUIRED = 402] = "Payment Required"; +statusCodes[exports.PERMANENT_REDIRECT = 308] = "Permanent Redirect"; +statusCodes[exports.PRECONDITION_FAILED = 412] = "Precondition Failed"; +statusCodes[exports.PRECONDITION_REQUIRED = 428] = "Precondition Required"; +statusCodes[exports.PROCESSING = 102] = "Processing"; +statusCodes[exports.PROXY_AUTHENTICATION_REQUIRED = 407] = "Proxy Authentication Required"; +statusCodes[exports.REQUEST_HEADER_FIELDS_TOO_LARGE = 431] = "Request Header Fields Too Large"; +statusCodes[exports.REQUEST_TIMEOUT = 408] = "Request Timeout"; +statusCodes[exports.REQUEST_TOO_LONG = 413] = "Request Entity Too Large"; +statusCodes[exports.REQUEST_URI_TOO_LONG = 414] = "Request-URI Too Long"; +statusCodes[exports.REQUESTED_RANGE_NOT_SATISFIABLE = 416] = "Requested Range Not Satisfiable"; +statusCodes[exports.RESET_CONTENT = 205] = "Reset Content"; +statusCodes[exports.SEE_OTHER = 303] = "See Other"; +statusCodes[exports.SERVICE_UNAVAILABLE = 503] = "Service Unavailable"; +statusCodes[exports.SWITCHING_PROTOCOLS = 101] = "Switching Protocols"; +statusCodes[exports.TEMPORARY_REDIRECT = 307] = "Temporary Redirect"; +statusCodes[exports.TOO_MANY_REQUESTS = 429] = "Too Many Requests"; +statusCodes[exports.UNAUTHORIZED = 401] = "Unauthorized"; +statusCodes[exports.UNPROCESSABLE_ENTITY = 422] = "Unprocessable Entity"; +statusCodes[exports.UNSUPPORTED_MEDIA_TYPE = 415] = "Unsupported Media Type"; +statusCodes[exports.USE_PROXY = 305] = "Use Proxy"; + +Object.assign = function(target, varArgs) { // .length of function is 2 + 'use strict'; + if (target == null) { // TypeError if undefined or null + throw new TypeError('Cannot convert undefined or null to object'); + } + + var to = Object(target); + + for (var index = 1; index < arguments.length; index++) { + var nextSource = arguments[index]; + + if (nextSource != null) { // Skip over if undefined or null + for (var nextKey in nextSource) { + // Avoid bugs when hasOwnProperty is shadowed + if (Object.prototype.hasOwnProperty.call(nextSource, nextKey)) { + to[nextKey] = nextSource[nextKey]; + } + } + } + } + return to; +}; + +var deprecatedHeaders = [ + 'connection', + 'host', + 'keep-alive', + 'proxy-connection', + 'transfer-encoding', + 'upgrade' +]; + +http2ws.STATUS_CODES = statusCodes; +// exports.getStatusText = function(statusCode) { +// if (statusCodes.hasOwnProperty(statusCode)) { +// return statusCodes[statusCode]; +// } else { +// throw new Error("Status code does not exist: " + statusCode); +// } +// }; + +// Logger shim, used when no logger is provided by the user. +function noop() {} +var defaultLogger = { + fatal: noop, + error: noop, + warn : noop, + info : noop, + debug: noop, + trace: noop, + + child: function() { return this; } +}; + + +// IncomingRequest class +// --------------------- + +function IncomingRequest(stream) { + IncomingMessage.call(this, stream); +} +IncomingRequest.prototype = Object.create(IncomingMessage.prototype, { constructor: { value: IncomingRequest } }); + +// [Request Header Fields](https://tools.ietf.org/html/rfc7540#section-8.1.2.3) +// * `headers` argument: HTTP/2.0 request and response header fields carry information as a series +// of key-value pairs. This includes the target URI for the request, the status code for the +// response, as well as HTTP header fields. +IncomingRequest.prototype._onHeaders = function _onHeaders(headers) { + // * The ":method" header field includes the HTTP method + // * The ":scheme" header field includes the scheme portion of the target URI + // * The ":authority" header field includes the authority portion of the target URI + // * The ":path" header field includes the path and query parts of the target URI. + // This field MUST NOT be empty; URIs that do not contain a path component MUST include a value + // of '/', unless the request is an OPTIONS request for '*', in which case the ":path" header + // field MUST include '*'. + // * All HTTP/2.0 requests MUST include exactly one valid value for all of these header fields. A + // server MUST treat the absence of any of these header fields, presence of multiple values, or + // an invalid value as a stream error of type PROTOCOL_ERROR. + this.method = this._checkSpecialHeader(':method' , headers[':method']); + this.scheme = this._checkSpecialHeader(':scheme' , headers[':scheme']); + this.host = this._checkSpecialHeader(':authority', headers[':authority'] ); + this.url = this._checkSpecialHeader(':path' , headers[':path'] ); + if (!this.method || !this.scheme || !this.host || !this.url) { + // This is invalid, and we've sent a RST_STREAM, so don't continue processing + return; + } + + // * Host header is included in the headers object for backwards compatibility. + this.headers.host = this.host; + + // * Handling regular headers. + IncomingMessage.prototype._onHeaders.call(this, headers); + + // * Signaling that the headers arrived. + this._log.info({ method: this.method, scheme: this.scheme, host: this.host, + path: this.url, headers: this.headers }, 'Incoming request'); + this.emit('ready'); +}; + +// OutgoingResponse class +// ---------------------- + +function OutgoingResponse(stream) { + OutgoingMessage.call(this); + + this._log = stream._log.child({ component: 'http' }); + + this.stream = stream; + this.statusCode = 200; + this.sendDate = true; + + this.stream.once('headers', this._onRequestHeaders.bind(this)); +} +OutgoingResponse.prototype = Object.create(OutgoingMessage.prototype, { constructor: { value: OutgoingResponse } }); + +OutgoingResponse.prototype.writeHead = function writeHead(statusCode, reasonPhrase, headers) { + if (this.headersSent) { + return; + } + + if (typeof reasonPhrase === 'string') { + this._log.warn('Reason phrase argument was present but ignored by the writeHead method'); + } else { + headers = reasonPhrase; + } + + for (var name in headers) { + this.setHeader(name, headers[name]); + } + headers = this._headers; + + if (this.sendDate && !('date' in this._headers)) { + headers.date = (new Date()).toUTCString(); + } + + this._log.info({ status: statusCode, headers: this._headers }, 'Sending server response'); + + headers[':status'] = this.statusCode = statusCode; + + this.stream.headers(headers); + this.headersSent = true; +}; + +OutgoingResponse.prototype._implicitHeaders = function _implicitHeaders() { + if (!this.headersSent) { + this.writeHead(this.statusCode); + } +}; + +OutgoingResponse.prototype._implicitHeader = function() { + this._implicitHeaders(); +}; + +OutgoingResponse.prototype.write = function write() { + this._implicitHeaders(); + return OutgoingMessage.prototype.write.apply(this, arguments); +}; + +OutgoingResponse.prototype.end = function end() { + this.finshed = true; + this._implicitHeaders(); + return OutgoingMessage.prototype.end.apply(this, arguments); +}; + +OutgoingResponse.prototype._onRequestHeaders = function _onRequestHeaders(headers) { + this._requestHeaders = headers; +}; + +OutgoingResponse.prototype.push = function push(options) { + if (typeof options === 'string') { + options = url.parse(options); + } + + if (!options.path) { + throw new Error('`path` option is mandatory.'); + } + + var promise = util._extend({ + ':method': (options.method || 'GET').toUpperCase(), + ':scheme': (options.protocol && options.protocol.slice(0, -1)) || this._requestHeaders[':scheme'], + ':authority': options.hostname || options.host || this._requestHeaders[':authority'], + ':path': options.path + }, options.headers); + + this._log.info({ method: promise[':method'], scheme: promise[':scheme'], + authority: promise[':authority'], path: promise[':path'], + headers: options.headers }, 'Promising push stream'); + + var pushStream = this.stream.promise(promise); + + return new OutgoingResponse(pushStream); +}; + +OutgoingResponse.prototype.altsvc = function altsvc(host, port, protocolID, maxAge, origin) { + if (origin === undefined) { + origin = ""; + } + this.stream.altsvc(host, port, protocolID, maxAge, origin); +}; + +// Overriding `EventEmitter`'s `on(event, listener)` method to forward certain subscriptions to +// `request`. See `Server.prototype.on` for explanation. +OutgoingResponse.prototype.on = function on(event, listener) { + if (this.request && (event === 'timeout')) { + this.request.on(event, listener && listener.bind(this)); + } else { + OutgoingMessage.prototype.on.call(this, event, listener); + } +}; + + +// Bunyan serializers exported by submodules that are worth adding when creating a logger. +exports.serializers = protocol.serializers; + +// IncomingMessage class +// --------------------- + +function IncomingMessage(stream) { + // * This is basically a read-only wrapper for the [Stream](protocol/stream.html) class. + PassThrough.call(this); + stream.pipe(this); + this.socket = this.stream = stream; + + this._log = stream._log.child({ component: 'http' }); + + // * HTTP/2.0 does not define a way to carry the version identifier that is included in the + // HTTP/1.1 request/status line. Version is always 2.0. + this.httpVersion = '2.0'; + this.httpVersionMajor = 2; + this.httpVersionMinor = 0; + + // * `this.headers` will store the regular headers (and none of the special colon headers) + this.headers = {}; + this.trailers = undefined; + this._lastHeadersSeen = undefined; + + // * Other metadata is filled in when the headers arrive. + stream.once('headers', this._onHeaders.bind(this)); + stream.once('end', this._onEnd.bind(this)); +} +IncomingMessage.prototype = Object.create(PassThrough.prototype, { constructor: { value: IncomingMessage } }); + +// [Request Header Fields](https://tools.ietf.org/html/rfc7540#section-8.1.2.3) +// * `headers` argument: HTTP/2.0 request and response header fields carry information as a series +// of key-value pairs. This includes the target URI for the request, the status code for the +// response, as well as HTTP header fields. +IncomingMessage.prototype._onHeaders = function _onHeaders(headers) { + // * Detects malformed headers + this._validateHeaders(headers); + + // * Store the _regular_ headers in `this.headers` + for (var name in headers) { + if (name[0] !== ':') { + if (name === 'set-cookie' && !Array.isArray(headers[name])) { + this.headers[name] = [headers[name]]; + } else { + this.headers[name] = headers[name]; + } + } + } + + // * The last header block, if it's not the first, will represent the trailers + var self = this; + this.stream.on('headers', function(headers) { + self._lastHeadersSeen = headers; + }); +}; + +IncomingMessage.prototype._onEnd = function _onEnd() { + this.trailers = this._lastHeadersSeen; +}; + +IncomingMessage.prototype.setTimeout = noop; + +IncomingMessage.prototype._checkSpecialHeader = function _checkSpecialHeader(key, value) { + if ((typeof value !== 'string') || (value.length === 0)) { + this._log.error({ key: key, value: value }, 'Invalid or missing special header field'); + this.stream.reset('PROTOCOL_ERROR'); + } + + return value; +}; + +IncomingMessage.prototype._validateHeaders = function _validateHeaders(headers) { + // * An HTTP/2.0 request or response MUST NOT include any of the following header fields: + // Connection, Host, Keep-Alive, Proxy-Connection, Transfer-Encoding, and Upgrade. A server + // MUST treat the presence of any of these header fields as a stream error of type + // PROTOCOL_ERROR. + // If the TE header is present, it's only valid value is 'trailers' + for (var i = 0; i < deprecatedHeaders.length; i++) { + var key = deprecatedHeaders[i]; + if (key in headers || (key === 'te' && headers[key] !== 'trailers')) { + this._log.error({ key: key, value: headers[key] }, 'Deprecated header found'); + this.stream.reset('PROTOCOL_ERROR'); + return; + } + } + + for (var headerName in headers) { + // * Empty header name field is malformed + if (headerName.length <= 1) { + this.stream.reset('PROTOCOL_ERROR'); + return; + } + // * A request or response containing uppercase header name field names MUST be + // treated as malformed (Section 8.1.3.5). Implementations that detect malformed + // requests or responses need to ensure that the stream ends. + if(/[A-Z]/.test(headerName)) { + this.stream.reset('PROTOCOL_ERROR'); + return; + } + } +}; + +// OutgoingMessage class +// --------------------- + +function OutgoingMessage() { + // * This is basically a read-only wrapper for the [Stream](protocol/stream.html) class. + Writable.call(this); + + this._headers = {}; + this._trailers = undefined; + this.headersSent = false; + this.finished = false; + + this.on('finish', this._finish); +} +OutgoingMessage.prototype = Object.create(Writable.prototype, { constructor: { value: OutgoingMessage } }); + +OutgoingMessage.prototype._write = function _write(chunk, encoding, callback) { + if (this.stream) { + this.stream.write(chunk, encoding, callback); + } else { + this.once('socket', this._write.bind(this, chunk, encoding, callback)); + } +}; + +OutgoingMessage.prototype._finish = function _finish() { + if (this.stream) { + if (this._trailers) { + if (this.request) { + this.request.addTrailers(this._trailers); + } else { + this.stream.headers(this._trailers); + } + } + this.finished = true; + this.stream.end(); + } else { + this.once('socket', this._finish.bind(this)); + } +}; + +OutgoingMessage.prototype.setHeader = function setHeader(name, value) { + if (this.headersSent) { + return this.emit('error', new Error('Can\'t set headers after they are sent.')); + } else { + name = name.toLowerCase(); + if (deprecatedHeaders.indexOf(name) !== -1) { + return this.emit('error', new Error('Cannot set deprecated header: ' + name)); + } + this._headers[name] = value; + } +}; + +OutgoingMessage.prototype.removeHeader = function removeHeader(name) { + if (this.headersSent) { + return this.emit('error', new Error('Can\'t remove headers after they are sent.')); + } else { + delete this._headers[name.toLowerCase()]; + } +}; + +OutgoingMessage.prototype.getHeader = function getHeader(name) { + return this._headers[name.toLowerCase()]; +}; + +OutgoingMessage.prototype.addTrailers = function addTrailers(trailers) { + this._trailers = trailers; +}; + +OutgoingMessage.prototype.setTimeout = noop; + +OutgoingMessage.prototype._checkSpecialHeader = IncomingMessage.prototype._checkSpecialHeader; + + +// Client side +// =========== + +exports.ClientRequest = OutgoingRequest; // for API compatibility +exports.OutgoingRequest = OutgoingRequest; +exports.IncomingResponse = IncomingResponse; +exports.Agent = Agent; +exports.globalAgent = undefined; + +function requestRaw(options, callback) { + if (typeof options === "string") { + options = url.parse(options); + } + options.plain = true; + if (options.protocol && options.protocol !== "http:") { + throw new Error('This interface only supports http-schemed URLs'); + } + if (options.agent && typeof(options.agent.request) === 'function') { + var agentOptions = Object.assign({}, options); + delete agentOptions.agent; + return options.agent.request(agentOptions, callback); + } + return exports.globalAgent.request(options, callback); +} + +function getRaw(options, callback) { + // TODO maybe handle string URL + // if (typeof options === "string") { + // options = url.parse(options); + // } + options.plain = true; + if (options.agent && typeof(options.agent.get) === 'function') { + var agentOptions = Object.assign({}, options); + delete agentOptions.agent; + return options.agent.get(agentOptions, callback); + } + return exports.globalAgent.get(options, callback); +} + +// Agent class +// ----------- + +function Agent(options) { + EventEmitter.call(this); + // this.setMaxListeners(0); + + options = Object.assign({}, options); + + this._settings = options.settings; + this._log = (options.log || defaultLogger).child({ component: 'http' }); + this.endpoints = {}; + + // * Using an own HTTPS agent, because the global agent does not look at `NPN/ALPNProtocols` when + // generating the key identifying the connection, so we may get useless non-negotiated TLS + // channels even if we ask for a negotiated one. This agent will contain only negotiated + // channels. + // DPW TODO + // this._httpsAgent = new https.Agent(options); + // + // this.sockets = this._httpsAgent.sockets; + // this.requests = this._httpsAgent.requests; + + // this.sockets = new WebSocket('ws://localhost:8080/echo', 'http2'); +} +Agent.prototype = Object.create(EventEmitter.prototype, { constructor: { value: Agent } }); + +Agent.prototype.request = function request(options, callback) { + if (typeof options === 'string') { + options = url.parse(options); + } else { + options = Object.assign({}, options); + } + + options.method = (options.method || 'GET').toUpperCase(); + options.protocol = options.protocol || 'http:'; + options.host = options.hostname || options.host || 'localhost'; + options.port = options.port || 443; + options.path = options.path || '/'; + + if (!options.plain && options.protocol === 'http:') { + this._log.error('Trying to negotiate client request with Upgrade from HTTP/1.1'); + this.emit('error', new Error('HTTP1.1 -> HTTP2 upgrade is not yet supported.')); + } + + var request = new OutgoingRequest(this._log); + + if (callback) { + request.on('response', callback); + } + + var key = [ + !!options.plain, + options.host, + options.port + ].join(':'); + var self = this; + + // * There's an existing HTTP/2 connection to this host + if (key in this.endpoints) { + var endpoint = this.endpoints[key]; + request._start(endpoint.createStream(), options); + } + + // * HTTP/2 over plain WS + else if (options.plain) { + endpoint = new Endpoint(this._log, 'CLIENT', this._settings); + var wsUrl = options.transport; + // TODO throw error earlier if not defined. + endpoint.socket = new WebSocket(wsUrl); + + // endpoint.socket = net.connect({ + // host: options.host, + // port: options.port, + // localAddress: options.localAddress + // }); + + endpoint.socket.on('error', function (error) { + self._log.error('Socket error: ' + error.toString()); + request.emit('error', error); + }); + + endpoint.on('error', function(error){ + self._log.error('Connection error: ' + error.toString()); + request.emit('error', error); + }); + + this.endpoints[key] = endpoint; + endpoint.pipe(endpoint.socket).pipe(endpoint); + request._start(endpoint.createStream(), options); + } + + return request; +}; + +Agent.prototype.get = function get(options, callback) { + var request = this.request(options, callback); + request.end(); + return request; +}; + +Agent.prototype.destroy = function(error) { + // DPW TODO _httpsAgent? + if (this._httpsAgent) { + this._httpsAgent.destroy(); + } + for (var key in this.endpoints) { + this.endpoints[key].close(error); + } +}; + +function unbundleSocket(socket) { + socket.removeAllListeners('data'); + socket.removeAllListeners('end'); + socket.removeAllListeners('readable'); + socket.removeAllListeners('close'); + socket.removeAllListeners('error'); + socket.unpipe(); + delete socket.ondata; + delete socket.onend; +} + +function hasAgentOptions(options) { + return options.pfx != null || + options.key != null || + options.passphrase != null || + options.cert != null || + options.ca != null || + options.ciphers != null || + options.rejectUnauthorized != null || + options.secureProtocol != null; +} + +// DPW TODO +httpsAgent? +Object.defineProperty(Agent.prototype, 'maxSockets', { + get: function getMaxSockets() { + return this._httpsAgent.maxSockets; + }, + set: function setMaxSockets(value) { + this._httpsAgent.maxSockets = value; + } +}); + +exports.globalAgent = new Agent(); + +// OutgoingRequest class +// --------------------- + +function OutgoingRequest() { + OutgoingMessage.call(this); + + this._log = undefined; + + this.stream = undefined; +} +OutgoingRequest.prototype = Object.create(OutgoingMessage.prototype, { constructor: { value: OutgoingRequest } }); + +OutgoingRequest.prototype._start = function _start(stream, options) { + this.stream = stream; + this.options = options; + + this._log = stream._log.child({ component: 'http' }); + + for (var key in options.headers) { + this.setHeader(key, options.headers[key]); + } + var headers = this._headers; + delete headers.host; + + if (options.auth) { + headers.authorization = 'Basic ' + new Buffer(options.auth).toString('base64'); + } + + headers[':scheme'] = options.protocol.slice(0, -1); + headers[':method'] = options.method; + headers[':authority'] = options.host; + headers[':path'] = options.path; + + this._log.info({ scheme: headers[':scheme'], method: headers[':method'], + authority: headers[':authority'], path: headers[':path'], + headers: (options.headers || {}) }, 'Sending request'); + this.stream.headers(headers); + this.headersSent = true; + + this.emit('socket', this.stream); + var response = new IncomingResponse(this.stream); + response.req = this; + response.once('ready', this.emit.bind(this, 'response', response)); + + this.stream.on('promise', this._onPromise.bind(this)); +}; + +OutgoingRequest.prototype._fallback = function _fallback(request) { + request.on('response', this.emit.bind(this, 'response')); + this.stream = this.request = request; + this.emit('socket', this.socket); +}; + +OutgoingRequest.prototype.setPriority = function setPriority(priority) { + if (this.stream) { + this.stream.priority(priority); + } else { + this.once('socket', this.setPriority.bind(this, priority)); + } +}; + +// Overriding `EventEmitter`'s `on(event, listener)` method to forward certain subscriptions to +// `request`. See `Server.prototype.on` for explanation. +OutgoingRequest.prototype.on = function on(event, listener) { + if (this.request && (event === 'upgrade')) { + this.request.on(event, listener && listener.bind(this)); + } else { + OutgoingMessage.prototype.on.call(this, event, listener); + } +}; + +// Methods only in fallback mode +OutgoingRequest.prototype.setNoDelay = function setNoDelay(noDelay) { + if (this.request) { + this.request.setNoDelay(noDelay); + } else if (!this.stream) { + this.on('socket', this.setNoDelay.bind(this, noDelay)); + } +}; + +OutgoingRequest.prototype.setSocketKeepAlive = function setSocketKeepAlive(enable, initialDelay) { + if (this.request) { + this.request.setSocketKeepAlive(enable, initialDelay); + } else if (!this.stream) { + this.on('socket', this.setSocketKeepAlive.bind(this, enable, initialDelay)); + } +}; + +OutgoingRequest.prototype.setTimeout = function setTimeout(timeout, callback) { + if (this.request) { + this.request.setTimeout(timeout, callback); + } else if (!this.stream) { + this.on('socket', this.setTimeout.bind(this, timeout, callback)); + } +}; + +// Aborting the request +OutgoingRequest.prototype.abort = function abort() { + if (this.request) { + this.request.abort(); + } else if (this.stream) { + this.stream.reset('CANCEL'); + } else { + this.on('socket', this.abort.bind(this)); + } +}; + +// Receiving push promises +OutgoingRequest.prototype._onPromise = function _onPromise(stream, headers) { + this._log.info({ push_stream: stream.id }, 'Receiving push promise'); + + var promise = new IncomingPromise(stream, headers); + + if (this.listeners('push').length > 0) { + this.emit('push', promise); + } else { + promise.cancel(); + } +}; + +// IncomingResponse class +// ---------------------- + +function IncomingResponse(stream) { + IncomingMessage.call(this, stream); +} +IncomingResponse.prototype = Object.create(IncomingMessage.prototype, { constructor: { value: IncomingResponse } }); + +// [Response Header Fields](https://tools.ietf.org/html/rfc7540#section-8.1.2.4) +// * `headers` argument: HTTP/2.0 request and response header fields carry information as a series +// of key-value pairs. This includes the target URI for the request, the status code for the +// response, as well as HTTP header fields. +IncomingResponse.prototype._onHeaders = function _onHeaders(headers) { + // * A single ":status" header field is defined that carries the HTTP status code field. This + // header field MUST be included in all responses. + // * A client MUST treat the absence of the ":status" header field, the presence of multiple + // values, or an invalid value as a stream error of type PROTOCOL_ERROR. + // Note: currently, we do not enforce it strictly: we accept any format, and parse it as int + // * HTTP/2.0 does not define a way to carry the reason phrase that is included in an HTTP/1.1 + // status line. + this.statusCode = parseInt(this._checkSpecialHeader(':status', headers[':status'])); + + // * Handling regular headers. + IncomingMessage.prototype._onHeaders.call(this, headers); + + // * Signaling that the headers arrived. + this._log.info({ status: this.statusCode, headers: this.headers}, 'Incoming response'); + this.emit('ready'); +}; + +// IncomingPromise class +// ------------------------- + +function IncomingPromise(responseStream, promiseHeaders) { + var stream = new Readable(); + stream._read = noop; + stream.push(null); + stream._log = responseStream._log; + + IncomingRequest.call(this, stream); + + this._onHeaders(promiseHeaders); + + this._responseStream = responseStream; + + var response = new IncomingResponse(this._responseStream); + response.once('ready', this.emit.bind(this, 'response', response)); + + this.stream.on('promise', this._onPromise.bind(this)); +} +IncomingPromise.prototype = Object.create(IncomingRequest.prototype, { constructor: { value: IncomingPromise } }); + +IncomingPromise.prototype.cancel = function cancel() { + this._responseStream.reset('CANCEL'); +}; + +IncomingPromise.prototype.setPriority = function setPriority(priority) { + this._responseStream.priority(priority); +}; + +IncomingPromise.prototype._onPromise = OutgoingRequest.prototype._onPromise; + +exports.Agent = Agent; +exports.get = getRaw; \ No newline at end of file diff --git a/lib/protocol/compressor.js b/lib/protocol/compressor.js index 3923a910..6ff7dde7 100644 --- a/lib/protocol/compressor.js +++ b/lib/protocol/compressor.js @@ -22,7 +22,7 @@ exports.HeaderSetDecompressor = HeaderSetDecompressor; exports.Compressor = Compressor; exports.Decompressor = Decompressor; -var TransformStream = require('stream').Transform; +var TransformStream = require('readable-stream').Transform; var assert = require('assert'); var util = require('util'); diff --git a/lib/protocol/endpoint.js b/lib/protocol/endpoint.js index a218db04..c6136dcb 100644 --- a/lib/protocol/endpoint.js +++ b/lib/protocol/endpoint.js @@ -5,8 +5,8 @@ var Deserializer = require('./framer').Deserializer; var Compressor = require('./compressor').Compressor; var Decompressor = require('./compressor').Decompressor; var Connection = require('./connection').Connection; -var Duplex = require('stream').Duplex; -var Transform = require('stream').Transform; +var Duplex = require('readable-stream').Duplex; +var Transform = require('readable-stream').Transform; exports.Endpoint = Endpoint; diff --git a/lib/protocol/flow.js b/lib/protocol/flow.js index 4ec5649b..b3daa90d 100644 --- a/lib/protocol/flow.js +++ b/lib/protocol/flow.js @@ -7,7 +7,7 @@ var assert = require('assert'); // subclassed by [Connection](connection.html) and the `upstream` component of [Stream](stream.html). // [1]: https://nodejs.org/api/stream.html#stream_class_stream_duplex -var Duplex = require('stream').Duplex; +var Duplex = require('readable-stream').Duplex; exports.Flow = Flow; diff --git a/lib/protocol/framer.js b/lib/protocol/framer.js index 244e60ae..1732a5a2 100644 --- a/lib/protocol/framer.js +++ b/lib/protocol/framer.js @@ -4,7 +4,7 @@ // [2]: https://nodejs.org/api/stream.html#stream_new_stream_readable_options var assert = require('assert'); -var Transform = require('stream').Transform; +var Transform = require('readable-stream').Transform; exports.Serializer = Serializer; exports.Deserializer = Deserializer; diff --git a/lib/protocol/stream.js b/lib/protocol/stream.js index 6d520b94..719f6fb0 100644 --- a/lib/protocol/stream.js +++ b/lib/protocol/stream.js @@ -9,7 +9,7 @@ var assert = require('assert'); // object itself) and one that is used by a Connection to read/write frames to/from the other peer // (`stream.upstream`). -var Duplex = require('stream').Duplex; +var Duplex = require('readable-stream').Duplex; exports.Stream = Stream; diff --git a/package.json b/package.json index 5372f17f..74a38972 100644 --- a/package.json +++ b/package.json @@ -6,15 +6,26 @@ "engines" : { "node" : ">=0.12.0" }, + "dependencies": { + "readable-stream": "2.2.2" + }, "devDependencies": { + "k3po-exec": "0.0.x", + "k3po-mocha.js": "0.0.x", "istanbul": "*", "chai": "*", "mocha": "*", "docco": "*", + "websocket": "1.0.24", + "websocket-stream": "3.3.3", + "browserify": "14.0.0 ", "bunyan": "*" }, "scripts": { "test": "istanbul test _mocha -- --reporter spec --slow 500 --timeout 15000", + "preintegration-test": "node ./node_modules/k3po-exec/k3po-exec.js --pom ./integration-test/k3po/pom.xml --log ./builds/integration-test/k3po-test.log --pidFile ./builds/integration-test/k3po-test.pid start", + "integration-test": "mocha --ui /Users/David/Documents/projects/javascript-k3po/k3po-mocha.js/k3po-ui.js integration-test/k3po-test.js", + "postintegration-test": "node ./node_modules/k3po-exec/k3po-exec.js --pom ./integration-test/k3po/pom.xml --log ./builds/integration-test/k3po-test.log --pidFile ./builds/integration-test/k3po-test.pid", "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" }, "repository": { From a785493258a1ef9e60866f011a0278787551dcf0 Mon Sep 17 00:00:00 2001 From: David Witherspoon Date: Fri, 17 Feb 2017 11:02:32 -0800 Subject: [PATCH 02/28] Working in browser --- .gitignore | 3 ++- index.html | 17 +++++++++++++++++ integration-test/http2-ws-itest.js | 9 +++++---- .../k3po/src/test/scripts/http2.get.over.ws.rpt | 10 +++++++--- lib/http2-ws.js | 16 +++++++++++++--- lib/protocol/compressor.js | 2 +- lib/protocol/connection.js | 2 +- lib/protocol/endpoint.js | 2 +- lib/protocol/flow.js | 2 +- lib/protocol/framer.js | 5 +++-- lib/protocol/index.js | 14 +++++++------- lib/protocol/stream.js | 2 +- lib/utils/assert-polyfil.js | 11 +++++++++++ lib/utils/environment.js | 8 ++++++++ package.json | 10 +++++----- 15 files changed, 83 insertions(+), 30 deletions(-) create mode 100644 index.html create mode 100644 lib/utils/assert-polyfil.js create mode 100644 lib/utils/environment.js diff --git a/.gitignore b/.gitignore index bc483625..08b69ae6 100644 --- a/.gitignore +++ b/.gitignore @@ -4,4 +4,5 @@ coverage doc .vscode/.browse* npm-debug.log -typings \ No newline at end of file +typings +builds diff --git a/index.html b/index.html new file mode 100644 index 00000000..353e6fde --- /dev/null +++ b/index.html @@ -0,0 +1,17 @@ + + + + + + +

Let's go http2 over ws

+ +

This is crazy

+ + + + + + diff --git a/integration-test/http2-ws-itest.js b/integration-test/http2-ws-itest.js index c995a9b9..bbf0cad5 100644 --- a/integration-test/http2-ws-itest.js +++ b/integration-test/http2-ws-itest.js @@ -10,10 +10,11 @@ describe('http2-cache', function () { it('http2.get.over.ws', function (done) { // DPW TODO proper handling of URL - http2ws.get({'host': 'localhost', 'port': 8080, 'transport':'ws://localhost:8080/echo'}, function(response){ - console.log(response); - done(); - }) + // http2ws.get({'host': 'localhost', 'port': 8080, 'transport':'ws://localhost:8080/echo'}, function(response){ + // console.log(response); + // done(); + // }) + console.log("waiting for ever !!!"); }); }); diff --git a/integration-test/k3po/src/test/scripts/http2.get.over.ws.rpt b/integration-test/k3po/src/test/scripts/http2.get.over.ws.rpt index dbac8412..b9228a1b 100644 --- a/integration-test/k3po/src/test/scripts/http2.get.over.ws.rpt +++ b/integration-test/k3po/src/test/scripts/http2.get.over.ws.rpt @@ -33,7 +33,7 @@ write header "Upgrade" "websocket" write header "Connection" "Upgrade" write header "Sec-WebSocket-Accept" ${ws:handshakeHash(key)} -# HTTP 2 Priority +# HTTP 2 Client Connection Preface read [0x82 0x98] ([0..4] :readMask) read option mask ${readMask} read "PRI * HTTP/2.0\r\n\r\nSM\r\n\r\n" @@ -101,8 +101,12 @@ write [0x00 0x00 0x45 0x01 0x04 0x00 0x00 0x00 0x01 0x8d 0x76 0x90 0xaa 0x69 0xd # DATA frame # ; END_STREAM # 404 Not Found

404 Not Found


nghttpd nghttp2/1.19.0 at port 8080
-write [0x82 0x9a] -write [0x93 0x00 0x01 0x00 0x00 0x00 0x01 0x3c 0x68 0x74 0x6d 0x6c 0x3e 0x3c 0x68 0x65] +write [0x82 0x7e 0x00 0x9c] +write [0x00 0x00 0x93] + [0x00] + [0x01] + [0x00 0x00 0x00 0x01] + [0x3c 0x68 0x74 0x6d 0x6c 0x3e 0x3c 0x68 0x65] [0x61 0x64 0x3e 0x3c 0x74 0x69 0x74 0x6c 0x65 0x3e 0x34 0x30 0x34 0x20 0x4e 0x6f] [0x74 0x20 0x46 0x6f 0x75 0x6e 0x64 0x3c 0x2f 0x74 0x69 0x74 0x6c 0x65 0x3e 0x3c] [0x2f 0x68 0x65 0x61 0x64 0x3e 0x3c 0x62 0x6f 0x64 0x79 0x3e 0x3c 0x68 0x31 0x3e] diff --git a/lib/http2-ws.js b/lib/http2-ws.js index e46738aa..f3dea8eb 100644 --- a/lib/http2-ws.js +++ b/lib/http2-ws.js @@ -1,5 +1,8 @@ -if (typeof WebSocket === 'undefined') { - WebSocket = require('websocket-stream'); +WebSocket = require('websocket-stream'); +var env = require('./utils/environment'); + +if(!env.isNode()){ + require("setimmediate"); } function http2ws(){ @@ -893,4 +896,11 @@ IncomingPromise.prototype.setPriority = function setPriority(priority) { IncomingPromise.prototype._onPromise = OutgoingRequest.prototype._onPromise; exports.Agent = Agent; -exports.get = getRaw; \ No newline at end of file +exports.get = getRaw; + +// If browser then attach to window +if (!env.isNode()) { + // DPW TODO, combine with node export logic + window.http2ws = http2ws; + http2ws.get = getRaw; +} \ No newline at end of file diff --git a/lib/protocol/compressor.js b/lib/protocol/compressor.js index 6ff7dde7..9013eb35 100644 --- a/lib/protocol/compressor.js +++ b/lib/protocol/compressor.js @@ -23,7 +23,7 @@ exports.Compressor = Compressor; exports.Decompressor = Decompressor; var TransformStream = require('readable-stream').Transform; -var assert = require('assert'); +var assert = require('../utils/assert-polyfil'); var util = require('util'); // Header compression diff --git a/lib/protocol/connection.js b/lib/protocol/connection.js index 2b86b7f1..e67004e1 100644 --- a/lib/protocol/connection.js +++ b/lib/protocol/connection.js @@ -1,4 +1,4 @@ -var assert = require('assert'); +var assert = require('../utils/assert-polyfil'); // The Connection class // ==================== diff --git a/lib/protocol/endpoint.js b/lib/protocol/endpoint.js index c6136dcb..c5d0b9ae 100644 --- a/lib/protocol/endpoint.js +++ b/lib/protocol/endpoint.js @@ -1,4 +1,4 @@ -var assert = require('assert'); +var assert = require('../utils/assert-polyfil'); var Serializer = require('./framer').Serializer; var Deserializer = require('./framer').Deserializer; diff --git a/lib/protocol/flow.js b/lib/protocol/flow.js index b3daa90d..13477cf3 100644 --- a/lib/protocol/flow.js +++ b/lib/protocol/flow.js @@ -1,4 +1,4 @@ -var assert = require('assert'); +var assert = require('../utils/assert-polyfil'); // The Flow class // ============== diff --git a/lib/protocol/framer.js b/lib/protocol/framer.js index 1732a5a2..4109cc49 100644 --- a/lib/protocol/framer.js +++ b/lib/protocol/framer.js @@ -2,14 +2,15 @@ // the Serializer and the Deserializer // [1]: https://nodejs.org/api/stream.html#stream_class_stream_transform // [2]: https://nodejs.org/api/stream.html#stream_new_stream_readable_options -var assert = require('assert'); +var assert = require('../utils/assert-polyfil'); var Transform = require('readable-stream').Transform; exports.Serializer = Serializer; exports.Deserializer = Deserializer; -var logData = Boolean(process.env.HTTP2_LOG_DATA); +// DPW to see what to do +var logData = false; var MAX_PAYLOAD_SIZE = 16384; var WINDOW_UPDATE_PAYLOAD_SIZE = 4; diff --git a/lib/protocol/index.js b/lib/protocol/index.js index 0f3720e2..1fadd5ee 100644 --- a/lib/protocol/index.js +++ b/lib/protocol/index.js @@ -41,13 +41,13 @@ exports.VERSION = 'h2'; exports.Endpoint = require('./endpoint').Endpoint; /* Bunyan serializers exported by submodules that are worth adding when creating a logger. */ -exports.serializers = {}; -var modules = ['./framer', './compressor', './flow', './connection', './stream', './endpoint']; -modules.map(require).forEach(function(module) { - for (var name in module.serializers) { - exports.serializers[name] = module.serializers[name]; - } -}); +// exports.serializers = {}; +// var modules = ['./framer', './compressor', './flow', './connection', './stream', './endpoint']; +// modules.map(require).forEach(function(module) { +// for (var name in module.serializers) { +// exports.serializers[name] = module.serializers[name]; +// } +// }); /* Stream API Endpoint API diff --git a/lib/protocol/stream.js b/lib/protocol/stream.js index 719f6fb0..db783315 100644 --- a/lib/protocol/stream.js +++ b/lib/protocol/stream.js @@ -1,4 +1,4 @@ -var assert = require('assert'); +var assert = require('../utils/assert-polyfil'); // The Stream class // ================ diff --git a/lib/utils/assert-polyfil.js b/lib/utils/assert-polyfil.js new file mode 100644 index 00000000..ac61ef60 --- /dev/null +++ b/lib/utils/assert-polyfil.js @@ -0,0 +1,11 @@ +var env = require('./environment'); +module.exports = env.isNode() ? require('assert') : function (condition, message) { + // http://stackoverflow.com/questions/15313418/javascript-assert + if (!condition) { + message = message || "Assertion failed"; + if (typeof Error !== "undefined") { + throw new Error(message); + } + throw message; // Fallback + } + }; \ No newline at end of file diff --git a/lib/utils/environment.js b/lib/utils/environment.js new file mode 100644 index 00000000..f5f8e45b --- /dev/null +++ b/lib/utils/environment.js @@ -0,0 +1,8 @@ +// Check what enviroment it is + +var env = {}; +env.isNode = function () { + return (typeof window === 'undefined'); +}; + +module.exports = env; \ No newline at end of file diff --git a/package.json b/package.json index 74a38972..5ead36b1 100644 --- a/package.json +++ b/package.json @@ -3,11 +3,13 @@ "version": "3.3.6", "description": "An HTTP/2 client and server implementation", "main": "lib/index.js", - "engines" : { - "node" : ">=0.12.0" + "engines": { + "node": ">=0.12.0" }, "dependencies": { - "readable-stream": "2.2.2" + "readable-stream": "2.2.2", + "websocket-stream": "3.3.3", + "setimmediate": "1.0.5 " }, "devDependencies": { "k3po-exec": "0.0.x", @@ -16,8 +18,6 @@ "chai": "*", "mocha": "*", "docco": "*", - "websocket": "1.0.24", - "websocket-stream": "3.3.3", "browserify": "14.0.0 ", "bunyan": "*" }, From 8db7ca22e94129acd43680c42ffd01798643949f Mon Sep 17 00:00:00 2001 From: David Witherspoon Date: Fri, 17 Feb 2017 14:24:17 -0800 Subject: [PATCH 03/28] polyfil hack --- lib/protocol/compressor.js | 2 +- lib/protocol/endpoint.js | 4 ++-- lib/protocol/flow.js | 2 +- lib/protocol/framer.js | 2 +- lib/protocol/stream.js | 2 +- package.json | 1 + 6 files changed, 7 insertions(+), 6 deletions(-) diff --git a/lib/protocol/compressor.js b/lib/protocol/compressor.js index 9013eb35..2c7b4b9d 100644 --- a/lib/protocol/compressor.js +++ b/lib/protocol/compressor.js @@ -22,8 +22,8 @@ exports.HeaderSetDecompressor = HeaderSetDecompressor; exports.Compressor = Compressor; exports.Decompressor = Decompressor; -var TransformStream = require('readable-stream').Transform; var assert = require('../utils/assert-polyfil'); +var TransformStream = require('../utils/stream-polyfil').Transform; var util = require('util'); // Header compression diff --git a/lib/protocol/endpoint.js b/lib/protocol/endpoint.js index c5d0b9ae..c965a4b1 100644 --- a/lib/protocol/endpoint.js +++ b/lib/protocol/endpoint.js @@ -5,8 +5,8 @@ var Deserializer = require('./framer').Deserializer; var Compressor = require('./compressor').Compressor; var Decompressor = require('./compressor').Decompressor; var Connection = require('./connection').Connection; -var Duplex = require('readable-stream').Duplex; -var Transform = require('readable-stream').Transform; +var Duplex = require('../utils/stream-polyfil').Duplex; +var Transform = require('../utils/stream-polyfil').Transform; exports.Endpoint = Endpoint; diff --git a/lib/protocol/flow.js b/lib/protocol/flow.js index 13477cf3..387dd6dd 100644 --- a/lib/protocol/flow.js +++ b/lib/protocol/flow.js @@ -7,7 +7,7 @@ var assert = require('../utils/assert-polyfil'); // subclassed by [Connection](connection.html) and the `upstream` component of [Stream](stream.html). // [1]: https://nodejs.org/api/stream.html#stream_class_stream_duplex -var Duplex = require('readable-stream').Duplex; +var Duplex = require('../utils/stream-polyfil').Duplex; exports.Flow = Flow; diff --git a/lib/protocol/framer.js b/lib/protocol/framer.js index 4109cc49..17baf5de 100644 --- a/lib/protocol/framer.js +++ b/lib/protocol/framer.js @@ -4,7 +4,7 @@ // [2]: https://nodejs.org/api/stream.html#stream_new_stream_readable_options var assert = require('../utils/assert-polyfil'); -var Transform = require('readable-stream').Transform; +var Transform = require('../utils/stream-polyfil').Transform; exports.Serializer = Serializer; exports.Deserializer = Deserializer; diff --git a/lib/protocol/stream.js b/lib/protocol/stream.js index db783315..4cb04b09 100644 --- a/lib/protocol/stream.js +++ b/lib/protocol/stream.js @@ -9,7 +9,7 @@ var assert = require('../utils/assert-polyfil'); // object itself) and one that is used by a Connection to read/write frames to/from the other peer // (`stream.upstream`). -var Duplex = require('readable-stream').Duplex; +var Duplex = require('../utils/stream-polyfil').Duplex; exports.Stream = Stream; diff --git a/package.json b/package.json index 5ead36b1..ae2d553d 100644 --- a/package.json +++ b/package.json @@ -7,6 +7,7 @@ "node": ">=0.12.0" }, "dependencies": { + "readable-stream": "breaks at 2.1.5, but websocket stream needs 2.1.5", "readable-stream": "2.2.2", "websocket-stream": "3.3.3", "setimmediate": "1.0.5 " From f22f32722c47417ce8aa1bbea2832f320c9f1855 Mon Sep 17 00:00:00 2001 From: David Witherspoon Date: Fri, 17 Feb 2017 14:43:15 -0800 Subject: [PATCH 04/28] Fixed stupid polyfil with real browserify polyfil --- lib/http.js | 6 +++--- lib/http2-ws.js | 6 +++--- lib/protocol/compressor.js | 4 ++-- lib/protocol/connection.js | 2 +- lib/protocol/endpoint.js | 6 +++--- lib/protocol/flow.js | 4 ++-- lib/protocol/framer.js | 4 ++-- lib/protocol/stream.js | 4 ++-- package.json | 4 ++-- 9 files changed, 20 insertions(+), 20 deletions(-) diff --git a/lib/http.js b/lib/http.js index 3dab8451..4c4234c5 100644 --- a/lib/http.js +++ b/lib/http.js @@ -130,9 +130,9 @@ var net = require('net'); var url = require('url'); var util = require('util'); var EventEmitter = require('events').EventEmitter; -var PassThrough = require('readable-stream').PassThrough; -var Readable = require('readable-stream').Readable; -var Writable = require('readable-stream').Writable; +var PassThrough = require('stream').PassThrough; +var Readable = require('stream').Readable; +var Writable = require('stream').Writable; var protocol = require('./protocol'); var Endpoint = protocol.Endpoint; var http = require('http'); diff --git a/lib/http2-ws.js b/lib/http2-ws.js index f3dea8eb..18b3ef01 100644 --- a/lib/http2-ws.js +++ b/lib/http2-ws.js @@ -9,9 +9,9 @@ function http2ws(){ } -var PassThrough = require('readable-stream').PassThrough; -var Readable = require('readable-stream').Readable; -var Writable = require('readable-stream').Writable; +var PassThrough = require('stream').PassThrough; +var Readable = require('stream').Readable; +var Writable = require('stream').Writable; var protocol = require('./protocol'); var Endpoint = protocol.Endpoint; diff --git a/lib/protocol/compressor.js b/lib/protocol/compressor.js index 2c7b4b9d..7847172b 100644 --- a/lib/protocol/compressor.js +++ b/lib/protocol/compressor.js @@ -22,8 +22,8 @@ exports.HeaderSetDecompressor = HeaderSetDecompressor; exports.Compressor = Compressor; exports.Decompressor = Decompressor; -var assert = require('../utils/assert-polyfil'); -var TransformStream = require('../utils/stream-polyfil').Transform; +var assert = require('assert'); +var TransformStream = require('stream').Transform; var util = require('util'); // Header compression diff --git a/lib/protocol/connection.js b/lib/protocol/connection.js index e67004e1..2b86b7f1 100644 --- a/lib/protocol/connection.js +++ b/lib/protocol/connection.js @@ -1,4 +1,4 @@ -var assert = require('../utils/assert-polyfil'); +var assert = require('assert'); // The Connection class // ==================== diff --git a/lib/protocol/endpoint.js b/lib/protocol/endpoint.js index c965a4b1..a218db04 100644 --- a/lib/protocol/endpoint.js +++ b/lib/protocol/endpoint.js @@ -1,12 +1,12 @@ -var assert = require('../utils/assert-polyfil'); +var assert = require('assert'); var Serializer = require('./framer').Serializer; var Deserializer = require('./framer').Deserializer; var Compressor = require('./compressor').Compressor; var Decompressor = require('./compressor').Decompressor; var Connection = require('./connection').Connection; -var Duplex = require('../utils/stream-polyfil').Duplex; -var Transform = require('../utils/stream-polyfil').Transform; +var Duplex = require('stream').Duplex; +var Transform = require('stream').Transform; exports.Endpoint = Endpoint; diff --git a/lib/protocol/flow.js b/lib/protocol/flow.js index 387dd6dd..4ec5649b 100644 --- a/lib/protocol/flow.js +++ b/lib/protocol/flow.js @@ -1,4 +1,4 @@ -var assert = require('../utils/assert-polyfil'); +var assert = require('assert'); // The Flow class // ============== @@ -7,7 +7,7 @@ var assert = require('../utils/assert-polyfil'); // subclassed by [Connection](connection.html) and the `upstream` component of [Stream](stream.html). // [1]: https://nodejs.org/api/stream.html#stream_class_stream_duplex -var Duplex = require('../utils/stream-polyfil').Duplex; +var Duplex = require('stream').Duplex; exports.Flow = Flow; diff --git a/lib/protocol/framer.js b/lib/protocol/framer.js index 17baf5de..b890de5e 100644 --- a/lib/protocol/framer.js +++ b/lib/protocol/framer.js @@ -2,9 +2,9 @@ // the Serializer and the Deserializer // [1]: https://nodejs.org/api/stream.html#stream_class_stream_transform // [2]: https://nodejs.org/api/stream.html#stream_new_stream_readable_options -var assert = require('../utils/assert-polyfil'); +var assert = require('assert'); -var Transform = require('../utils/stream-polyfil').Transform; +var Transform = require('stream').Transform; exports.Serializer = Serializer; exports.Deserializer = Deserializer; diff --git a/lib/protocol/stream.js b/lib/protocol/stream.js index 4cb04b09..6d520b94 100644 --- a/lib/protocol/stream.js +++ b/lib/protocol/stream.js @@ -1,4 +1,4 @@ -var assert = require('../utils/assert-polyfil'); +var assert = require('assert'); // The Stream class // ================ @@ -9,7 +9,7 @@ var assert = require('../utils/assert-polyfil'); // object itself) and one that is used by a Connection to read/write frames to/from the other peer // (`stream.upstream`). -var Duplex = require('../utils/stream-polyfil').Duplex; +var Duplex = require('stream').Duplex; exports.Stream = Stream; diff --git a/package.json b/package.json index ae2d553d..78ad546a 100644 --- a/package.json +++ b/package.json @@ -7,8 +7,8 @@ "node": ">=0.12.0" }, "dependencies": { - "readable-stream": "breaks at 2.1.5, but websocket stream needs 2.1.5", - "readable-stream": "2.2.2", + "assert": "1.4.1", + "stream-browserify": "2.0.1", "websocket-stream": "3.3.3", "setimmediate": "1.0.5 " }, From 98324dbd2e32f4f9a2a4a5ffe995f187996fdbb9 Mon Sep 17 00:00:00 2001 From: David Witherspoon Date: Fri, 17 Feb 2017 14:45:42 -0800 Subject: [PATCH 05/28] Remove / undid pointless work --- lib/protocol/compressor.js | 2 +- lib/utils/assert-polyfil.js | 11 ----------- 2 files changed, 1 insertion(+), 12 deletions(-) delete mode 100644 lib/utils/assert-polyfil.js diff --git a/lib/protocol/compressor.js b/lib/protocol/compressor.js index 7847172b..3923a910 100644 --- a/lib/protocol/compressor.js +++ b/lib/protocol/compressor.js @@ -22,8 +22,8 @@ exports.HeaderSetDecompressor = HeaderSetDecompressor; exports.Compressor = Compressor; exports.Decompressor = Decompressor; -var assert = require('assert'); var TransformStream = require('stream').Transform; +var assert = require('assert'); var util = require('util'); // Header compression diff --git a/lib/utils/assert-polyfil.js b/lib/utils/assert-polyfil.js deleted file mode 100644 index ac61ef60..00000000 --- a/lib/utils/assert-polyfil.js +++ /dev/null @@ -1,11 +0,0 @@ -var env = require('./environment'); -module.exports = env.isNode() ? require('assert') : function (condition, message) { - // http://stackoverflow.com/questions/15313418/javascript-assert - if (!condition) { - message = message || "Assertion failed"; - if (typeof Error !== "undefined") { - throw new Error(message); - } - throw message; // Fallback - } - }; \ No newline at end of file From c788b276e000d461291576a0fbefd5cf4f909eed Mon Sep 17 00:00:00 2001 From: David Witherspoon Date: Fri, 17 Feb 2017 14:50:18 -0800 Subject: [PATCH 06/28] Fixed logData call to work in non-node environments --- lib/protocol/framer.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/protocol/framer.js b/lib/protocol/framer.js index b890de5e..147ad8e0 100644 --- a/lib/protocol/framer.js +++ b/lib/protocol/framer.js @@ -10,7 +10,7 @@ exports.Serializer = Serializer; exports.Deserializer = Deserializer; // DPW to see what to do -var logData = false; +var logData = (process !== 'undefined' && process.env !== 'undefined' && process.env.HTTP2_LOG_DATA); var MAX_PAYLOAD_SIZE = 16384; var WINDOW_UPDATE_PAYLOAD_SIZE = 4; From fd845a7831a42259c1a3f25cfc9a0debcdfd2904 Mon Sep 17 00:00:00 2001 From: David Witherspoon Date: Fri, 17 Feb 2017 15:02:26 -0800 Subject: [PATCH 07/28] Allowed call to Bunyan (node) not to break non node envs, add url to dependencies --- lib/protocol/index.js | 20 +++++++++++++------- package.json | 1 + 2 files changed, 14 insertions(+), 7 deletions(-) diff --git a/lib/protocol/index.js b/lib/protocol/index.js index 1fadd5ee..46a079f9 100644 --- a/lib/protocol/index.js +++ b/lib/protocol/index.js @@ -41,13 +41,19 @@ exports.VERSION = 'h2'; exports.Endpoint = require('./endpoint').Endpoint; /* Bunyan serializers exported by submodules that are worth adding when creating a logger. */ -// exports.serializers = {}; -// var modules = ['./framer', './compressor', './flow', './connection', './stream', './endpoint']; -// modules.map(require).forEach(function(module) { -// for (var name in module.serializers) { -// exports.serializers[name] = module.serializers[name]; -// } -// }); + +exports.serializers = {}; +var modules = ['./framer', './compressor', './flow', './connection', './stream', './endpoint']; +try { + modules.map(require).forEach(function (module) { + for (var name in module.serializers) { + exports.serializers[name] = module.serializers[name]; + } + }); +} catch (e) { + // NOOP, probably in browser +} + /* Stream API Endpoint API diff --git a/package.json b/package.json index 78ad546a..05efaf33 100644 --- a/package.json +++ b/package.json @@ -10,6 +10,7 @@ "assert": "1.4.1", "stream-browserify": "2.0.1", "websocket-stream": "3.3.3", + "url": "0.11.0", "setimmediate": "1.0.5 " }, "devDependencies": { From b5cd8c812361511cdec3ece39fb4f323a4ece983 Mon Sep 17 00:00:00 2001 From: David Witherspoon Date: Fri, 17 Feb 2017 15:34:57 -0800 Subject: [PATCH 08/28] moved _.extend to Object.assign --- lib/http.js | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/lib/http.js b/lib/http.js index 4c4234c5..984453d2 100644 --- a/lib/http.js +++ b/lib/http.js @@ -429,7 +429,7 @@ function forwardEvent(event, source, target) { // ------------ function Server(options) { - options = util._extend({}, options); + options = Object.assign({}, options); this._log = (options.log || defaultLogger).child({ component: 'http' }); this._settings = options.settings; @@ -766,7 +766,7 @@ OutgoingResponse.prototype.push = function push(options) { throw new Error('`path` option is mandatory.'); } - var promise = util._extend({ + var promise = Object.assign({ ':method': (options.method || 'GET').toUpperCase(), ':scheme': (options.protocol && options.protocol.slice(0, -1)) || this._requestHeaders[':scheme'], ':authority': options.hostname || options.host || this._requestHeaders[':authority'], @@ -817,7 +817,7 @@ function requestRaw(options, callback) { throw new Error('This interface only supports http-schemed URLs'); } if (options.agent && typeof(options.agent.request) === 'function') { - var agentOptions = util._extend({}, options); + var agentOptions = Object.assign({}, options); delete agentOptions.agent; return options.agent.request(agentOptions, callback); } @@ -833,7 +833,7 @@ function requestTLS(options, callback) { throw new Error('This interface only supports https-schemed URLs'); } if (options.agent && typeof(options.agent.request) === 'function') { - var agentOptions = util._extend({}, options); + var agentOptions = Object.assign({}, options); delete agentOptions.agent; return options.agent.request(agentOptions, callback); } @@ -849,7 +849,7 @@ function getRaw(options, callback) { throw new Error('This interface only supports http-schemed URLs'); } if (options.agent && typeof(options.agent.get) === 'function') { - var agentOptions = util._extend({}, options); + var agentOptions = Object.assign({}, options); delete agentOptions.agent; return options.agent.get(agentOptions, callback); } @@ -865,7 +865,7 @@ function getTLS(options, callback) { throw new Error('This interface only supports https-schemed URLs'); } if (options.agent && typeof(options.agent.get) === 'function') { - var agentOptions = util._extend({}, options); + var agentOptions = Object.assign({}, options); delete agentOptions.agent; return options.agent.get(agentOptions, callback); } @@ -879,7 +879,7 @@ function Agent(options) { EventEmitter.call(this); this.setMaxListeners(0); - options = util._extend({}, options); + options = Object.assign({}, options); this._settings = options.settings; this._log = (options.log || defaultLogger).child({ component: 'http' }); @@ -902,7 +902,7 @@ Agent.prototype.request = function request(options, callback) { if (typeof options === 'string') { options = url.parse(options); } else { - options = util._extend({}, options); + options = Object.assign({}, options); } options.method = (options.method || 'GET').toUpperCase(); From d413a93bbb702f07e97efbe6701e85b338033f62 Mon Sep 17 00:00:00 2001 From: David Witherspoon Date: Fri, 17 Feb 2017 17:29:46 -0800 Subject: [PATCH 09/28] Appears to be working ok --- hand-test.js | 18 +++ index.js | 9 ++ integration-test/http2-ws-itest.js | 42 +++---- integration-test/http2-ws-itest2.js | 33 +++++ .../src/test/scripts/http2.get.over.ws.rpt | 18 +-- .../src/test/scripts/http2.get.over.ws2.rpt | 118 ++++++++++++++++++ lib/http.js | 84 ++++++++++++- lib/protocol/connection.js | 3 +- lib/protocol/endpoint.js | 3 +- lib/protocol/flow.js | 3 +- package.json | 5 +- test/http.js | 67 ++++++++++ test/stream.js | 3 +- 13 files changed, 369 insertions(+), 37 deletions(-) create mode 100644 hand-test.js create mode 100644 index.js create mode 100644 integration-test/http2-ws-itest2.js create mode 100644 integration-test/k3po/src/test/scripts/http2.get.over.ws2.rpt diff --git a/hand-test.js b/hand-test.js new file mode 100644 index 00000000..34a1c338 --- /dev/null +++ b/hand-test.js @@ -0,0 +1,18 @@ +var request = http2.raw.request({ + plain: true, + host: 'localhost', + port: 8080, + transport: function(){ + console.log("DPW got called here"); + return websocket('ws://localhost:8080/echo'); + } + }, function(response) { + console.log("Response with: " + response.statusCode); + response.on('data', function(data) { + console.log(" " + data); + }); + }); + request.end(); +}); + + diff --git a/index.js b/index.js new file mode 100644 index 00000000..ac476446 --- /dev/null +++ b/index.js @@ -0,0 +1,9 @@ +var http = require('./lib/http'); +var timers = require('timers'); +var setImmediate = require('timers').setImmediate; + + +websocket = require('websocket-stream'); +window.http2 = {}; +window.http2.raw = {}; +window.http2.raw.request = http.raw.request; diff --git a/integration-test/http2-ws-itest.js b/integration-test/http2-ws-itest.js index bbf0cad5..e4f76d25 100644 --- a/integration-test/http2-ws-itest.js +++ b/integration-test/http2-ws-itest.js @@ -1,21 +1,21 @@ -var expect = require('chai').expect; -var http2ws = require('../lib/http2-ws'); - -describe('http2-cache', function () { - - // TODO consider doing browser testing - // if (browserConfig) { - // browserConfig.origin('http://localhost:8080').addResource("http://chaijs.com/chai.js"); - // } - - it('http2.get.over.ws', function (done) { - // DPW TODO proper handling of URL - // http2ws.get({'host': 'localhost', 'port': 8080, 'transport':'ws://localhost:8080/echo'}, function(response){ - // console.log(response); - // done(); - // }) - console.log("waiting for ever !!!"); - }); - -}); - +// var expect = require('chai').expect; +// var http2ws = require('../lib/http2-ws'); +// +// describe('http2-cache', function () { +// +// // TODO consider doing browser testing +// // if (browserConfig) { +// // browserConfig.origin('http://localhost:8080').addResource("http://chaijs.com/chai.js"); +// // } +// +// it('http2.get.over.ws', function (done) { +// // DPW TODO proper handling of URL +// // http2ws.get({'host': 'localhost', 'port': 8080, 'transport':'ws://localhost:8080/echo'}, function(response){ +// // console.log(response); +// // done(); +// // }) +// console.log("waiting for ever !!!"); +// }); +// +// }); +// diff --git a/integration-test/http2-ws-itest2.js b/integration-test/http2-ws-itest2.js new file mode 100644 index 00000000..444137db --- /dev/null +++ b/integration-test/http2-ws-itest2.js @@ -0,0 +1,33 @@ +var expect = require('chai').expect; +var http2 = require('../lib/http'); +var websocket = require('websocket-stream'); + +describe('http2-cache', function () { + + // TODO consider doing browser testing + // if (browserConfig) { + // browserConfig.origin('http://localhost:8080').addResource("http://chaijs.com/chai.js"); + // } +console.log("start"); + it('http2.get.over.ws', function (done) { + // var request = http2.raw.request({ + // plain: true, + // host: 'localhost', + // port: 8080, + // transport: function(){ + // console.log("DPW got called here"); + // return websocket('ws://localhost:8080/echo'); + // } + // }, function(response) { + // console.log("Response with: " + response.statusCode); + // response.on('data', function(data) { + // console.log(" " + data); + // done(); + // }); + // }); + // request.end(); + console.log("waiting for ever"); + }); + +}); + diff --git a/integration-test/k3po/src/test/scripts/http2.get.over.ws.rpt b/integration-test/k3po/src/test/scripts/http2.get.over.ws.rpt index b9228a1b..a5d8e9fc 100644 --- a/integration-test/k3po/src/test/scripts/http2.get.over.ws.rpt +++ b/integration-test/k3po/src/test/scripts/http2.get.over.ws.rpt @@ -60,24 +60,24 @@ read [0x82 0x89] ([0..4] :readMask) read option mask ${readMask} read [0x00 0x00 0x0b] # length = 11 read [0x01] # type = headers -read [0x04] # flags +read [0x05] # flags read [0x00 0x00 0x00 0x01] # stream id = 1 read option mask [0x00 0x00 0x00 0x00] # Authority read [0x82 0x8b] ([0..4] :readMask) read option mask ${readMask} -read [0x86 0x82 0x41 0x86 0xa0 0xe4 0x1d 0x13 0x9d 0x09 0x84] +read [0x87 0x82 0x41 0x86 0xa0 0xe4 0x1d 0x13 0x9d 0x09 0x84] read option mask [0x00 0x00 0x00 0x00] # Content (0 length) -read [0x82 0x89] ([0..4] :readMask) -read option mask ${readMask} -read [0x00 0x00 0x00] # length = 0 -read [0x00] # type = data -read [0x01] -read [0x00 0x00 0x00 0x01] # stream id = 1 -read option mask [0x00 0x00 0x00 0x00] +#read [0x82 0x89] ([0..4] :readMask) +#read option mask ${readMask} +#read [0x00 0x00 0x00] # length = 0 +#read [0x00] # type = data +#read [0x01] +#read [0x00 0x00 0x00 0x01] # stream id = 1 +#read option mask [0x00 0x00 0x00 0x00] ### Response HEADERS diff --git a/integration-test/k3po/src/test/scripts/http2.get.over.ws2.rpt b/integration-test/k3po/src/test/scripts/http2.get.over.ws2.rpt new file mode 100644 index 00000000..b9228a1b --- /dev/null +++ b/integration-test/k3po/src/test/scripts/http2.get.over.ws2.rpt @@ -0,0 +1,118 @@ +# +# Copyright 2007-2015, Kaazing Corporation. All rights reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +property location 'http://localhost:8080/echo' +accept ${location} +accepted +connected + +read method "GET" +read version "HTTP/1.1" +read header "Host" "localhost:8080" +read header "Upgrade" /(?i:websocket)/ +read header "Connection" /(?i:Upgrade)/ +read header "Sec-WebSocket-Key" /(?[a-zA-Z0-9+\/=]{24})/ +read header "Sec-WebSocket-Version" "13" + +write status "101" "Switching Protocols" +write version "HTTP/1.1" +write header "Upgrade" "websocket" +write header "Connection" "Upgrade" +write header "Sec-WebSocket-Accept" ${ws:handshakeHash(key)} + +# HTTP 2 Client Connection Preface +read [0x82 0x98] ([0..4] :readMask) +read option mask ${readMask} +read "PRI * HTTP/2.0\r\n\r\nSM\r\n\r\n" +read option mask [0x00 0x00 0x00 0x00] + +# HTTP 2 Settings +read [0x82 0x89] ([0..4] :readMask) +read option mask ${readMask} +read [0x00 0x00 0x00] +read [0x04] +read [0x00] +read [0x00 0x00 0x00 0x00] +read option mask [0x00 0x00 0x00 0x00] + +# Ack Settings +write [0x82 0x09] +write [0x00 0x00 0x00] +write [0x04] +write [0x01] +write [0x00 0x00 0x00 0x00] + +# HTTP 2 Headers +read [0x82 0x89] ([0..4] :readMask) +read option mask ${readMask} +read [0x00 0x00 0x0b] # length = 11 +read [0x01] # type = headers +read [0x04] # flags +read [0x00 0x00 0x00 0x01] # stream id = 1 +read option mask [0x00 0x00 0x00 0x00] + + # Authority +read [0x82 0x8b] ([0..4] :readMask) +read option mask ${readMask} +read [0x86 0x82 0x41 0x86 0xa0 0xe4 0x1d 0x13 0x9d 0x09 0x84] +read option mask [0x00 0x00 0x00 0x00] + +# Content (0 length) +read [0x82 0x89] ([0..4] :readMask) +read option mask ${readMask} +read [0x00 0x00 0x00] # length = 0 +read [0x00] # type = data +read [0x01] +read [0x00 0x00 0x00 0x01] # stream id = 1 +read option mask [0x00 0x00 0x00 0x00] + + +### Response HEADERS + +# HEADERS frame +# ; END_HEADERS +# (padlen=0) +# ; First response header +# :status: 404 +# server: nghttpd nghttp2/1.19.0 +# date: Wed, 01 Feb 2017 19:12:46 GMT +# content-type: text/html; charset=UTF-8 +# content-length: 147 +write [0x82 0x4E] +write [0x00 0x00 0x45 0x01 0x04 0x00 0x00 0x00 0x01 0x8d 0x76 0x90 0xaa 0x69 0xd2 0x9a] + [0xe4 0x52 0xa9 0xa7 0x4a 0x6b 0x13 0x01 0x5c 0x2f 0xae 0x0f 0x61 0x96 0xe4 0x59] + [0x3e 0x94 0x00 0x54 0xc2 0x58 0xd4 0x10 0x02 0xea 0x81 0x7e 0xe0 0x45 0x71 0xa7] + [0x14 0xc5 0xa3 0x7f 0x5f 0x92 0x49 0x7c 0xa5 0x89 0xd3 0x4d 0x1f 0x6a 0x12 0x71] + [0xd8 0x82 0xa6 0x0e 0x1b 0xf0 0xac 0xf7 0x0f 0x0d 0x03 0x31 0x34 0x37] + +# DATA frame +# ; END_STREAM +# 404 Not Found

404 Not Found


nghttpd nghttp2/1.19.0 at port 8080
+write [0x82 0x7e 0x00 0x9c] +write [0x00 0x00 0x93] + [0x00] + [0x01] + [0x00 0x00 0x00 0x01] + [0x3c 0x68 0x74 0x6d 0x6c 0x3e 0x3c 0x68 0x65] + [0x61 0x64 0x3e 0x3c 0x74 0x69 0x74 0x6c 0x65 0x3e 0x34 0x30 0x34 0x20 0x4e 0x6f] + [0x74 0x20 0x46 0x6f 0x75 0x6e 0x64 0x3c 0x2f 0x74 0x69 0x74 0x6c 0x65 0x3e 0x3c] + [0x2f 0x68 0x65 0x61 0x64 0x3e 0x3c 0x62 0x6f 0x64 0x79 0x3e 0x3c 0x68 0x31 0x3e] + [0x34 0x30 0x34 0x20 0x4e 0x6f 0x74 0x20 0x46 0x6f 0x75 0x6e 0x64 0x3c 0x2f 0x68] + [0x31 0x3e 0x3c 0x68 0x72 0x3e 0x3c 0x61 0x64 0x64 0x72 0x65 0x73 0x73 0x3e 0x6e] + [0x67 0x68 0x74 0x74 0x70 0x64 0x20 0x6e 0x67 0x68 0x74 0x74 0x70 0x32 0x2f 0x31] + [0x2e 0x31 0x39 0x2e 0x30 0x20 0x61 0x74 0x20 0x70 0x6f 0x72 0x74 0x20 0x38 0x30] + [0x38 0x30 0x3c 0x2f 0x61 0x64 0x64 0x72 0x65 0x73 0x73 0x3e 0x3c 0x2f 0x62 0x6f] + [0x64 0x79 0x3e 0x3c 0x2f 0x68 0x74 0x6d 0x6c 0x3e] \ No newline at end of file diff --git a/lib/http.js b/lib/http.js index 984453d2..82cd472d 100644 --- a/lib/http.js +++ b/lib/http.js @@ -135,10 +135,63 @@ var Readable = require('stream').Readable; var Writable = require('stream').Writable; var protocol = require('./protocol'); var Endpoint = protocol.Endpoint; -var http = require('http'); var https = require('https'); -exports.STATUS_CODES = http.STATUS_CODES; +var statusCodes = {}; +statusCodes[exports.ACCEPTED = 202] = "Accepted"; +statusCodes[exports.BAD_GATEWAY = 502] = "Bad Gateway"; +statusCodes[exports.BAD_REQUEST = 400] = "Bad Request"; +statusCodes[exports.CONFLICT = 409] = "Conflict"; +statusCodes[exports.CONTINUE = 100] = "Continue"; +statusCodes[exports.CREATED = 201] = "Created"; +statusCodes[exports.EXPECTATION_FAILED = 417] = "Expectation Failed"; +statusCodes[exports.FAILED_DEPENDENCY = 424] = "Failed Dependency"; +statusCodes[exports.FORBIDDEN = 403] = "Forbidden"; +statusCodes[exports.GATEWAY_TIMEOUT = 504] = "Gateway Timeout"; +statusCodes[exports.GONE = 410] = "Gone"; +statusCodes[exports.HTTP_VERSION_NOT_SUPPORTED = 505] = "HTTP Version Not Supported"; +statusCodes[exports.INSUFFICIENT_SPACE_ON_RESOURCE = 419] = "Insufficient Space on Resource"; +statusCodes[exports.INSUFFICIENT_STORAGE = 507] = "Insufficient Storage"; +statusCodes[exports.INTERNAL_SERVER_ERROR = 500] = "Server Error"; +statusCodes[exports.LENGTH_REQUIRED = 411] = "Length Required"; +statusCodes[exports.LOCKED = 423] = "Locked"; +statusCodes[exports.METHOD_FAILURE = 420] = "Method Failure"; +statusCodes[exports.METHOD_NOT_ALLOWED = 405] = "Method Not Allowed"; +statusCodes[exports.MOVED_PERMANENTLY = 301] = "Moved Permanently"; +statusCodes[exports.MOVED_TEMPORARILY = 302] = "Moved Temporarily"; +statusCodes[exports.MULTI_STATUS = 207] = "Multi-Status"; +statusCodes[exports.MULTIPLE_CHOICES = 300] = "Multiple Choices"; +statusCodes[exports.NETWORK_AUTHENTICATION_REQUIRED = 511] = "Network Authentication Required"; +statusCodes[exports.NO_CONTENT = 204] = "No Content"; +statusCodes[exports.NON_AUTHORITATIVE_INFORMATION = 203] = "Non Authoritative Information"; +statusCodes[exports.NOT_ACCEPTABLE = 406] = "Not Acceptable"; +statusCodes[exports.NOT_FOUND = 404] = "Not Found"; +statusCodes[exports.NOT_IMPLEMENTED = 501] = "Not Implemented"; +statusCodes[exports.NOT_MODIFIED = 304] = "Not Modified"; +statusCodes[exports.OK = 200] = "OK"; +statusCodes[exports.PARTIAL_CONTENT = 206] = "Partial Content"; +statusCodes[exports.PAYMENT_REQUIRED = 402] = "Payment Required"; +statusCodes[exports.PERMANENT_REDIRECT = 308] = "Permanent Redirect"; +statusCodes[exports.PRECONDITION_FAILED = 412] = "Precondition Failed"; +statusCodes[exports.PRECONDITION_REQUIRED = 428] = "Precondition Required"; +statusCodes[exports.PROCESSING = 102] = "Processing"; +statusCodes[exports.PROXY_AUTHENTICATION_REQUIRED = 407] = "Proxy Authentication Required"; +statusCodes[exports.REQUEST_HEADER_FIELDS_TOO_LARGE = 431] = "Request Header Fields Too Large"; +statusCodes[exports.REQUEST_TIMEOUT = 408] = "Request Timeout"; +statusCodes[exports.REQUEST_TOO_LONG = 413] = "Request Entity Too Large"; +statusCodes[exports.REQUEST_URI_TOO_LONG = 414] = "Request-URI Too Long"; +statusCodes[exports.REQUESTED_RANGE_NOT_SATISFIABLE = 416] = "Requested Range Not Satisfiable"; +statusCodes[exports.RESET_CONTENT = 205] = "Reset Content"; +statusCodes[exports.SEE_OTHER = 303] = "See Other"; +statusCodes[exports.SERVICE_UNAVAILABLE = 503] = "Service Unavailable"; +statusCodes[exports.SWITCHING_PROTOCOLS = 101] = "Switching Protocols"; +statusCodes[exports.TEMPORARY_REDIRECT = 307] = "Temporary Redirect"; +statusCodes[exports.TOO_MANY_REQUESTS = 429] = "Too Many Requests"; +statusCodes[exports.UNAUTHORIZED = 401] = "Unauthorized"; +statusCodes[exports.UNPROCESSABLE_ENTITY = 422] = "Unprocessable Entity"; +statusCodes[exports.UNSUPPORTED_MEDIA_TYPE = 415] = "Unsupported Media Type"; +statusCodes[exports.USE_PROXY = 305] = "Use Proxy"; +exports.STATUS_CODES = statusCodes; exports.IncomingMessage = IncomingMessage; exports.OutgoingMessage = OutgoingMessage; exports.protocol = protocol; @@ -464,7 +517,15 @@ function Server(options) { forwardEvent('listening', this._server, this); } + // HTTP2 over generic socket server + else if (options.transport) { + this._log.info('Creating HTTP/2 server over generic stream'); + this._mode = 'plain'; + this._server = options.transport(start); + } + // HTTP2 over plain TCP + // TODO Deprecate else if (options.plain) { this._log.info('Creating HTTP/2 server over plain TCP'); this._mode = 'plain'; @@ -935,7 +996,26 @@ Agent.prototype.request = function request(options, callback) { request._start(endpoint.createStream(), options); } + // * HTTP/2 over generic stream transport + else if(options.transport) { + endpoint = new Endpoint(this._log, 'CLIENT', this._settings); + endpoint.socket = options.transport(); + + endpoint.socket.on('error', function (error) { + self._log.error('Socket error: ' + error.toString()); + request.emit('error', error); + }); + + endpoint.on('error', function(error){ + self._log.error('Connection error: ' + error.toString()); + request.emit('error', error); + }); + this.endpoints[key] = endpoint; + endpoint.pipe(endpoint.socket).pipe(endpoint); + request._start(endpoint.createStream(), options); +} // * HTTP/2 over plain TCP + // TODO depricate else if (options.plain) { endpoint = new Endpoint(this._log, 'CLIENT', this._settings); endpoint.socket = net.connect({ diff --git a/lib/protocol/connection.js b/lib/protocol/connection.js index 2b86b7f1..8a5fa511 100644 --- a/lib/protocol/connection.js +++ b/lib/protocol/connection.js @@ -8,6 +8,7 @@ var assert = require('assert'); // [Flow](flow.html) subclass. var Flow = require('./flow').Flow; +var timers = require('timers'); exports.Connection = Connection; @@ -272,7 +273,7 @@ Connection.prototype._send = function _send(immediate) { } else { if (!this._sendScheduled) { this._sendScheduled = true; - setImmediate(this._send.bind(this, true)); + timers.setImmediate(this._send.bind(this, true)); } return; } diff --git a/lib/protocol/endpoint.js b/lib/protocol/endpoint.js index a218db04..fece9ac5 100644 --- a/lib/protocol/endpoint.js +++ b/lib/protocol/endpoint.js @@ -7,6 +7,7 @@ var Decompressor = require('./compressor').Decompressor; var Connection = require('./connection').Connection; var Duplex = require('stream').Duplex; var Transform = require('stream').Transform; +var timers = require('timers'); exports.Endpoint = Endpoint; @@ -240,7 +241,7 @@ Endpoint.prototype._initializeErrorHandling = function _initializeErrorHandling( Endpoint.prototype._error = function _error(component, error) { this._log.fatal({ source: component, message: error }, 'Fatal error, closing connection'); this.close(error); - setImmediate(this.emit.bind(this, 'error', error)); + timers.setImmediate(this.emit.bind(this, 'error', error)); }; Endpoint.prototype.close = function close(error) { diff --git a/lib/protocol/flow.js b/lib/protocol/flow.js index 4ec5649b..9529c0a3 100644 --- a/lib/protocol/flow.js +++ b/lib/protocol/flow.js @@ -1,4 +1,5 @@ var assert = require('assert'); +var timers = require('timers'); // The Flow class // ============== @@ -91,7 +92,7 @@ Flow.prototype._write = function _write(frame, encoding, callback) { this._receive(frame, function() { this._received += frame.data.length; if (!this._restoreWindowTimer) { - this._restoreWindowTimer = setImmediate(this._restoreWindow.bind(this)); + this._restoreWindowTimer = timers.setImmediate(this._restoreWindow.bind(this)); } callback(); }.bind(this)); diff --git a/package.json b/package.json index 05efaf33..2425251a 100644 --- a/package.json +++ b/package.json @@ -11,7 +11,10 @@ "stream-browserify": "2.0.1", "websocket-stream": "3.3.3", "url": "0.11.0", - "setimmediate": "1.0.5 " + "setimmediate": "1.0.5", + "https-browserify": "0.0.1", + "timers-browserify": "2.0.2", + "events": "1.1.1" }, "devDependencies": { "k3po-exec": "0.0.x", diff --git a/test/http.js b/test/http.js index 95a074e4..fb09982e 100644 --- a/test/http.js +++ b/test/http.js @@ -7,6 +7,8 @@ var net = require('net'); var http2 = require('../lib/http'); var https = require('https'); +var http = require('http'); +var websocket = require('websocket-stream'); var serverOptions = { key: fs.readFileSync(path.join(__dirname, '../example/localhost.key')), @@ -390,6 +392,71 @@ describe('http.js', function() { }); }); }); + // DPW ADDED -- TODO Move below to best section + describe('request over plain WS', function() { + it.skip('should work as expected', function(done) { + var path = '/x'; + var message = 'Hello world'; + + var server = http2.raw.createServer({ + log: util.serverLog, + transport: function(options){ + var server = http.createServer(options); + var wss = websocket.createServer({ + server: server + }); + return wss; // HTTP2 over plain TCP + } + }, function(request, response) { + expect(request.url).to.equal(path); + response.end(message); + }); + + server.listen(1237, function() { + var request = http2.raw.request({ + plain: true, + host: 'localhost', + port: 1237, + path: path, + transport: function(){ + console.log(ok); + } + }, function(response) { + response.on('data', function(data) { + expect(data.toString()).to.equal(message); + server.close(); + done(); + }); + }); + request.end(); + }); + }); + }); + // describe('get over plain TCP', function() { + // it('should work as expected', function(done) { + // var path = '/x'; + // var message = 'Hello world'; + // + // var server = http2.raw.createServer({ + // log: util.serverLog + // }, function(request, response) { + // expect(request.url).to.equal(path); + // response.end(message); + // }); + // + // server.listen(1237, function() { + // var request = http2.raw.get('http://localhost:1237/x', function(response) { + // response.on('data', function(data) { + // expect(data.toString()).to.equal(message); + // server.close(); + // done(); + // }); + // }); + // request.end(); + // }); + // }); + // }); + // DPW ADDED STOPPED -- TODO Move above to best section describe('request over plain TCP', function() { it('should work as expected', function(done) { var path = '/x'; diff --git a/test/stream.js b/test/stream.js index 90e0ef64..e99f74c3 100644 --- a/test/stream.js +++ b/test/stream.js @@ -1,5 +1,6 @@ var expect = require('chai').expect; var util = require('./util'); +var timers = require('timers'); var stream = require('../lib/protocol/stream'); var Stream = stream.Stream; @@ -97,7 +98,7 @@ function execute_sequence(stream, sequence, done) { done(); } - setImmediate(execute.bind(null, check)); + timers.setImmediate(execute.bind(null, check)); } var example_frames = [ From 645a0474b410289950a000b13a73fdc1564cd24a Mon Sep 17 00:00:00 2001 From: David Witherspoon Date: Tue, 21 Feb 2017 16:47:44 -0800 Subject: [PATCH 10/28] Working http over ws server --- lib/http.js | 26 ++++++++++++++++---- test/http.js | 68 +++++++++++++++++++++++++--------------------------- 2 files changed, 54 insertions(+), 40 deletions(-) diff --git a/lib/http.js b/lib/http.js index 82cd472d..795aecc0 100644 --- a/lib/http.js +++ b/lib/http.js @@ -518,14 +518,30 @@ function Server(options) { } // HTTP2 over generic socket server - else if (options.transport) { - this._log.info('Creating HTTP/2 server over generic stream'); + // DPW TODO create generic stream + // else if (options.transport) { + // this._log.info('Creating HTTP/2 server over generic stream'); + // this._mode = 'plain'; + // this._server = options.transport(start); + // } + + // HTTP2 over plain WebSocket + else if (options.transport){ + var http = require('http'); + var websocket = require('websocket-stream'); + + this._log.info('Creating HTTP/2 server over plain WebSocket'); this._mode = 'plain'; - this._server = options.transport(start); - } + var httpServer = http.createServer(); + options.server = httpServer; + this._server = websocket.createServer(options, start); + this._server.listen = function(options, cb){ + httpServer.listen(options, cb); + } + + } // HTTP2 over plain TCP - // TODO Deprecate else if (options.plain) { this._log.info('Creating HTTP/2 server over plain TCP'); this._mode = 'plain'; diff --git a/test/http.js b/test/http.js index fb09982e..f9913bcc 100644 --- a/test/http.js +++ b/test/http.js @@ -394,32 +394,27 @@ describe('http.js', function() { }); // DPW ADDED -- TODO Move below to best section describe('request over plain WS', function() { - it.skip('should work as expected', function(done) { + it('should work as expected', function(done) { var path = '/x'; var message = 'Hello world'; + var portnum = 1239; var server = http2.raw.createServer({ log: util.serverLog, - transport: function(options){ - var server = http.createServer(options); - var wss = websocket.createServer({ - server: server - }); - return wss; // HTTP2 over plain TCP - } + transport: true }, function(request, response) { expect(request.url).to.equal(path); response.end(message); }); - server.listen(1237, function() { + server.listen(portnum, function() { var request = http2.raw.request({ plain: true, host: 'localhost', - port: 1237, + port: portnum, path: path, transport: function(){ - console.log(ok); + return websocket('ws://localhost:' + portnum); } }, function(response) { response.on('data', function(data) { @@ -432,30 +427,33 @@ describe('http.js', function() { }); }); }); - // describe('get over plain TCP', function() { - // it('should work as expected', function(done) { - // var path = '/x'; - // var message = 'Hello world'; - // - // var server = http2.raw.createServer({ - // log: util.serverLog - // }, function(request, response) { - // expect(request.url).to.equal(path); - // response.end(message); - // }); - // - // server.listen(1237, function() { - // var request = http2.raw.get('http://localhost:1237/x', function(response) { - // response.on('data', function(data) { - // expect(data.toString()).to.equal(message); - // server.close(); - // done(); - // }); - // }); - // request.end(); - // }); - // }); - // }); + describe('get over plain WS', function() { + it('should work as expected', function(done) { + var path = '/x'; + var message = 'Hello world'; + + var server = http2.raw.createServer({ + log: util.serverLog, + transport: true + }, function(request, response) { + expect(request.url).to.equal(path); + response.end(message); + }); + + server.listen(1239, function() { + var request = http2.raw.get({path : path, transport: function(){ + return websocket('ws://localhost:' + 1239); + }}, function(response) { + response.on('data', function(data) { + expect(data.toString()).to.equal(message); + server.close(); + done(); + }); + }); + request.end(); + }); + }); + }); // DPW ADDED STOPPED -- TODO Move above to best section describe('request over plain TCP', function() { it('should work as expected', function(done) { From 456ad16462c08378a4c50facb256012a61f3dbd5 Mon Sep 17 00:00:00 2001 From: David Witherspoon Date: Tue, 21 Feb 2017 16:55:49 -0800 Subject: [PATCH 11/28] test clean up --- test/http.js | 120 +++++++++++++++++++++++++-------------------------- 1 file changed, 59 insertions(+), 61 deletions(-) diff --git a/test/http.js b/test/http.js index f9913bcc..78ef172f 100644 --- a/test/http.js +++ b/test/http.js @@ -392,69 +392,67 @@ describe('http.js', function() { }); }); }); - // DPW ADDED -- TODO Move below to best section - describe('request over plain WS', function() { - it('should work as expected', function(done) { - var path = '/x'; - var message = 'Hello world'; - var portnum = 1239; - - var server = http2.raw.createServer({ - log: util.serverLog, - transport: true - }, function(request, response) { - expect(request.url).to.equal(path); - response.end(message); - }); - - server.listen(portnum, function() { - var request = http2.raw.request({ - plain: true, - host: 'localhost', - port: portnum, - path: path, - transport: function(){ - return websocket('ws://localhost:' + portnum); - } - }, function(response) { - response.on('data', function(data) { - expect(data.toString()).to.equal(message); - server.close(); - done(); - }); - }); - request.end(); - }); - }); - }); - describe('get over plain WS', function() { - it('should work as expected', function(done) { - var path = '/x'; - var message = 'Hello world'; + describe('request over plain WS', function() { + it('should work as expected', function(done) { + var path = '/x'; + var message = 'Hello world'; + var portnum = 1239; + + var server = http2.raw.createServer({ + log: util.serverLog, + transport: "websocket" + }, function(request, response) { + expect(request.url).to.equal(path); + response.end(message); + }); - var server = http2.raw.createServer({ - log: util.serverLog, - transport: true - }, function(request, response) { - expect(request.url).to.equal(path); - response.end(message); - }); + server.listen(portnum, function() { + var request = http2.raw.request({ + plain: true, + host: 'localhost', + port: portnum, + path: path, + transport: function(){ + return websocket('ws://localhost:' + portnum); + } + }, function(response) { + response.on('data', function(data) { + expect(data.toString()).to.equal(message); + server.close(); + done(); + }); + }); + request.end(); + }); + }); + }); + describe('get over plain WS', function() { + it('should work as expected', function(done) { + var path = '/x'; + var message = 'Hello world'; + + var server = http2.raw.createServer({ + log: util.serverLog, + transport: "websocket" + }, function(request, response) { + expect(request.url).to.equal(path); + response.end(message); + }); - server.listen(1239, function() { - var request = http2.raw.get({path : path, transport: function(){ - return websocket('ws://localhost:' + 1239); - }}, function(response) { - response.on('data', function(data) { - expect(data.toString()).to.equal(message); - server.close(); - done(); - }); - }); - request.end(); - }); - }); - }); - // DPW ADDED STOPPED -- TODO Move above to best section + server.listen(1243, function() { + var request = http2.raw.get({path : path, transport: function(){ + return websocket('ws://localhost:' + 1243); + }}, function(response) { + response.on('data', function(data) { + expect(data.toString()).to.equal(message); + server.close(); + done(); + }); + }); + request.end(); + }); + }); + }); describe('request over plain TCP', function() { it('should work as expected', function(done) { var path = '/x'; From 5e160ad7e54a466a65de27d7c4fa643c9bfb0455 Mon Sep 17 00:00:00 2001 From: David Witherspoon Date: Tue, 21 Feb 2017 17:02:15 -0800 Subject: [PATCH 12/28] Clean up temp files --- hand-test.js | 18 --- index.html | 17 --- index.js | 9 -- integration-test/http2-ws-itest.js | 21 ---- integration-test/http2-ws-itest2.js | 33 ----- integration-test/k3po/pom.xml | 41 ------ .../src/test/scripts/http2.get.over.ws.rpt | 118 ------------------ .../src/test/scripts/http2.get.over.ws2.rpt | 118 ------------------ lib/http.js | 11 +- 9 files changed, 3 insertions(+), 383 deletions(-) delete mode 100644 hand-test.js delete mode 100644 index.html delete mode 100644 index.js delete mode 100644 integration-test/http2-ws-itest.js delete mode 100644 integration-test/http2-ws-itest2.js delete mode 100644 integration-test/k3po/pom.xml delete mode 100644 integration-test/k3po/src/test/scripts/http2.get.over.ws.rpt delete mode 100644 integration-test/k3po/src/test/scripts/http2.get.over.ws2.rpt diff --git a/hand-test.js b/hand-test.js deleted file mode 100644 index 34a1c338..00000000 --- a/hand-test.js +++ /dev/null @@ -1,18 +0,0 @@ -var request = http2.raw.request({ - plain: true, - host: 'localhost', - port: 8080, - transport: function(){ - console.log("DPW got called here"); - return websocket('ws://localhost:8080/echo'); - } - }, function(response) { - console.log("Response with: " + response.statusCode); - response.on('data', function(data) { - console.log(" " + data); - }); - }); - request.end(); -}); - - diff --git a/index.html b/index.html deleted file mode 100644 index 353e6fde..00000000 --- a/index.html +++ /dev/null @@ -1,17 +0,0 @@ - - - - - - -

Let's go http2 over ws

- -

This is crazy

- - - - - - diff --git a/index.js b/index.js deleted file mode 100644 index ac476446..00000000 --- a/index.js +++ /dev/null @@ -1,9 +0,0 @@ -var http = require('./lib/http'); -var timers = require('timers'); -var setImmediate = require('timers').setImmediate; - - -websocket = require('websocket-stream'); -window.http2 = {}; -window.http2.raw = {}; -window.http2.raw.request = http.raw.request; diff --git a/integration-test/http2-ws-itest.js b/integration-test/http2-ws-itest.js deleted file mode 100644 index e4f76d25..00000000 --- a/integration-test/http2-ws-itest.js +++ /dev/null @@ -1,21 +0,0 @@ -// var expect = require('chai').expect; -// var http2ws = require('../lib/http2-ws'); -// -// describe('http2-cache', function () { -// -// // TODO consider doing browser testing -// // if (browserConfig) { -// // browserConfig.origin('http://localhost:8080').addResource("http://chaijs.com/chai.js"); -// // } -// -// it('http2.get.over.ws', function (done) { -// // DPW TODO proper handling of URL -// // http2ws.get({'host': 'localhost', 'port': 8080, 'transport':'ws://localhost:8080/echo'}, function(response){ -// // console.log(response); -// // done(); -// // }) -// console.log("waiting for ever !!!"); -// }); -// -// }); -// diff --git a/integration-test/http2-ws-itest2.js b/integration-test/http2-ws-itest2.js deleted file mode 100644 index 444137db..00000000 --- a/integration-test/http2-ws-itest2.js +++ /dev/null @@ -1,33 +0,0 @@ -var expect = require('chai').expect; -var http2 = require('../lib/http'); -var websocket = require('websocket-stream'); - -describe('http2-cache', function () { - - // TODO consider doing browser testing - // if (browserConfig) { - // browserConfig.origin('http://localhost:8080').addResource("http://chaijs.com/chai.js"); - // } -console.log("start"); - it('http2.get.over.ws', function (done) { - // var request = http2.raw.request({ - // plain: true, - // host: 'localhost', - // port: 8080, - // transport: function(){ - // console.log("DPW got called here"); - // return websocket('ws://localhost:8080/echo'); - // } - // }, function(response) { - // console.log("Response with: " + response.statusCode); - // response.on('data', function(data) { - // console.log(" " + data); - // done(); - // }); - // }); - // request.end(); - console.log("waiting for ever"); - }); - -}); - diff --git a/integration-test/k3po/pom.xml b/integration-test/k3po/pom.xml deleted file mode 100644 index 13b79a23..00000000 --- a/integration-test/k3po/pom.xml +++ /dev/null @@ -1,41 +0,0 @@ - - - 4.0.0 - org.reaktivity - nukleus-http2-test - pom - develop-SNAPSHOT - Runs K3PO with dependencies for HTTP 2 Spec - - - - - org.kaazing - k3po-maven-plugin - 3.0.0-alpha-54 - - - - start - stop - - - - - - org.kaazing - specification.ws - 3.0.0-alpha-54 - - - - - - - - - - diff --git a/integration-test/k3po/src/test/scripts/http2.get.over.ws.rpt b/integration-test/k3po/src/test/scripts/http2.get.over.ws.rpt deleted file mode 100644 index a5d8e9fc..00000000 --- a/integration-test/k3po/src/test/scripts/http2.get.over.ws.rpt +++ /dev/null @@ -1,118 +0,0 @@ -# -# Copyright 2007-2015, Kaazing Corporation. All rights reserved. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -# - -property location 'http://localhost:8080/echo' -accept ${location} -accepted -connected - -read method "GET" -read version "HTTP/1.1" -read header "Host" "localhost:8080" -read header "Upgrade" /(?i:websocket)/ -read header "Connection" /(?i:Upgrade)/ -read header "Sec-WebSocket-Key" /(?[a-zA-Z0-9+\/=]{24})/ -read header "Sec-WebSocket-Version" "13" - -write status "101" "Switching Protocols" -write version "HTTP/1.1" -write header "Upgrade" "websocket" -write header "Connection" "Upgrade" -write header "Sec-WebSocket-Accept" ${ws:handshakeHash(key)} - -# HTTP 2 Client Connection Preface -read [0x82 0x98] ([0..4] :readMask) -read option mask ${readMask} -read "PRI * HTTP/2.0\r\n\r\nSM\r\n\r\n" -read option mask [0x00 0x00 0x00 0x00] - -# HTTP 2 Settings -read [0x82 0x89] ([0..4] :readMask) -read option mask ${readMask} -read [0x00 0x00 0x00] -read [0x04] -read [0x00] -read [0x00 0x00 0x00 0x00] -read option mask [0x00 0x00 0x00 0x00] - -# Ack Settings -write [0x82 0x09] -write [0x00 0x00 0x00] -write [0x04] -write [0x01] -write [0x00 0x00 0x00 0x00] - -# HTTP 2 Headers -read [0x82 0x89] ([0..4] :readMask) -read option mask ${readMask} -read [0x00 0x00 0x0b] # length = 11 -read [0x01] # type = headers -read [0x05] # flags -read [0x00 0x00 0x00 0x01] # stream id = 1 -read option mask [0x00 0x00 0x00 0x00] - - # Authority -read [0x82 0x8b] ([0..4] :readMask) -read option mask ${readMask} -read [0x87 0x82 0x41 0x86 0xa0 0xe4 0x1d 0x13 0x9d 0x09 0x84] -read option mask [0x00 0x00 0x00 0x00] - -# Content (0 length) -#read [0x82 0x89] ([0..4] :readMask) -#read option mask ${readMask} -#read [0x00 0x00 0x00] # length = 0 -#read [0x00] # type = data -#read [0x01] -#read [0x00 0x00 0x00 0x01] # stream id = 1 -#read option mask [0x00 0x00 0x00 0x00] - - -### Response HEADERS - -# HEADERS frame -# ; END_HEADERS -# (padlen=0) -# ; First response header -# :status: 404 -# server: nghttpd nghttp2/1.19.0 -# date: Wed, 01 Feb 2017 19:12:46 GMT -# content-type: text/html; charset=UTF-8 -# content-length: 147 -write [0x82 0x4E] -write [0x00 0x00 0x45 0x01 0x04 0x00 0x00 0x00 0x01 0x8d 0x76 0x90 0xaa 0x69 0xd2 0x9a] - [0xe4 0x52 0xa9 0xa7 0x4a 0x6b 0x13 0x01 0x5c 0x2f 0xae 0x0f 0x61 0x96 0xe4 0x59] - [0x3e 0x94 0x00 0x54 0xc2 0x58 0xd4 0x10 0x02 0xea 0x81 0x7e 0xe0 0x45 0x71 0xa7] - [0x14 0xc5 0xa3 0x7f 0x5f 0x92 0x49 0x7c 0xa5 0x89 0xd3 0x4d 0x1f 0x6a 0x12 0x71] - [0xd8 0x82 0xa6 0x0e 0x1b 0xf0 0xac 0xf7 0x0f 0x0d 0x03 0x31 0x34 0x37] - -# DATA frame -# ; END_STREAM -# 404 Not Found

404 Not Found


nghttpd nghttp2/1.19.0 at port 8080
-write [0x82 0x7e 0x00 0x9c] -write [0x00 0x00 0x93] - [0x00] - [0x01] - [0x00 0x00 0x00 0x01] - [0x3c 0x68 0x74 0x6d 0x6c 0x3e 0x3c 0x68 0x65] - [0x61 0x64 0x3e 0x3c 0x74 0x69 0x74 0x6c 0x65 0x3e 0x34 0x30 0x34 0x20 0x4e 0x6f] - [0x74 0x20 0x46 0x6f 0x75 0x6e 0x64 0x3c 0x2f 0x74 0x69 0x74 0x6c 0x65 0x3e 0x3c] - [0x2f 0x68 0x65 0x61 0x64 0x3e 0x3c 0x62 0x6f 0x64 0x79 0x3e 0x3c 0x68 0x31 0x3e] - [0x34 0x30 0x34 0x20 0x4e 0x6f 0x74 0x20 0x46 0x6f 0x75 0x6e 0x64 0x3c 0x2f 0x68] - [0x31 0x3e 0x3c 0x68 0x72 0x3e 0x3c 0x61 0x64 0x64 0x72 0x65 0x73 0x73 0x3e 0x6e] - [0x67 0x68 0x74 0x74 0x70 0x64 0x20 0x6e 0x67 0x68 0x74 0x74 0x70 0x32 0x2f 0x31] - [0x2e 0x31 0x39 0x2e 0x30 0x20 0x61 0x74 0x20 0x70 0x6f 0x72 0x74 0x20 0x38 0x30] - [0x38 0x30 0x3c 0x2f 0x61 0x64 0x64 0x72 0x65 0x73 0x73 0x3e 0x3c 0x2f 0x62 0x6f] - [0x64 0x79 0x3e 0x3c 0x2f 0x68 0x74 0x6d 0x6c 0x3e] \ No newline at end of file diff --git a/integration-test/k3po/src/test/scripts/http2.get.over.ws2.rpt b/integration-test/k3po/src/test/scripts/http2.get.over.ws2.rpt deleted file mode 100644 index b9228a1b..00000000 --- a/integration-test/k3po/src/test/scripts/http2.get.over.ws2.rpt +++ /dev/null @@ -1,118 +0,0 @@ -# -# Copyright 2007-2015, Kaazing Corporation. All rights reserved. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -# - -property location 'http://localhost:8080/echo' -accept ${location} -accepted -connected - -read method "GET" -read version "HTTP/1.1" -read header "Host" "localhost:8080" -read header "Upgrade" /(?i:websocket)/ -read header "Connection" /(?i:Upgrade)/ -read header "Sec-WebSocket-Key" /(?[a-zA-Z0-9+\/=]{24})/ -read header "Sec-WebSocket-Version" "13" - -write status "101" "Switching Protocols" -write version "HTTP/1.1" -write header "Upgrade" "websocket" -write header "Connection" "Upgrade" -write header "Sec-WebSocket-Accept" ${ws:handshakeHash(key)} - -# HTTP 2 Client Connection Preface -read [0x82 0x98] ([0..4] :readMask) -read option mask ${readMask} -read "PRI * HTTP/2.0\r\n\r\nSM\r\n\r\n" -read option mask [0x00 0x00 0x00 0x00] - -# HTTP 2 Settings -read [0x82 0x89] ([0..4] :readMask) -read option mask ${readMask} -read [0x00 0x00 0x00] -read [0x04] -read [0x00] -read [0x00 0x00 0x00 0x00] -read option mask [0x00 0x00 0x00 0x00] - -# Ack Settings -write [0x82 0x09] -write [0x00 0x00 0x00] -write [0x04] -write [0x01] -write [0x00 0x00 0x00 0x00] - -# HTTP 2 Headers -read [0x82 0x89] ([0..4] :readMask) -read option mask ${readMask} -read [0x00 0x00 0x0b] # length = 11 -read [0x01] # type = headers -read [0x04] # flags -read [0x00 0x00 0x00 0x01] # stream id = 1 -read option mask [0x00 0x00 0x00 0x00] - - # Authority -read [0x82 0x8b] ([0..4] :readMask) -read option mask ${readMask} -read [0x86 0x82 0x41 0x86 0xa0 0xe4 0x1d 0x13 0x9d 0x09 0x84] -read option mask [0x00 0x00 0x00 0x00] - -# Content (0 length) -read [0x82 0x89] ([0..4] :readMask) -read option mask ${readMask} -read [0x00 0x00 0x00] # length = 0 -read [0x00] # type = data -read [0x01] -read [0x00 0x00 0x00 0x01] # stream id = 1 -read option mask [0x00 0x00 0x00 0x00] - - -### Response HEADERS - -# HEADERS frame -# ; END_HEADERS -# (padlen=0) -# ; First response header -# :status: 404 -# server: nghttpd nghttp2/1.19.0 -# date: Wed, 01 Feb 2017 19:12:46 GMT -# content-type: text/html; charset=UTF-8 -# content-length: 147 -write [0x82 0x4E] -write [0x00 0x00 0x45 0x01 0x04 0x00 0x00 0x00 0x01 0x8d 0x76 0x90 0xaa 0x69 0xd2 0x9a] - [0xe4 0x52 0xa9 0xa7 0x4a 0x6b 0x13 0x01 0x5c 0x2f 0xae 0x0f 0x61 0x96 0xe4 0x59] - [0x3e 0x94 0x00 0x54 0xc2 0x58 0xd4 0x10 0x02 0xea 0x81 0x7e 0xe0 0x45 0x71 0xa7] - [0x14 0xc5 0xa3 0x7f 0x5f 0x92 0x49 0x7c 0xa5 0x89 0xd3 0x4d 0x1f 0x6a 0x12 0x71] - [0xd8 0x82 0xa6 0x0e 0x1b 0xf0 0xac 0xf7 0x0f 0x0d 0x03 0x31 0x34 0x37] - -# DATA frame -# ; END_STREAM -# 404 Not Found

404 Not Found


nghttpd nghttp2/1.19.0 at port 8080
-write [0x82 0x7e 0x00 0x9c] -write [0x00 0x00 0x93] - [0x00] - [0x01] - [0x00 0x00 0x00 0x01] - [0x3c 0x68 0x74 0x6d 0x6c 0x3e 0x3c 0x68 0x65] - [0x61 0x64 0x3e 0x3c 0x74 0x69 0x74 0x6c 0x65 0x3e 0x34 0x30 0x34 0x20 0x4e 0x6f] - [0x74 0x20 0x46 0x6f 0x75 0x6e 0x64 0x3c 0x2f 0x74 0x69 0x74 0x6c 0x65 0x3e 0x3c] - [0x2f 0x68 0x65 0x61 0x64 0x3e 0x3c 0x62 0x6f 0x64 0x79 0x3e 0x3c 0x68 0x31 0x3e] - [0x34 0x30 0x34 0x20 0x4e 0x6f 0x74 0x20 0x46 0x6f 0x75 0x6e 0x64 0x3c 0x2f 0x68] - [0x31 0x3e 0x3c 0x68 0x72 0x3e 0x3c 0x61 0x64 0x64 0x72 0x65 0x73 0x73 0x3e 0x6e] - [0x67 0x68 0x74 0x74 0x70 0x64 0x20 0x6e 0x67 0x68 0x74 0x74 0x70 0x32 0x2f 0x31] - [0x2e 0x31 0x39 0x2e 0x30 0x20 0x61 0x74 0x20 0x70 0x6f 0x72 0x74 0x20 0x38 0x30] - [0x38 0x30 0x3c 0x2f 0x61 0x64 0x64 0x72 0x65 0x73 0x73 0x3e 0x3c 0x2f 0x62 0x6f] - [0x64 0x79 0x3e 0x3c 0x2f 0x68 0x74 0x6d 0x6c 0x3e] \ No newline at end of file diff --git a/lib/http.js b/lib/http.js index 795aecc0..7c83c4b9 100644 --- a/lib/http.js +++ b/lib/http.js @@ -517,16 +517,11 @@ function Server(options) { forwardEvent('listening', this._server, this); } - // HTTP2 over generic socket server - // DPW TODO create generic stream - // else if (options.transport) { - // this._log.info('Creating HTTP/2 server over generic stream'); - // this._mode = 'plain'; - // this._server = options.transport(start); - // } + // TODO, revise interface to work over create generic "stream" sever // HTTP2 over plain WebSocket - else if (options.transport){ + else if ('websocket' === options.transport){ + console.log('DPW -- ' + options.transport); var http = require('http'); var websocket = require('websocket-stream'); From 89e094740ec03eacde2137b54691f372693d9a9a Mon Sep 17 00:00:00 2001 From: David Witherspoon Date: Tue, 21 Feb 2017 17:04:31 -0800 Subject: [PATCH 13/28] removed test library, minor code clean up --- lib/http2-ws.js | 906 --------------------------------------- lib/protocol/endpoint.js | 2 +- 2 files changed, 1 insertion(+), 907 deletions(-) delete mode 100644 lib/http2-ws.js diff --git a/lib/http2-ws.js b/lib/http2-ws.js deleted file mode 100644 index 18b3ef01..00000000 --- a/lib/http2-ws.js +++ /dev/null @@ -1,906 +0,0 @@ -WebSocket = require('websocket-stream'); -var env = require('./utils/environment'); - -if(!env.isNode()){ - require("setimmediate"); -} - -function http2ws(){ - -} - -var PassThrough = require('stream').PassThrough; -var Readable = require('stream').Readable; -var Writable = require('stream').Writable; -var protocol = require('./protocol'); -var Endpoint = protocol.Endpoint; - - -// var EventEmitter = require('events').EventEmitter; -var indexOf; - -if (typeof Array.prototype.indexOf === 'function') { - indexOf = function (haystack, needle) { - return haystack.indexOf(needle); - }; -} else { - indexOf = function (haystack, needle) { - var i = 0, length = haystack.length, idx = -1, found = false; - - while (i < length && !found) { - if (haystack[i] === needle) { - idx = i; - found = true; - } - - i++; - } - - return idx; - }; -}; - - -/* Polyfill EventEmitter. */ -var EventEmitter = function () { - this.events = {}; -}; - -EventEmitter.prototype.on = function (event, listener) { - if (typeof this.events[event] !== 'object') { - this.events[event] = []; - } - - this.events[event].push(listener); -}; - -EventEmitter.prototype.removeListener = function (event, listener) { - var idx; - - if (typeof this.events[event] === 'object') { - idx = indexOf(this.events[event], listener); - - if (idx > -1) { - this.events[event].splice(idx, 1); - } - } -}; - -EventEmitter.prototype.emit = function (event) { - var i, listeners, length, args = [].slice.call(arguments, 1); - - if (typeof this.events[event] === 'object') { - listeners = this.events[event].slice(); - length = listeners.length; - - for (i = 0; i < length; i++) { - listeners[i].apply(this, args); - } - } -}; - -EventEmitter.prototype.once = function (event, listener) { - this.on(event, function g () { - this.removeListener(event, g); - listener.apply(this, arguments); - }); -}; - - -var statusCodes = {}; -statusCodes[exports.ACCEPTED = 202] = "Accepted"; -statusCodes[exports.BAD_GATEWAY = 502] = "Bad Gateway"; -statusCodes[exports.BAD_REQUEST = 400] = "Bad Request"; -statusCodes[exports.CONFLICT = 409] = "Conflict"; -statusCodes[exports.CONTINUE = 100] = "Continue"; -statusCodes[exports.CREATED = 201] = "Created"; -statusCodes[exports.EXPECTATION_FAILED = 417] = "Expectation Failed"; -statusCodes[exports.FAILED_DEPENDENCY = 424] = "Failed Dependency"; -statusCodes[exports.FORBIDDEN = 403] = "Forbidden"; -statusCodes[exports.GATEWAY_TIMEOUT = 504] = "Gateway Timeout"; -statusCodes[exports.GONE = 410] = "Gone"; -statusCodes[exports.HTTP_VERSION_NOT_SUPPORTED = 505] = "HTTP Version Not Supported"; -statusCodes[exports.INSUFFICIENT_SPACE_ON_RESOURCE = 419] = "Insufficient Space on Resource"; -statusCodes[exports.INSUFFICIENT_STORAGE = 507] = "Insufficient Storage"; -statusCodes[exports.INTERNAL_SERVER_ERROR = 500] = "Server Error"; -statusCodes[exports.LENGTH_REQUIRED = 411] = "Length Required"; -statusCodes[exports.LOCKED = 423] = "Locked"; -statusCodes[exports.METHOD_FAILURE = 420] = "Method Failure"; -statusCodes[exports.METHOD_NOT_ALLOWED = 405] = "Method Not Allowed"; -statusCodes[exports.MOVED_PERMANENTLY = 301] = "Moved Permanently"; -statusCodes[exports.MOVED_TEMPORARILY = 302] = "Moved Temporarily"; -statusCodes[exports.MULTI_STATUS = 207] = "Multi-Status"; -statusCodes[exports.MULTIPLE_CHOICES = 300] = "Multiple Choices"; -statusCodes[exports.NETWORK_AUTHENTICATION_REQUIRED = 511] = "Network Authentication Required"; -statusCodes[exports.NO_CONTENT = 204] = "No Content"; -statusCodes[exports.NON_AUTHORITATIVE_INFORMATION = 203] = "Non Authoritative Information"; -statusCodes[exports.NOT_ACCEPTABLE = 406] = "Not Acceptable"; -statusCodes[exports.NOT_FOUND = 404] = "Not Found"; -statusCodes[exports.NOT_IMPLEMENTED = 501] = "Not Implemented"; -statusCodes[exports.NOT_MODIFIED = 304] = "Not Modified"; -statusCodes[exports.OK = 200] = "OK"; -statusCodes[exports.PARTIAL_CONTENT = 206] = "Partial Content"; -statusCodes[exports.PAYMENT_REQUIRED = 402] = "Payment Required"; -statusCodes[exports.PERMANENT_REDIRECT = 308] = "Permanent Redirect"; -statusCodes[exports.PRECONDITION_FAILED = 412] = "Precondition Failed"; -statusCodes[exports.PRECONDITION_REQUIRED = 428] = "Precondition Required"; -statusCodes[exports.PROCESSING = 102] = "Processing"; -statusCodes[exports.PROXY_AUTHENTICATION_REQUIRED = 407] = "Proxy Authentication Required"; -statusCodes[exports.REQUEST_HEADER_FIELDS_TOO_LARGE = 431] = "Request Header Fields Too Large"; -statusCodes[exports.REQUEST_TIMEOUT = 408] = "Request Timeout"; -statusCodes[exports.REQUEST_TOO_LONG = 413] = "Request Entity Too Large"; -statusCodes[exports.REQUEST_URI_TOO_LONG = 414] = "Request-URI Too Long"; -statusCodes[exports.REQUESTED_RANGE_NOT_SATISFIABLE = 416] = "Requested Range Not Satisfiable"; -statusCodes[exports.RESET_CONTENT = 205] = "Reset Content"; -statusCodes[exports.SEE_OTHER = 303] = "See Other"; -statusCodes[exports.SERVICE_UNAVAILABLE = 503] = "Service Unavailable"; -statusCodes[exports.SWITCHING_PROTOCOLS = 101] = "Switching Protocols"; -statusCodes[exports.TEMPORARY_REDIRECT = 307] = "Temporary Redirect"; -statusCodes[exports.TOO_MANY_REQUESTS = 429] = "Too Many Requests"; -statusCodes[exports.UNAUTHORIZED = 401] = "Unauthorized"; -statusCodes[exports.UNPROCESSABLE_ENTITY = 422] = "Unprocessable Entity"; -statusCodes[exports.UNSUPPORTED_MEDIA_TYPE = 415] = "Unsupported Media Type"; -statusCodes[exports.USE_PROXY = 305] = "Use Proxy"; - -Object.assign = function(target, varArgs) { // .length of function is 2 - 'use strict'; - if (target == null) { // TypeError if undefined or null - throw new TypeError('Cannot convert undefined or null to object'); - } - - var to = Object(target); - - for (var index = 1; index < arguments.length; index++) { - var nextSource = arguments[index]; - - if (nextSource != null) { // Skip over if undefined or null - for (var nextKey in nextSource) { - // Avoid bugs when hasOwnProperty is shadowed - if (Object.prototype.hasOwnProperty.call(nextSource, nextKey)) { - to[nextKey] = nextSource[nextKey]; - } - } - } - } - return to; -}; - -var deprecatedHeaders = [ - 'connection', - 'host', - 'keep-alive', - 'proxy-connection', - 'transfer-encoding', - 'upgrade' -]; - -http2ws.STATUS_CODES = statusCodes; -// exports.getStatusText = function(statusCode) { -// if (statusCodes.hasOwnProperty(statusCode)) { -// return statusCodes[statusCode]; -// } else { -// throw new Error("Status code does not exist: " + statusCode); -// } -// }; - -// Logger shim, used when no logger is provided by the user. -function noop() {} -var defaultLogger = { - fatal: noop, - error: noop, - warn : noop, - info : noop, - debug: noop, - trace: noop, - - child: function() { return this; } -}; - - -// IncomingRequest class -// --------------------- - -function IncomingRequest(stream) { - IncomingMessage.call(this, stream); -} -IncomingRequest.prototype = Object.create(IncomingMessage.prototype, { constructor: { value: IncomingRequest } }); - -// [Request Header Fields](https://tools.ietf.org/html/rfc7540#section-8.1.2.3) -// * `headers` argument: HTTP/2.0 request and response header fields carry information as a series -// of key-value pairs. This includes the target URI for the request, the status code for the -// response, as well as HTTP header fields. -IncomingRequest.prototype._onHeaders = function _onHeaders(headers) { - // * The ":method" header field includes the HTTP method - // * The ":scheme" header field includes the scheme portion of the target URI - // * The ":authority" header field includes the authority portion of the target URI - // * The ":path" header field includes the path and query parts of the target URI. - // This field MUST NOT be empty; URIs that do not contain a path component MUST include a value - // of '/', unless the request is an OPTIONS request for '*', in which case the ":path" header - // field MUST include '*'. - // * All HTTP/2.0 requests MUST include exactly one valid value for all of these header fields. A - // server MUST treat the absence of any of these header fields, presence of multiple values, or - // an invalid value as a stream error of type PROTOCOL_ERROR. - this.method = this._checkSpecialHeader(':method' , headers[':method']); - this.scheme = this._checkSpecialHeader(':scheme' , headers[':scheme']); - this.host = this._checkSpecialHeader(':authority', headers[':authority'] ); - this.url = this._checkSpecialHeader(':path' , headers[':path'] ); - if (!this.method || !this.scheme || !this.host || !this.url) { - // This is invalid, and we've sent a RST_STREAM, so don't continue processing - return; - } - - // * Host header is included in the headers object for backwards compatibility. - this.headers.host = this.host; - - // * Handling regular headers. - IncomingMessage.prototype._onHeaders.call(this, headers); - - // * Signaling that the headers arrived. - this._log.info({ method: this.method, scheme: this.scheme, host: this.host, - path: this.url, headers: this.headers }, 'Incoming request'); - this.emit('ready'); -}; - -// OutgoingResponse class -// ---------------------- - -function OutgoingResponse(stream) { - OutgoingMessage.call(this); - - this._log = stream._log.child({ component: 'http' }); - - this.stream = stream; - this.statusCode = 200; - this.sendDate = true; - - this.stream.once('headers', this._onRequestHeaders.bind(this)); -} -OutgoingResponse.prototype = Object.create(OutgoingMessage.prototype, { constructor: { value: OutgoingResponse } }); - -OutgoingResponse.prototype.writeHead = function writeHead(statusCode, reasonPhrase, headers) { - if (this.headersSent) { - return; - } - - if (typeof reasonPhrase === 'string') { - this._log.warn('Reason phrase argument was present but ignored by the writeHead method'); - } else { - headers = reasonPhrase; - } - - for (var name in headers) { - this.setHeader(name, headers[name]); - } - headers = this._headers; - - if (this.sendDate && !('date' in this._headers)) { - headers.date = (new Date()).toUTCString(); - } - - this._log.info({ status: statusCode, headers: this._headers }, 'Sending server response'); - - headers[':status'] = this.statusCode = statusCode; - - this.stream.headers(headers); - this.headersSent = true; -}; - -OutgoingResponse.prototype._implicitHeaders = function _implicitHeaders() { - if (!this.headersSent) { - this.writeHead(this.statusCode); - } -}; - -OutgoingResponse.prototype._implicitHeader = function() { - this._implicitHeaders(); -}; - -OutgoingResponse.prototype.write = function write() { - this._implicitHeaders(); - return OutgoingMessage.prototype.write.apply(this, arguments); -}; - -OutgoingResponse.prototype.end = function end() { - this.finshed = true; - this._implicitHeaders(); - return OutgoingMessage.prototype.end.apply(this, arguments); -}; - -OutgoingResponse.prototype._onRequestHeaders = function _onRequestHeaders(headers) { - this._requestHeaders = headers; -}; - -OutgoingResponse.prototype.push = function push(options) { - if (typeof options === 'string') { - options = url.parse(options); - } - - if (!options.path) { - throw new Error('`path` option is mandatory.'); - } - - var promise = util._extend({ - ':method': (options.method || 'GET').toUpperCase(), - ':scheme': (options.protocol && options.protocol.slice(0, -1)) || this._requestHeaders[':scheme'], - ':authority': options.hostname || options.host || this._requestHeaders[':authority'], - ':path': options.path - }, options.headers); - - this._log.info({ method: promise[':method'], scheme: promise[':scheme'], - authority: promise[':authority'], path: promise[':path'], - headers: options.headers }, 'Promising push stream'); - - var pushStream = this.stream.promise(promise); - - return new OutgoingResponse(pushStream); -}; - -OutgoingResponse.prototype.altsvc = function altsvc(host, port, protocolID, maxAge, origin) { - if (origin === undefined) { - origin = ""; - } - this.stream.altsvc(host, port, protocolID, maxAge, origin); -}; - -// Overriding `EventEmitter`'s `on(event, listener)` method to forward certain subscriptions to -// `request`. See `Server.prototype.on` for explanation. -OutgoingResponse.prototype.on = function on(event, listener) { - if (this.request && (event === 'timeout')) { - this.request.on(event, listener && listener.bind(this)); - } else { - OutgoingMessage.prototype.on.call(this, event, listener); - } -}; - - -// Bunyan serializers exported by submodules that are worth adding when creating a logger. -exports.serializers = protocol.serializers; - -// IncomingMessage class -// --------------------- - -function IncomingMessage(stream) { - // * This is basically a read-only wrapper for the [Stream](protocol/stream.html) class. - PassThrough.call(this); - stream.pipe(this); - this.socket = this.stream = stream; - - this._log = stream._log.child({ component: 'http' }); - - // * HTTP/2.0 does not define a way to carry the version identifier that is included in the - // HTTP/1.1 request/status line. Version is always 2.0. - this.httpVersion = '2.0'; - this.httpVersionMajor = 2; - this.httpVersionMinor = 0; - - // * `this.headers` will store the regular headers (and none of the special colon headers) - this.headers = {}; - this.trailers = undefined; - this._lastHeadersSeen = undefined; - - // * Other metadata is filled in when the headers arrive. - stream.once('headers', this._onHeaders.bind(this)); - stream.once('end', this._onEnd.bind(this)); -} -IncomingMessage.prototype = Object.create(PassThrough.prototype, { constructor: { value: IncomingMessage } }); - -// [Request Header Fields](https://tools.ietf.org/html/rfc7540#section-8.1.2.3) -// * `headers` argument: HTTP/2.0 request and response header fields carry information as a series -// of key-value pairs. This includes the target URI for the request, the status code for the -// response, as well as HTTP header fields. -IncomingMessage.prototype._onHeaders = function _onHeaders(headers) { - // * Detects malformed headers - this._validateHeaders(headers); - - // * Store the _regular_ headers in `this.headers` - for (var name in headers) { - if (name[0] !== ':') { - if (name === 'set-cookie' && !Array.isArray(headers[name])) { - this.headers[name] = [headers[name]]; - } else { - this.headers[name] = headers[name]; - } - } - } - - // * The last header block, if it's not the first, will represent the trailers - var self = this; - this.stream.on('headers', function(headers) { - self._lastHeadersSeen = headers; - }); -}; - -IncomingMessage.prototype._onEnd = function _onEnd() { - this.trailers = this._lastHeadersSeen; -}; - -IncomingMessage.prototype.setTimeout = noop; - -IncomingMessage.prototype._checkSpecialHeader = function _checkSpecialHeader(key, value) { - if ((typeof value !== 'string') || (value.length === 0)) { - this._log.error({ key: key, value: value }, 'Invalid or missing special header field'); - this.stream.reset('PROTOCOL_ERROR'); - } - - return value; -}; - -IncomingMessage.prototype._validateHeaders = function _validateHeaders(headers) { - // * An HTTP/2.0 request or response MUST NOT include any of the following header fields: - // Connection, Host, Keep-Alive, Proxy-Connection, Transfer-Encoding, and Upgrade. A server - // MUST treat the presence of any of these header fields as a stream error of type - // PROTOCOL_ERROR. - // If the TE header is present, it's only valid value is 'trailers' - for (var i = 0; i < deprecatedHeaders.length; i++) { - var key = deprecatedHeaders[i]; - if (key in headers || (key === 'te' && headers[key] !== 'trailers')) { - this._log.error({ key: key, value: headers[key] }, 'Deprecated header found'); - this.stream.reset('PROTOCOL_ERROR'); - return; - } - } - - for (var headerName in headers) { - // * Empty header name field is malformed - if (headerName.length <= 1) { - this.stream.reset('PROTOCOL_ERROR'); - return; - } - // * A request or response containing uppercase header name field names MUST be - // treated as malformed (Section 8.1.3.5). Implementations that detect malformed - // requests or responses need to ensure that the stream ends. - if(/[A-Z]/.test(headerName)) { - this.stream.reset('PROTOCOL_ERROR'); - return; - } - } -}; - -// OutgoingMessage class -// --------------------- - -function OutgoingMessage() { - // * This is basically a read-only wrapper for the [Stream](protocol/stream.html) class. - Writable.call(this); - - this._headers = {}; - this._trailers = undefined; - this.headersSent = false; - this.finished = false; - - this.on('finish', this._finish); -} -OutgoingMessage.prototype = Object.create(Writable.prototype, { constructor: { value: OutgoingMessage } }); - -OutgoingMessage.prototype._write = function _write(chunk, encoding, callback) { - if (this.stream) { - this.stream.write(chunk, encoding, callback); - } else { - this.once('socket', this._write.bind(this, chunk, encoding, callback)); - } -}; - -OutgoingMessage.prototype._finish = function _finish() { - if (this.stream) { - if (this._trailers) { - if (this.request) { - this.request.addTrailers(this._trailers); - } else { - this.stream.headers(this._trailers); - } - } - this.finished = true; - this.stream.end(); - } else { - this.once('socket', this._finish.bind(this)); - } -}; - -OutgoingMessage.prototype.setHeader = function setHeader(name, value) { - if (this.headersSent) { - return this.emit('error', new Error('Can\'t set headers after they are sent.')); - } else { - name = name.toLowerCase(); - if (deprecatedHeaders.indexOf(name) !== -1) { - return this.emit('error', new Error('Cannot set deprecated header: ' + name)); - } - this._headers[name] = value; - } -}; - -OutgoingMessage.prototype.removeHeader = function removeHeader(name) { - if (this.headersSent) { - return this.emit('error', new Error('Can\'t remove headers after they are sent.')); - } else { - delete this._headers[name.toLowerCase()]; - } -}; - -OutgoingMessage.prototype.getHeader = function getHeader(name) { - return this._headers[name.toLowerCase()]; -}; - -OutgoingMessage.prototype.addTrailers = function addTrailers(trailers) { - this._trailers = trailers; -}; - -OutgoingMessage.prototype.setTimeout = noop; - -OutgoingMessage.prototype._checkSpecialHeader = IncomingMessage.prototype._checkSpecialHeader; - - -// Client side -// =========== - -exports.ClientRequest = OutgoingRequest; // for API compatibility -exports.OutgoingRequest = OutgoingRequest; -exports.IncomingResponse = IncomingResponse; -exports.Agent = Agent; -exports.globalAgent = undefined; - -function requestRaw(options, callback) { - if (typeof options === "string") { - options = url.parse(options); - } - options.plain = true; - if (options.protocol && options.protocol !== "http:") { - throw new Error('This interface only supports http-schemed URLs'); - } - if (options.agent && typeof(options.agent.request) === 'function') { - var agentOptions = Object.assign({}, options); - delete agentOptions.agent; - return options.agent.request(agentOptions, callback); - } - return exports.globalAgent.request(options, callback); -} - -function getRaw(options, callback) { - // TODO maybe handle string URL - // if (typeof options === "string") { - // options = url.parse(options); - // } - options.plain = true; - if (options.agent && typeof(options.agent.get) === 'function') { - var agentOptions = Object.assign({}, options); - delete agentOptions.agent; - return options.agent.get(agentOptions, callback); - } - return exports.globalAgent.get(options, callback); -} - -// Agent class -// ----------- - -function Agent(options) { - EventEmitter.call(this); - // this.setMaxListeners(0); - - options = Object.assign({}, options); - - this._settings = options.settings; - this._log = (options.log || defaultLogger).child({ component: 'http' }); - this.endpoints = {}; - - // * Using an own HTTPS agent, because the global agent does not look at `NPN/ALPNProtocols` when - // generating the key identifying the connection, so we may get useless non-negotiated TLS - // channels even if we ask for a negotiated one. This agent will contain only negotiated - // channels. - // DPW TODO - // this._httpsAgent = new https.Agent(options); - // - // this.sockets = this._httpsAgent.sockets; - // this.requests = this._httpsAgent.requests; - - // this.sockets = new WebSocket('ws://localhost:8080/echo', 'http2'); -} -Agent.prototype = Object.create(EventEmitter.prototype, { constructor: { value: Agent } }); - -Agent.prototype.request = function request(options, callback) { - if (typeof options === 'string') { - options = url.parse(options); - } else { - options = Object.assign({}, options); - } - - options.method = (options.method || 'GET').toUpperCase(); - options.protocol = options.protocol || 'http:'; - options.host = options.hostname || options.host || 'localhost'; - options.port = options.port || 443; - options.path = options.path || '/'; - - if (!options.plain && options.protocol === 'http:') { - this._log.error('Trying to negotiate client request with Upgrade from HTTP/1.1'); - this.emit('error', new Error('HTTP1.1 -> HTTP2 upgrade is not yet supported.')); - } - - var request = new OutgoingRequest(this._log); - - if (callback) { - request.on('response', callback); - } - - var key = [ - !!options.plain, - options.host, - options.port - ].join(':'); - var self = this; - - // * There's an existing HTTP/2 connection to this host - if (key in this.endpoints) { - var endpoint = this.endpoints[key]; - request._start(endpoint.createStream(), options); - } - - // * HTTP/2 over plain WS - else if (options.plain) { - endpoint = new Endpoint(this._log, 'CLIENT', this._settings); - var wsUrl = options.transport; - // TODO throw error earlier if not defined. - endpoint.socket = new WebSocket(wsUrl); - - // endpoint.socket = net.connect({ - // host: options.host, - // port: options.port, - // localAddress: options.localAddress - // }); - - endpoint.socket.on('error', function (error) { - self._log.error('Socket error: ' + error.toString()); - request.emit('error', error); - }); - - endpoint.on('error', function(error){ - self._log.error('Connection error: ' + error.toString()); - request.emit('error', error); - }); - - this.endpoints[key] = endpoint; - endpoint.pipe(endpoint.socket).pipe(endpoint); - request._start(endpoint.createStream(), options); - } - - return request; -}; - -Agent.prototype.get = function get(options, callback) { - var request = this.request(options, callback); - request.end(); - return request; -}; - -Agent.prototype.destroy = function(error) { - // DPW TODO _httpsAgent? - if (this._httpsAgent) { - this._httpsAgent.destroy(); - } - for (var key in this.endpoints) { - this.endpoints[key].close(error); - } -}; - -function unbundleSocket(socket) { - socket.removeAllListeners('data'); - socket.removeAllListeners('end'); - socket.removeAllListeners('readable'); - socket.removeAllListeners('close'); - socket.removeAllListeners('error'); - socket.unpipe(); - delete socket.ondata; - delete socket.onend; -} - -function hasAgentOptions(options) { - return options.pfx != null || - options.key != null || - options.passphrase != null || - options.cert != null || - options.ca != null || - options.ciphers != null || - options.rejectUnauthorized != null || - options.secureProtocol != null; -} - -// DPW TODO +httpsAgent? -Object.defineProperty(Agent.prototype, 'maxSockets', { - get: function getMaxSockets() { - return this._httpsAgent.maxSockets; - }, - set: function setMaxSockets(value) { - this._httpsAgent.maxSockets = value; - } -}); - -exports.globalAgent = new Agent(); - -// OutgoingRequest class -// --------------------- - -function OutgoingRequest() { - OutgoingMessage.call(this); - - this._log = undefined; - - this.stream = undefined; -} -OutgoingRequest.prototype = Object.create(OutgoingMessage.prototype, { constructor: { value: OutgoingRequest } }); - -OutgoingRequest.prototype._start = function _start(stream, options) { - this.stream = stream; - this.options = options; - - this._log = stream._log.child({ component: 'http' }); - - for (var key in options.headers) { - this.setHeader(key, options.headers[key]); - } - var headers = this._headers; - delete headers.host; - - if (options.auth) { - headers.authorization = 'Basic ' + new Buffer(options.auth).toString('base64'); - } - - headers[':scheme'] = options.protocol.slice(0, -1); - headers[':method'] = options.method; - headers[':authority'] = options.host; - headers[':path'] = options.path; - - this._log.info({ scheme: headers[':scheme'], method: headers[':method'], - authority: headers[':authority'], path: headers[':path'], - headers: (options.headers || {}) }, 'Sending request'); - this.stream.headers(headers); - this.headersSent = true; - - this.emit('socket', this.stream); - var response = new IncomingResponse(this.stream); - response.req = this; - response.once('ready', this.emit.bind(this, 'response', response)); - - this.stream.on('promise', this._onPromise.bind(this)); -}; - -OutgoingRequest.prototype._fallback = function _fallback(request) { - request.on('response', this.emit.bind(this, 'response')); - this.stream = this.request = request; - this.emit('socket', this.socket); -}; - -OutgoingRequest.prototype.setPriority = function setPriority(priority) { - if (this.stream) { - this.stream.priority(priority); - } else { - this.once('socket', this.setPriority.bind(this, priority)); - } -}; - -// Overriding `EventEmitter`'s `on(event, listener)` method to forward certain subscriptions to -// `request`. See `Server.prototype.on` for explanation. -OutgoingRequest.prototype.on = function on(event, listener) { - if (this.request && (event === 'upgrade')) { - this.request.on(event, listener && listener.bind(this)); - } else { - OutgoingMessage.prototype.on.call(this, event, listener); - } -}; - -// Methods only in fallback mode -OutgoingRequest.prototype.setNoDelay = function setNoDelay(noDelay) { - if (this.request) { - this.request.setNoDelay(noDelay); - } else if (!this.stream) { - this.on('socket', this.setNoDelay.bind(this, noDelay)); - } -}; - -OutgoingRequest.prototype.setSocketKeepAlive = function setSocketKeepAlive(enable, initialDelay) { - if (this.request) { - this.request.setSocketKeepAlive(enable, initialDelay); - } else if (!this.stream) { - this.on('socket', this.setSocketKeepAlive.bind(this, enable, initialDelay)); - } -}; - -OutgoingRequest.prototype.setTimeout = function setTimeout(timeout, callback) { - if (this.request) { - this.request.setTimeout(timeout, callback); - } else if (!this.stream) { - this.on('socket', this.setTimeout.bind(this, timeout, callback)); - } -}; - -// Aborting the request -OutgoingRequest.prototype.abort = function abort() { - if (this.request) { - this.request.abort(); - } else if (this.stream) { - this.stream.reset('CANCEL'); - } else { - this.on('socket', this.abort.bind(this)); - } -}; - -// Receiving push promises -OutgoingRequest.prototype._onPromise = function _onPromise(stream, headers) { - this._log.info({ push_stream: stream.id }, 'Receiving push promise'); - - var promise = new IncomingPromise(stream, headers); - - if (this.listeners('push').length > 0) { - this.emit('push', promise); - } else { - promise.cancel(); - } -}; - -// IncomingResponse class -// ---------------------- - -function IncomingResponse(stream) { - IncomingMessage.call(this, stream); -} -IncomingResponse.prototype = Object.create(IncomingMessage.prototype, { constructor: { value: IncomingResponse } }); - -// [Response Header Fields](https://tools.ietf.org/html/rfc7540#section-8.1.2.4) -// * `headers` argument: HTTP/2.0 request and response header fields carry information as a series -// of key-value pairs. This includes the target URI for the request, the status code for the -// response, as well as HTTP header fields. -IncomingResponse.prototype._onHeaders = function _onHeaders(headers) { - // * A single ":status" header field is defined that carries the HTTP status code field. This - // header field MUST be included in all responses. - // * A client MUST treat the absence of the ":status" header field, the presence of multiple - // values, or an invalid value as a stream error of type PROTOCOL_ERROR. - // Note: currently, we do not enforce it strictly: we accept any format, and parse it as int - // * HTTP/2.0 does not define a way to carry the reason phrase that is included in an HTTP/1.1 - // status line. - this.statusCode = parseInt(this._checkSpecialHeader(':status', headers[':status'])); - - // * Handling regular headers. - IncomingMessage.prototype._onHeaders.call(this, headers); - - // * Signaling that the headers arrived. - this._log.info({ status: this.statusCode, headers: this.headers}, 'Incoming response'); - this.emit('ready'); -}; - -// IncomingPromise class -// ------------------------- - -function IncomingPromise(responseStream, promiseHeaders) { - var stream = new Readable(); - stream._read = noop; - stream.push(null); - stream._log = responseStream._log; - - IncomingRequest.call(this, stream); - - this._onHeaders(promiseHeaders); - - this._responseStream = responseStream; - - var response = new IncomingResponse(this._responseStream); - response.once('ready', this.emit.bind(this, 'response', response)); - - this.stream.on('promise', this._onPromise.bind(this)); -} -IncomingPromise.prototype = Object.create(IncomingRequest.prototype, { constructor: { value: IncomingPromise } }); - -IncomingPromise.prototype.cancel = function cancel() { - this._responseStream.reset('CANCEL'); -}; - -IncomingPromise.prototype.setPriority = function setPriority(priority) { - this._responseStream.priority(priority); -}; - -IncomingPromise.prototype._onPromise = OutgoingRequest.prototype._onPromise; - -exports.Agent = Agent; -exports.get = getRaw; - -// If browser then attach to window -if (!env.isNode()) { - // DPW TODO, combine with node export logic - window.http2ws = http2ws; - http2ws.get = getRaw; -} \ No newline at end of file diff --git a/lib/protocol/endpoint.js b/lib/protocol/endpoint.js index fece9ac5..22e17ce1 100644 --- a/lib/protocol/endpoint.js +++ b/lib/protocol/endpoint.js @@ -7,7 +7,7 @@ var Decompressor = require('./compressor').Decompressor; var Connection = require('./connection').Connection; var Duplex = require('stream').Duplex; var Transform = require('stream').Transform; -var timers = require('timers'); +var timers = require('timers'); exports.Endpoint = Endpoint; From d248656ef00e56d3d377625c1b29f6e9f06891b0 Mon Sep 17 00:00:00 2001 From: David Witherspoon Date: Tue, 21 Feb 2017 17:05:46 -0800 Subject: [PATCH 14/28] Celaned up unneeded dependencies --- package.json | 5 ----- 1 file changed, 5 deletions(-) diff --git a/package.json b/package.json index 2425251a..f5d98a5a 100644 --- a/package.json +++ b/package.json @@ -17,8 +17,6 @@ "events": "1.1.1" }, "devDependencies": { - "k3po-exec": "0.0.x", - "k3po-mocha.js": "0.0.x", "istanbul": "*", "chai": "*", "mocha": "*", @@ -28,9 +26,6 @@ }, "scripts": { "test": "istanbul test _mocha -- --reporter spec --slow 500 --timeout 15000", - "preintegration-test": "node ./node_modules/k3po-exec/k3po-exec.js --pom ./integration-test/k3po/pom.xml --log ./builds/integration-test/k3po-test.log --pidFile ./builds/integration-test/k3po-test.pid start", - "integration-test": "mocha --ui /Users/David/Documents/projects/javascript-k3po/k3po-mocha.js/k3po-ui.js integration-test/k3po-test.js", - "postintegration-test": "node ./node_modules/k3po-exec/k3po-exec.js --pom ./integration-test/k3po/pom.xml --log ./builds/integration-test/k3po-test.log --pidFile ./builds/integration-test/k3po-test.pid", "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" }, "repository": { From f5ff1908960574c959282e0f23f26539b1021c1b Mon Sep 17 00:00:00 2001 From: David Witherspoon Date: Tue, 21 Feb 2017 17:08:13 -0800 Subject: [PATCH 15/28] Removed console.log --- lib/http.js | 1 - 1 file changed, 1 deletion(-) diff --git a/lib/http.js b/lib/http.js index 7c83c4b9..653c330b 100644 --- a/lib/http.js +++ b/lib/http.js @@ -521,7 +521,6 @@ function Server(options) { // HTTP2 over plain WebSocket else if ('websocket' === options.transport){ - console.log('DPW -- ' + options.transport); var http = require('http'); var websocket = require('websocket-stream'); From 5c7c0d160cf2757c8f1d2cfc7464f0fdfe58e83c Mon Sep 17 00:00:00 2001 From: David Witherspoon Date: Thu, 23 Feb 2017 16:03:48 -0800 Subject: [PATCH 16/28] Changed API to be fully pluggable for any transport or server --- lib/http.js | 22 +++++----------------- test/http.js | 25 ++++++++++++++++++++----- 2 files changed, 25 insertions(+), 22 deletions(-) diff --git a/lib/http.js b/lib/http.js index 653c330b..e9996ce0 100644 --- a/lib/http.js +++ b/lib/http.js @@ -517,25 +517,13 @@ function Server(options) { forwardEvent('listening', this._server, this); } - // TODO, revise interface to work over create generic "stream" sever - - // HTTP2 over plain WebSocket - else if ('websocket' === options.transport){ - var http = require('http'); - var websocket = require('websocket-stream'); - - this._log.info('Creating HTTP/2 server over plain WebSocket'); + // HTTP2 over any generic transport + else if (options.transport){ this._mode = 'plain'; - - var httpServer = http.createServer(); - options.server = httpServer; - this._server = websocket.createServer(options, start); - this._server.listen = function(options, cb){ - httpServer.listen(options, cb); - } - + this._server = options.transport(options, start); } - // HTTP2 over plain TCP + + // HTTP2 over plain TCP (Perhaps this should be deprecated??) else if (options.plain) { this._log.info('Creating HTTP/2 server over plain TCP'); this._mode = 'plain'; diff --git a/test/http.js b/test/http.js index 78ef172f..b333c8a9 100644 --- a/test/http.js +++ b/test/http.js @@ -392,7 +392,7 @@ describe('http.js', function() { }); }); }); - describe('request over plain WS', function() { + describe('request over generic plain transport (example WebSocket)', function() { it('should work as expected', function(done) { var path = '/x'; var message = 'Hello world'; @@ -400,12 +400,19 @@ describe('http.js', function() { var server = http2.raw.createServer({ log: util.serverLog, - transport: "websocket" + transport: function(options, start){ + var httpServer = http.createServer(); + options.server = httpServer; + var res = websocket.createServer(options, start); + res.listen = function(options, cb){ + httpServer.listen(options, cb); + }; + return res; + } }, function(request, response) { expect(request.url).to.equal(path); response.end(message); }); - server.listen(portnum, function() { var request = http2.raw.request({ plain: true, @@ -426,14 +433,22 @@ describe('http.js', function() { }); }); }); - describe('get over plain WS', function() { + describe('get over plain generic transport (example WebSocket)', function() { it('should work as expected', function(done) { var path = '/x'; var message = 'Hello world'; var server = http2.raw.createServer({ log: util.serverLog, - transport: "websocket" + transport: function(options, start){ + var httpServer = http.createServer(); + options.server = httpServer; + var res = websocket.createServer(options, start); + res.listen = function(options, cb){ + httpServer.listen(options, cb); + }; + return res; + } }, function(request, response) { expect(request.url).to.equal(path); response.end(message); From b9a5350fa8c56f37bef2458a0e3a2d53040996d5 Mon Sep 17 00:00:00 2001 From: David Witherspoon Date: Mon, 6 Mar 2017 11:47:40 -0800 Subject: [PATCH 17/28] Added status code and worked around mid stream close with hack --- lib/http.js | 127 ++++++++++++++++++++++++++++----------------------- test/http.js | 49 +++++++++++++++++++- 2 files changed, 118 insertions(+), 58 deletions(-) diff --git a/lib/http.js b/lib/http.js index e9996ce0..337268c3 100644 --- a/lib/http.js +++ b/lib/http.js @@ -137,61 +137,62 @@ var protocol = require('./protocol'); var Endpoint = protocol.Endpoint; var https = require('https'); -var statusCodes = {}; -statusCodes[exports.ACCEPTED = 202] = "Accepted"; -statusCodes[exports.BAD_GATEWAY = 502] = "Bad Gateway"; -statusCodes[exports.BAD_REQUEST = 400] = "Bad Request"; -statusCodes[exports.CONFLICT = 409] = "Conflict"; -statusCodes[exports.CONTINUE = 100] = "Continue"; -statusCodes[exports.CREATED = 201] = "Created"; -statusCodes[exports.EXPECTATION_FAILED = 417] = "Expectation Failed"; -statusCodes[exports.FAILED_DEPENDENCY = 424] = "Failed Dependency"; -statusCodes[exports.FORBIDDEN = 403] = "Forbidden"; -statusCodes[exports.GATEWAY_TIMEOUT = 504] = "Gateway Timeout"; -statusCodes[exports.GONE = 410] = "Gone"; -statusCodes[exports.HTTP_VERSION_NOT_SUPPORTED = 505] = "HTTP Version Not Supported"; -statusCodes[exports.INSUFFICIENT_SPACE_ON_RESOURCE = 419] = "Insufficient Space on Resource"; -statusCodes[exports.INSUFFICIENT_STORAGE = 507] = "Insufficient Storage"; -statusCodes[exports.INTERNAL_SERVER_ERROR = 500] = "Server Error"; -statusCodes[exports.LENGTH_REQUIRED = 411] = "Length Required"; -statusCodes[exports.LOCKED = 423] = "Locked"; -statusCodes[exports.METHOD_FAILURE = 420] = "Method Failure"; -statusCodes[exports.METHOD_NOT_ALLOWED = 405] = "Method Not Allowed"; -statusCodes[exports.MOVED_PERMANENTLY = 301] = "Moved Permanently"; -statusCodes[exports.MOVED_TEMPORARILY = 302] = "Moved Temporarily"; -statusCodes[exports.MULTI_STATUS = 207] = "Multi-Status"; -statusCodes[exports.MULTIPLE_CHOICES = 300] = "Multiple Choices"; -statusCodes[exports.NETWORK_AUTHENTICATION_REQUIRED = 511] = "Network Authentication Required"; -statusCodes[exports.NO_CONTENT = 204] = "No Content"; -statusCodes[exports.NON_AUTHORITATIVE_INFORMATION = 203] = "Non Authoritative Information"; -statusCodes[exports.NOT_ACCEPTABLE = 406] = "Not Acceptable"; -statusCodes[exports.NOT_FOUND = 404] = "Not Found"; -statusCodes[exports.NOT_IMPLEMENTED = 501] = "Not Implemented"; -statusCodes[exports.NOT_MODIFIED = 304] = "Not Modified"; -statusCodes[exports.OK = 200] = "OK"; -statusCodes[exports.PARTIAL_CONTENT = 206] = "Partial Content"; -statusCodes[exports.PAYMENT_REQUIRED = 402] = "Payment Required"; -statusCodes[exports.PERMANENT_REDIRECT = 308] = "Permanent Redirect"; -statusCodes[exports.PRECONDITION_FAILED = 412] = "Precondition Failed"; -statusCodes[exports.PRECONDITION_REQUIRED = 428] = "Precondition Required"; -statusCodes[exports.PROCESSING = 102] = "Processing"; -statusCodes[exports.PROXY_AUTHENTICATION_REQUIRED = 407] = "Proxy Authentication Required"; -statusCodes[exports.REQUEST_HEADER_FIELDS_TOO_LARGE = 431] = "Request Header Fields Too Large"; -statusCodes[exports.REQUEST_TIMEOUT = 408] = "Request Timeout"; -statusCodes[exports.REQUEST_TOO_LONG = 413] = "Request Entity Too Large"; -statusCodes[exports.REQUEST_URI_TOO_LONG = 414] = "Request-URI Too Long"; -statusCodes[exports.REQUESTED_RANGE_NOT_SATISFIABLE = 416] = "Requested Range Not Satisfiable"; -statusCodes[exports.RESET_CONTENT = 205] = "Reset Content"; -statusCodes[exports.SEE_OTHER = 303] = "See Other"; -statusCodes[exports.SERVICE_UNAVAILABLE = 503] = "Service Unavailable"; -statusCodes[exports.SWITCHING_PROTOCOLS = 101] = "Switching Protocols"; -statusCodes[exports.TEMPORARY_REDIRECT = 307] = "Temporary Redirect"; -statusCodes[exports.TOO_MANY_REQUESTS = 429] = "Too Many Requests"; -statusCodes[exports.UNAUTHORIZED = 401] = "Unauthorized"; -statusCodes[exports.UNPROCESSABLE_ENTITY = 422] = "Unprocessable Entity"; -statusCodes[exports.UNSUPPORTED_MEDIA_TYPE = 415] = "Unsupported Media Type"; -statusCodes[exports.USE_PROXY = 305] = "Use Proxy"; -exports.STATUS_CODES = statusCodes; + +exports.STATUS_CODES = { + '202': 'Accepted', + '502': 'Bad Gateway', + '400': 'Bad Request', + '409': 'Conflict', + '100': 'Continue', + '201': 'Created', + '417': 'Expectation Failed', + '424': 'Failed Dependency', + '403': 'Forbidden', + '504': 'Gateway Timeout', + '410': 'Gone', + '505': 'HTTP Version Not Supported', + '419': 'Insufficient Space on Resource', + '507': 'Insufficient Storage', + '500': 'Server Error', + '411': 'Length Required', + '423': 'Locked', + '420': 'Method Failure', + '405': 'Method Not Allowed', + '301': 'Moved Permanently', + '302': 'Moved Temporarily', + '207': 'Multi-Status', + '300': 'Multiple Choices', + '511': 'Network Authentication Required', + '204': 'No Content', + '203': 'Non Authoritative Information', + '406': 'Not Acceptable', + '404': 'Not Found', + '501': 'Not Implemented', + '304': 'Not Modified', + '200': 'OK', + '206': 'Partial Content', + '402': 'Payment Required', + '308': 'Permanent Redirect', + '412': 'Precondition Failed', + '428': 'Precondition Required', + '102': 'Processing', + '407': 'Proxy Authentication Required', + '431': 'Request Header Fields Too Large', + '408': 'Request Timeout', + '413': 'Request Entity Too Large', + '414': 'Request-URI Too Long', + '416': 'Requested Range Not Satisfiable', + '205': 'Reset Content', + '303': 'See Other', + '503': 'Service Unavailable', + '101': 'Switching Protocols', + '307': 'Temporary Redirect', + '429': 'Too Many Requests', + '401': 'Unauthorized', + '422': 'Unprocessable Entity', + '415': 'Unsupported Media Type', + '305': 'Use Proxy' +}; exports.IncomingMessage = IncomingMessage; exports.OutgoingMessage = OutgoingMessage; exports.protocol = protocol; @@ -989,7 +990,7 @@ Agent.prototype.request = function request(options, callback) { var self = this; // * There's an existing HTTP/2 connection to this host - if (key in this.endpoints) { + if (key in this.endpoints && this.endpoints[key]) { var endpoint = this.endpoints[key]; request._start(endpoint.createStream(), options); } @@ -1008,6 +1009,20 @@ Agent.prototype.request = function request(options, callback) { self._log.error('Connection error: ' + error.toString()); request.emit('error', error); }); + + var self = this; + endpoint.socket.on('close', function (error) { + // DPW This is sort of a hack to protect against + // the reuse of a endpoint that has the underlying + // connection closed. It would probably be better + // to implement this near lin 933 (if (key in this.endpoints)) + // by checking the endpoint state (requires new API to expose) + + // Alternatively, this could be a bug with my WS connection + // not emitting an error when it is unexpectedly closed ?? + delete self.endpoints[key]; + }); + this.endpoints[key] = endpoint; endpoint.pipe(endpoint.socket).pipe(endpoint); request._start(endpoint.createStream(), options); diff --git a/test/http.js b/test/http.js index b333c8a9..d22e828c 100644 --- a/test/http.js +++ b/test/http.js @@ -407,6 +407,9 @@ describe('http.js', function() { res.listen = function(options, cb){ httpServer.listen(options, cb); }; + res.close = function (cb) { + httpServer.close(cb); + }; return res; } }, function(request, response) { @@ -433,6 +436,7 @@ describe('http.js', function() { }); }); }); + describe('get over plain generic transport (example WebSocket)', function() { it('should work as expected', function(done) { var path = '/x'; @@ -447,6 +451,9 @@ describe('http.js', function() { res.listen = function(options, cb){ httpServer.listen(options, cb); }; + res.close = function (cb) { + httpServer.close(cb); + }; return res; } }, function(request, response) { @@ -454,9 +461,9 @@ describe('http.js', function() { response.end(message); }); - server.listen(1243, function() { + server.listen(1239, function() { var request = http2.raw.get({path : path, transport: function(){ - return websocket('ws://localhost:' + 1243); + return websocket('ws://localhost:' + 1239); }}, function(response) { response.on('data', function(data) { expect(data.toString()).to.equal(message); @@ -468,6 +475,44 @@ describe('http.js', function() { }); }); }); + describe('get over plain generic transport (example WebSocket) 2', function() { + it('should work as expected', function(done) { + var path = '/x'; + var message = 'Hello world'; + + var server = http2.raw.createServer({ + log: util.serverLog, + transport: function(options, start){ + var httpServer = http.createServer(); + options.server = httpServer; + var res = websocket.createServer(options, start); + res.listen = function(options, cb){ + httpServer.listen(options, cb); + }; + res.close = function (cb) { + httpServer.close(cb); + }; + return res; + } + }, function(request, response) { + expect(request.url).to.equal(path); + response.end(message); + }); + + server.listen(1239, function() { + var request = http2.raw.get({path : path, transport: function(){ + return websocket('ws://localhost:' + 1239); + }}, function(response) { + response.on('data', function(data) { + expect(data.toString()).to.equal(message); + server.close(); + done(); + }); + }); + request.end(); + }); + }); + }); describe('request over plain TCP', function() { it('should work as expected', function(done) { var path = '/x'; From 25b44ed3fc4d36e1d8fc3a0ad671697448cb9cb9 Mon Sep 17 00:00:00 2001 From: David Witherspoon Date: Tue, 7 Mar 2017 16:09:35 -0800 Subject: [PATCH 18/28] Renamed transport to create connection to be consistent with node API --- lib/http.js | 4 ++-- test/http.js | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/lib/http.js b/lib/http.js index 337268c3..3db885e2 100644 --- a/lib/http.js +++ b/lib/http.js @@ -996,9 +996,9 @@ Agent.prototype.request = function request(options, callback) { } // * HTTP/2 over generic stream transport - else if(options.transport) { + else if(options.createConnection) { endpoint = new Endpoint(this._log, 'CLIENT', this._settings); - endpoint.socket = options.transport(); + endpoint.socket = options.createConnection(options); endpoint.socket.on('error', function (error) { self._log.error('Socket error: ' + error.toString()); diff --git a/test/http.js b/test/http.js index d22e828c..47c516ca 100644 --- a/test/http.js +++ b/test/http.js @@ -422,7 +422,7 @@ describe('http.js', function() { host: 'localhost', port: portnum, path: path, - transport: function(){ + createConnection: function(){ return websocket('ws://localhost:' + portnum); } }, function(response) { @@ -462,7 +462,7 @@ describe('http.js', function() { }); server.listen(1239, function() { - var request = http2.raw.get({path : path, transport: function(){ + var request = http2.raw.get({path : path, createConnection: function(){ return websocket('ws://localhost:' + 1239); }}, function(response) { response.on('data', function(data) { From 9b4e5a57304a41023745b7d72bceb9d34a244737 Mon Sep 17 00:00:00 2001 From: David Witherspoon Date: Thu, 30 Mar 2017 14:39:59 -0700 Subject: [PATCH 19/28] Clean up formatting --- test/util.js | 1 + 1 file changed, 1 insertion(+) diff --git a/test/util.js b/test/util.js index 52c6a1be..b2c24940 100644 --- a/test/util.js +++ b/test/util.js @@ -87,3 +87,4 @@ exports.shuffleBuffers = function shuffleBuffers(buffers) { return output; }; + From 39f3392672af3afe03ee1f0eb6a176f94b704e2c Mon Sep 17 00:00:00 2001 From: David Witherspoon Date: Thu, 30 Mar 2017 14:43:14 -0700 Subject: [PATCH 20/28] Update http.js fix typo? --- lib/http.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/http.js b/lib/http.js index 3db885e2..4d4d948d 100644 --- a/lib/http.js +++ b/lib/http.js @@ -1028,7 +1028,7 @@ Agent.prototype.request = function request(options, callback) { request._start(endpoint.createStream(), options); } // * HTTP/2 over plain TCP - // TODO depricate + // TODO deprecate? else if (options.plain) { endpoint = new Endpoint(this._log, 'CLIENT', this._settings); endpoint.socket = net.connect({ From 9c77188c2ad3b8738bcf8ac7f57cacd9f3caddef Mon Sep 17 00:00:00 2001 From: David Witherspoon Date: Thu, 30 Mar 2017 14:44:14 -0700 Subject: [PATCH 21/28] Update framer.js --- lib/protocol/framer.js | 1 - 1 file changed, 1 deletion(-) diff --git a/lib/protocol/framer.js b/lib/protocol/framer.js index 147ad8e0..5df10f13 100644 --- a/lib/protocol/framer.js +++ b/lib/protocol/framer.js @@ -9,7 +9,6 @@ var Transform = require('stream').Transform; exports.Serializer = Serializer; exports.Deserializer = Deserializer; -// DPW to see what to do var logData = (process !== 'undefined' && process.env !== 'undefined' && process.env.HTTP2_LOG_DATA); var MAX_PAYLOAD_SIZE = 16384; From ac1c1c99e197fd54b6d0ef6b658d0e113dbf8c59 Mon Sep 17 00:00:00 2001 From: David Witherspoon Date: Thu, 30 Mar 2017 14:49:09 -0700 Subject: [PATCH 22/28] removed unneeded class --- lib/utils/environment.js | 8 -------- 1 file changed, 8 deletions(-) delete mode 100644 lib/utils/environment.js diff --git a/lib/utils/environment.js b/lib/utils/environment.js deleted file mode 100644 index f5f8e45b..00000000 --- a/lib/utils/environment.js +++ /dev/null @@ -1,8 +0,0 @@ -// Check what enviroment it is - -var env = {}; -env.isNode = function () { - return (typeof window === 'undefined'); -}; - -module.exports = env; \ No newline at end of file From 34ce5babfdadb77b4d0825de4244e031c7d97f26 Mon Sep 17 00:00:00 2001 From: Harold Thetiot Date: Tue, 13 Jun 2017 15:26:50 -0700 Subject: [PATCH 23/28] replace options.createConnection by options.transport and Re-use transportUrl endPoint if specified --- lib/http.js | 24 +++++++++++++++++------- 1 file changed, 17 insertions(+), 7 deletions(-) diff --git a/lib/http.js b/lib/http.js index 4d4d948d..1cb5a464 100644 --- a/lib/http.js +++ b/lib/http.js @@ -982,11 +982,21 @@ Agent.prototype.request = function request(options, callback) { request.on('response', callback); } - var key = [ - !!options.plain, - options.host, - options.port - ].join(':'); + // Re-use transportUrl endPoint if specified + if (options.transportUrl && options.transport) { + key = ([ + options.transportUrl + ]).join(':'); + + // Re-use host:port endPoint + } else { + key = ([ + !!options.plain, + options.host, + options.port + ]).join(':'); + } + var self = this; // * There's an existing HTTP/2 connection to this host @@ -996,9 +1006,9 @@ Agent.prototype.request = function request(options, callback) { } // * HTTP/2 over generic stream transport - else if(options.createConnection) { + else if(options.transport) { endpoint = new Endpoint(this._log, 'CLIENT', this._settings); - endpoint.socket = options.createConnection(options); + endpoint.socket = options.transport; endpoint.socket.on('error', function (error) { self._log.error('Socket error: ' + error.toString()); From 9146e1def4f767e42eba2ca4ecc58be744023ab4 Mon Sep 17 00:00:00 2001 From: Harold Thetiot Date: Fri, 30 Jun 2017 17:21:10 -0700 Subject: [PATCH 24/28] fix test/http 'get over plain generic transport' --- test/http.js | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/test/http.js b/test/http.js index 47c516ca..a75d0cab 100644 --- a/test/http.js +++ b/test/http.js @@ -422,9 +422,7 @@ describe('http.js', function() { host: 'localhost', port: portnum, path: path, - createConnection: function(){ - return websocket('ws://localhost:' + portnum); - } + transport: websocket('ws://localhost:' + portnum) }, function(response) { response.on('data', function(data) { expect(data.toString()).to.equal(message); @@ -440,6 +438,7 @@ describe('http.js', function() { describe('get over plain generic transport (example WebSocket)', function() { it('should work as expected', function(done) { var path = '/x'; + var portnum = 1239; var message = 'Hello world'; var server = http2.raw.createServer({ @@ -461,10 +460,11 @@ describe('http.js', function() { response.end(message); }); - server.listen(1239, function() { - var request = http2.raw.get({path : path, createConnection: function(){ - return websocket('ws://localhost:' + 1239); - }}, function(response) { + server.listen(portnum, function() { + var request = http2.raw.get({ + path: path, + transport: websocket('ws://localhost:' + portnum) + }, function(response) { response.on('data', function(data) { expect(data.toString()).to.equal(message); server.close(); From 77176f1e61ecc5214baf70a208340e2249993d7d Mon Sep 17 00:00:00 2001 From: Harold Thetiot Date: Fri, 30 Jun 2017 17:27:33 -0700 Subject: [PATCH 25/28] make that by default DeprecatedHeaders trigger only warning. --- lib/http.js | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/lib/http.js b/lib/http.js index 1cb5a464..fd379576 100644 --- a/lib/http.js +++ b/lib/http.js @@ -338,6 +338,9 @@ IncomingMessage.prototype._checkSpecialHeader = function _checkSpecialHeader(key return value; }; +// By default DeprecatedHeaders trigger only warning. +IncomingMessage.prototype.allowDeprecatedHeaders = true; + IncomingMessage.prototype._validateHeaders = function _validateHeaders(headers) { // * An HTTP/2.0 request or response MUST NOT include any of the following header fields: // Connection, Host, Keep-Alive, Proxy-Connection, Transfer-Encoding, and Upgrade. A server @@ -347,8 +350,12 @@ IncomingMessage.prototype._validateHeaders = function _validateHeaders(headers) for (var i = 0; i < deprecatedHeaders.length; i++) { var key = deprecatedHeaders[i]; if (key in headers || (key === 'te' && headers[key] !== 'trailers')) { - this._log.error({ key: key, value: headers[key] }, 'Deprecated header found'); - this.stream.reset('PROTOCOL_ERROR'); + if (this.allowDeprecatedHeaders) { + this._log.warning({ key: key, value: headers[key] }, 'Deprecated header found'); + } else { + this._log.error({ key: key, value: headers[key] }, 'Deprecated header found'); + this.stream.reset('PROTOCOL_ERROR'); + } return; } } From 41759296f9ab3bf261bf6c34937918884727a246 Mon Sep 17 00:00:00 2001 From: Harold Thetiot Date: Fri, 30 Jun 2017 17:54:28 -0700 Subject: [PATCH 26/28] make that by default DeprecatedHeaders trigger only warning on IncomingMessage but error on OutgoingMessage --- lib/http.js | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/lib/http.js b/lib/http.js index fd379576..d130d409 100644 --- a/lib/http.js +++ b/lib/http.js @@ -351,7 +351,7 @@ IncomingMessage.prototype._validateHeaders = function _validateHeaders(headers) var key = deprecatedHeaders[i]; if (key in headers || (key === 'te' && headers[key] !== 'trailers')) { if (this.allowDeprecatedHeaders) { - this._log.warning({ key: key, value: headers[key] }, 'Deprecated header found'); + this._log.warn({ key: key, value: headers[key] }, 'Deprecated header found'); } else { this._log.error({ key: key, value: headers[key] }, 'Deprecated header found'); this.stream.reset('PROTOCOL_ERROR'); @@ -416,13 +416,18 @@ OutgoingMessage.prototype._finish = function _finish() { } }; +// By default DeprecatedHeaders trigger error. +OutgoingMessage.prototype.allowDeprecatedHeaders = false; + OutgoingMessage.prototype.setHeader = function setHeader(name, value) { if (this.headersSent) { return this.emit('error', new Error('Can\'t set headers after they are sent.')); } else { name = name.toLowerCase(); if (deprecatedHeaders.indexOf(name) !== -1) { - return this.emit('error', new Error('Cannot set deprecated header: ' + name)); + if (this.allowDeprecatedHeaders === false) { + return this.emit('error', new Error('Cannot set deprecated header: ' + name)); + } } this._headers[name] = value; } From 7f8f879613b2c0ce31ba26f1bb91bda4205d6751 Mon Sep 17 00:00:00 2001 From: Harold Thetiot Date: Fri, 30 Jun 2017 18:14:12 -0700 Subject: [PATCH 27/28] revert allowDeprecatedHeader feature on IncomingMessage and OutgoingMessage --- lib/http.js | 15 +-------------- 1 file changed, 1 insertion(+), 14 deletions(-) diff --git a/lib/http.js b/lib/http.js index d130d409..07dbe683 100644 --- a/lib/http.js +++ b/lib/http.js @@ -338,9 +338,6 @@ IncomingMessage.prototype._checkSpecialHeader = function _checkSpecialHeader(key return value; }; -// By default DeprecatedHeaders trigger only warning. -IncomingMessage.prototype.allowDeprecatedHeaders = true; - IncomingMessage.prototype._validateHeaders = function _validateHeaders(headers) { // * An HTTP/2.0 request or response MUST NOT include any of the following header fields: // Connection, Host, Keep-Alive, Proxy-Connection, Transfer-Encoding, and Upgrade. A server @@ -350,12 +347,8 @@ IncomingMessage.prototype._validateHeaders = function _validateHeaders(headers) for (var i = 0; i < deprecatedHeaders.length; i++) { var key = deprecatedHeaders[i]; if (key in headers || (key === 'te' && headers[key] !== 'trailers')) { - if (this.allowDeprecatedHeaders) { - this._log.warn({ key: key, value: headers[key] }, 'Deprecated header found'); - } else { this._log.error({ key: key, value: headers[key] }, 'Deprecated header found'); this.stream.reset('PROTOCOL_ERROR'); - } return; } } @@ -415,19 +408,13 @@ OutgoingMessage.prototype._finish = function _finish() { this.once('socket', this._finish.bind(this)); } }; - -// By default DeprecatedHeaders trigger error. -OutgoingMessage.prototype.allowDeprecatedHeaders = false; - OutgoingMessage.prototype.setHeader = function setHeader(name, value) { if (this.headersSent) { return this.emit('error', new Error('Can\'t set headers after they are sent.')); } else { name = name.toLowerCase(); if (deprecatedHeaders.indexOf(name) !== -1) { - if (this.allowDeprecatedHeaders === false) { - return this.emit('error', new Error('Cannot set deprecated header: ' + name)); - } + return this.emit('error', new Error('Cannot set deprecated header: ' + name)); } this._headers[name] = value; } From 07e57f8c33113532ec3a98a7b262270773ac2df5 Mon Sep 17 00:00:00 2001 From: David Witherspoon Date: Thu, 6 Jul 2017 18:44:36 -0700 Subject: [PATCH 28/28] Added port to :authority header --- lib/http.js | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/lib/http.js b/lib/http.js index 07dbe683..affc8ca9 100644 --- a/lib/http.js +++ b/lib/http.js @@ -1211,7 +1211,14 @@ OutgoingRequest.prototype._start = function _start(stream, options) { headers[':scheme'] = options.protocol.slice(0, -1); headers[':method'] = options.method; - headers[':authority'] = options.host; + if(options.port) + { + headers[':authority'] = options.host + ':' + options.port; + } + else + { + headers[':authority'] = options.host; + } headers[':path'] = options.path; this._log.info({ scheme: headers[':scheme'], method: headers[':method'],