Skip to content

Request to HTTP/2 server is not handled on("end") or on("close"), and behave differently from HTTP/1 #33537

Closed
@nwtgck

Description

@nwtgck
  • Version: v12.16.3, v14.3.0
  • Platform: Darwin **** 18.7.0 Darwin Kernel Version 18.7.0: Mon Feb 10 21:08:45 PST 2020; root:xnu-4903.278.28~1/RELEASE_X86_64 x86_64
  • Subsystem: http2
  • Google Chrome: Version 83.0.4103.61 (Official Build) (64-bit)
  • Firefox: 76.0.1 (64-bit)

What steps will reproduce the bug?

Here is a simple echo server: return posted data to the client. This reproducing program runs locally.

const https = require("https");
const http2 = require("http2");
const fs = require("fs");

const serverKeyPath = "ssl_certs/server.key";
const serverCrtPath = "ssl_certs/server.crt";
const httpsPort = 8443;

const handler = (req, res) => {
  console.log(`${req.method} ${req.url}`);
  if (req.method === "GET") {
    res.writeHead(200, {
      'Content-Type': "text/html",
    });
    res.end(`\
<input type="file" id="myfile" style="font-size: 1.5em;">
<button id="post_btn" onclick="post()">Post</button><br>
<img id="myimage">
<script>
function post() {
  console.log(window.myfile.files);
  const xhr = new XMLHttpRequest();
  xhr.open("POST", "/post_test", true);
  const file = window.myfile.files[0];
  xhr.responseType = 'blob';
  xhr.onload = () => {
    window.myimage.src = URL.createObjectURL(xhr.response);
  };
  xhr.send(file);
}
</script>
  `);
    return;
  }

  // Echo-server
  req.pipe(res);

  req.on('close', () => {
    console.log(req.url, "closed");
  });
  req.on('end', () => {
    console.log(req.url, "ended");
  });
};

// You can switch
// const usesHttp2 = false;
const usesHttp2 = true;

const listenListener = () => {
  console.log(`Listen on ${httpsPort} with '${usesHttp2 ? "http2" : "https"}'...`);
};

if (usesHttp2) {
  http2.createSecureServer(
    {
      key: fs.readFileSync(serverKeyPath),
      cert: fs.readFileSync(serverCrtPath),
      // allowHTTP1: true,
    },
    handler,
  ).listen(httpsPort, listenListener);
} else {
  https.createServer(
    {
      key: fs.readFileSync(serverKeyPath),
      cert: fs.readFileSync(serverCrtPath),
    },
    handler,
  ).listen(httpsPort, listenListener); 
}

After run, you can access to https://localhost:8443/ on your browser.

You can create server.key and server.crt as follows.

openssl req \
  -newkey rsa:2048 \
  -x509 \
  -nodes \
  -keyout server.key \
  -new \
  -out server.crt \
  -subj /CN=localhost \
  -reqexts SAN \
  -extensions SAN \
  -config <(cat /etc/ssl/openssl.cnf \
      <(printf '[SAN]\nsubjectAltName=DNS:localhost,IP:192.168.0.1')) \
  -sha256 \
  -days 3650

How often does it reproduce? Is there a required condition?

Always.

What is the expected behavior?

Here is the expected behavior. When using const usesHttp2 = false;, we have the expected behavior with the "https" module.
node-https-post-success mp4 opt

What do you see instead?

With the "http2" module, we have the following when const usesHttp2 = true;.
node-http2-post-not-success mp4 opt

When the first POST, the server never returns the posted image and callbacks for on("close") and on("end") are not called. But, when you push the "post" button again, the first data are returned and the image is displayed.

In summary, a handler with "https" works well, but the handler with "http2" doesn't work well.

Additional information

This behavior is reproduced on Google Chrome and Firefox. The posted image is https://en.wikipedia.org/wiki/File:Lenna_(test_image).png#file.
Maybe related: #32978

Metadata

Metadata

Assignees

No one assigned

    Labels

    http2Issues or PRs related to the http2 subsystem.

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions