oven-sh / bun

Incredibly fast JavaScript runtime, bundler, test runner, and package manager – all in one
https://bun.sh
Other
74.5k stars 2.79k forks source link

`error: h2 is not supported` in `http2-wrapper` package #14958

Open jnv opened 1 month ago

jnv commented 1 month ago

What version of Bun is running?

1.1.34+5e5e7c60f

What platform is your computer?

Darwin 23.6.0 arm64 arm

What steps can reproduce the bug?

(For faster reproduction, here is a repository with the script below and dependencies: https://github.com/jnv/http2-wrapper-bun-repro)

  1. Setup new bun project, install http2-wrapper with bun add http2-wrapper
  2. Copy the script in usage to js file (e.g. usage.js):

    const http2 = require('http2-wrapper');
    
    const options = {
      hostname: 'nghttp2.org',
      protocol: 'https:',
      path: '/httpbin/post',
      method: 'POST',
      headers: {
        'content-length': 6,
      },
    };
    
    const request = http2.request(options, (response) => {
      console.log('statusCode:', response.statusCode);
      console.log('headers:', response.headers);
    
      const body = [];
      response.on('data', (chunk) => {
        body.push(chunk);
      });
      response.on('end', () => {
        console.log('body:', Buffer.concat(body).toString());
      });
    });
    
    request.on('error', console.error);
    
    request.write('123');
    request.end('456');
  3. Run the script with bun usage.js

What is the expected behavior?

The script exits successfully and writes a HTTP response body.

What do you see instead?

The script crashes with the following error:

113 |   }
114 | };
115 | 
116 | // This is basically inverted `closeCoveredSessions(...)`.
117 | const closeSessionIfCovered = (where, coveredSession) => {
118 |   for (let index = 0; index < where.length; index++) {
                                   ^
TypeError: undefined is not an object (evaluating 'where.length')
      at closeSessionIfCovered (/path/to/http2-wrapper-bun-repro/node_modules/http2-wrapper/source/agent.js:118:30)
      at /path/to/http2-wrapper-bun-repro/node_modules/http2-wrapper/source/agent.js:524:7
      at emit (node:events:56:48)
      at #onConnect (node:http2:2076:13)
      at onConnect (node:http2:2218:23)
      at emit (node:events:56:48)
      at handshake (node:net:90:14)
error: h2 is not supported
 code: "ERR_HTTP2_ERROR"

      at native:1:1
      at #onConnect (node:http2:2076:13)
      at onConnect (node:http2:2218:23)
      at emit (node:events:56:48)
      at handshake (node:net:90:14)

Additional information

http2-wrapper is used by got among others.

Looking for the error, I found it in bun's source. Perhaps TLSSocket is behaving differently from Node?

cirospaciari commented 4 weeks ago

Today node:http2 on bun don't support HTTP/1.1 fallback and when using TLS expects the h2 ALPN Protocol with is not being received in this case, when not using TLS its assumes h2c protocol also know as HTTP/2 over TCP or HTTP/2 over cleartext. The difference in node.js is that node.js can receive HTTP/1.1 fallback and upgrade to HTTP/2 using the Upgrade header (https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Upgrade), in the future HTTP/1.1 fallback will be implemented and this behavior will match node.js until there I recommend the server and client to use ALPN Protocol since is the fastest and most supported way of starting a HTTP/2 connection.

Directly using node:http2 works as expected:

const http2 = require('node:http2');

// Define the URL and path
const client = http2.connect('https://nghttp2.org');

client.on('error', console.error);

// Prepare the request
const options = {
  ':method': 'POST',
  ':path': '/httpbin/post',
  'content-length': 6
};

const request = client.request(options);

request.on('response', (headers, flags) => {
  console.log('statusCode:', headers[':status']);
  console.log('headers:', headers);
});

const body = [];

// Collect response data
request.on('data', (chunk) => {
  body.push(chunk);
});

request.on('end', () => {
  console.log('body:', Buffer.concat(body).toString());
  client.close(); // Close the connection
});

// Write data to the request
request.write('123');
request.end('456');

Image

cirospaciari commented 1 week ago

Not to self: after another compatbility check we also need to include the Origin H2 extension: https://datatracker.ietf.org/doc/html/rfc8336 to properly work with http2-wrapper when using h2, add support for http/1.1 fallback and upgrade from http/1.1 -> h2 using headers. But should work fine for http/1.1 when using .auto