diff --git a/lib/client.js b/lib/client.js index d6ee7e2fa7a..63226de0197 100644 --- a/lib/client.js +++ b/lib/client.js @@ -1289,7 +1289,11 @@ function write (client, request) { let header if (upgrade) { - header = `${method} ${path} HTTP/1.1\r\nconnection: upgrade\r\nupgrade: ${upgrade}\r\n` + let connectionString = "upgrade"; + if (headers.toLowerCase().includes("http2-settings")) { + connectionString += ", http2-settings"; + } + header = `${method} ${path} HTTP/1.1\r\nconnection: ${connectionString}\r\nupgrade: ${upgrade}\r\n` } else if (client[kPipelining]) { header = `${method} ${path} HTTP/1.1\r\nconnection: keep-alive\r\n` } else { diff --git a/lib/core/session.js b/lib/core/session.js new file mode 100644 index 00000000000..e69de29bb2d diff --git a/lib/http2client.js b/lib/http2client.js new file mode 100644 index 00000000000..5ac49b8e5fc --- /dev/null +++ b/lib/http2client.js @@ -0,0 +1,79 @@ +"use strict"; +/* eslint-disable no-unused-vars, max-len, id-length */ + +const assert = require("assert"); +const { kUrl, kSocket, kHTTP2Opts } = require("./core/symbols"); + +const FRAME_TYPES = [ + "DATA", // standard request / response payloads + "HEADERS", // starts stream, carries request headers + "PRIORITY", // advises the priority of a stream + "RST_STREAM", // immediately terminates a stream + "SETTINGS", // inform other end of endpoint config + "PUSH_PROMISE", // pre-warns peer of wanted streams + "PING", + "GOAWAY", // graceful version of RST_STREAM + "WINDOW_UPDATE", // defines flow-control + "CONTINUATION" // continue a sequence of headers +]; + +function parseHttp2Settings(settings) { + // under RFC7540 section 3.2.1, the settings must be a base64url encoded + // SETTINGS frame. 16 bit identifiers, 32 bit values. */ + let parsed = ""; + for (let i = 0; i < Object.keys(settings).length; i += 2) { + parsed += `${settings[i]}${settings[i + 1]}\r\n`; + } + + // base64url polyfill, courtesy of @panva + let encoded; + if (Buffer.isEncoding("base64url")) { + encoded = Buffer.from(parsed) + .toString("base64url") + .replace(/[=]+$/g, ""); + } else { + encoded = Buffer.from(parsed) + .toString("base64") + .replace(/\+/g, "-") + .replace(/\//g, "_") + .replace(/[=]+$/g, ""); + } + return encoded; +} + +function http2Connect(client) { + assert(!client[kSocket]); + const { protocol, port, hostname, pathname } = client[kUrl]; + if (protocol === "https:") { + throw new Error("Invalid protocol - httpConnect upgrades http streams"); + } + // TODO: allow ALT-SVC + client.upgrade({ + path: pathname, + protocol: "h2c", + headers: { + "HTTP2-Settings": parseHttp2Settings(client[kHTTP2Opts] || {}) + } + }) + .then((res) => { + console.log("hi"); + console.log(res); + }) + .catch((err) => { + if (err.code === "UND_ERR_SOCKET" && err.message === "bad upgrade") { + // do not upgrade + } else { + throw err; + } + }); +} + +function https2Connect(client) { + const { protocol, port, hostname } = client[kUrl]; + if (protocol === "http:") { + throw new Error("Invalid protocol - https2Connect upgrades https streams"); + } + // TODO: tls negotiation comes FIRST +} + +module.exports = http2Connect;