Skip to content

Commit 4fa5a98

Browse files
committed
Prefer HTTP/1.1 in ALPN negotiation, unless only H2 is supported
This is useful because it's simpler for many end use cases, and with the new SNI endpoints you can now externally control it anyway (e.g. http2.http1.testserver.host will prefer HTTP/2 but support both).
1 parent 2b1dc67 commit 4fa5a98

File tree

2 files changed

+34
-13
lines changed

2 files changed

+34
-13
lines changed

src/tls-handler.ts

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,8 @@ interface TlsHandlerConfig {
1414
};
1515
}
1616

17-
const supportedProtocolFilters: { [key: string]: string } = {
17+
const DEFAULT_ALPN_PROTOCOLS = ['http/1.1', 'h2'];
18+
const SNI_PROTOCOL_FILTERS: { [key: string]: string } = {
1819
'http2': 'h2',
1920
'http1': 'http/1.1'
2021
};
@@ -41,14 +42,16 @@ export async function createTlsHandler(
4142
const serverNameParts = getSNIPrefixParts(servername, tlsConfig.rootDomain);
4243

4344
let protocolFilterNames = serverNameParts.filter(protocol =>
44-
supportedProtocolFilters[protocol]
45+
SNI_PROTOCOL_FILTERS[protocol]
4546
);
4647
const serverProtocols = protocolFilterNames.length > 0
47-
? protocolFilterNames.map(protocol => supportedProtocolFilters[protocol])
48-
: Object.values(supportedProtocolFilters);
48+
? protocolFilterNames.map(protocol => SNI_PROTOCOL_FILTERS[protocol])
49+
: DEFAULT_ALPN_PROTOCOLS;
4950

50-
// Follow the clients preferences, within the protocols we support:
51-
return clientProtocols.find(protocol => serverProtocols.includes(protocol));
51+
// Enforce our own protocol preference over the client's (they can
52+
// specify a preference via SNI, if they so choose). This also means
53+
// we accept a preference order in our SNI as well e.g. http2.http1.*.
54+
return serverProtocols.find(protocol => clientProtocols.includes(protocol));
5255
},
5356
SNICallback: (domain: string, cb: Function) => {
5457
const serverNameParts = getSNIPrefixParts(domain, tlsConfig.rootDomain);

test/https.spec.ts

Lines changed: 25 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -66,7 +66,7 @@ Connection: keep-alive
6666
expect(result).to.equal('Failed: Client network socket disconnected before secure TLS connection was established');
6767
});
6868

69-
it("negotiates http2 for http2.*", async () => {
69+
it("negotiate http2 for http2.*", async () => {
7070
const conn = tls.connect({
7171
port: serverPort,
7272
servername: 'http2.localhost',
@@ -83,7 +83,7 @@ Connection: keep-alive
8383
expect(selectedProtocol).to.equal('h2');
8484
});
8585

86-
it("negotiates http1.1 for http1.*", async () => {
86+
it("negotiate http1.1 for http1.*", async () => {
8787
const conn = tls.connect({
8888
port: serverPort,
8989
servername: 'http1.localhost',
@@ -100,11 +100,29 @@ Connection: keep-alive
100100
expect(selectedProtocol).to.equal('http/1.1');
101101
});
102102

103-
it("follows client ALPN preference if all are supported", async () => {
103+
it("can use multiple SNI parts to control ALPN preference order", async () => {
104+
const conn = tls.connect({
105+
port: serverPort,
106+
servername: 'http2.http1.localhost',
107+
ALPNProtocols: ['http/1.1', 'h2'],
108+
rejectUnauthorized: false // Needed as it's untrusted
109+
});
110+
111+
const selectedProtocol = await new Promise<any>((resolve, reject) => {
112+
conn.on('secureConnect', () => resolve(conn.alpnProtocol));
113+
conn.on('error', reject);
114+
});
115+
conn.destroy();
116+
117+
expect(selectedProtocol).to.equal('h2');
118+
});
119+
120+
it("overrides client ALPN preference if no preference is shown, unless forced", async () => {
104121
await Promise.all([
105-
['h2', 'http/1.1'],
106-
['http/1.1', 'h2']
107-
].map(async (protocols) => {
122+
{ protocols: ['h2', 'http/1.1'], expected: 'http/1.1' },
123+
{ protocols: ['http/1.1', 'h2'], expected: 'http/1.1' },
124+
{ protocols: ['h2'], expected: 'h2' }
125+
].map(async ({ protocols, expected }) => {
108126
const conn = tls.connect({
109127
port: serverPort,
110128
servername: 'do-anything.localhost',
@@ -117,7 +135,7 @@ Connection: keep-alive
117135
conn.on('error', reject);
118136
});
119137
conn.destroy();
120-
expect(selectedProtocol).to.equal(protocols[0]);
138+
expect(selectedProtocol).to.equal(expected);
121139
}));
122140
});
123141

0 commit comments

Comments
 (0)