Deploying NodeJS App with HTTP2(H2) on Cloud Run

456 views Asked by At

I am trying to deploy a simple NodeJS application which is HTTP2(H2) enabled, this is working fine locally

Here is my NodeJS code

const spdy = require('spdy')
const express = require('express')
const path = require('path')
const fs = require('fs')

const port = 8080
const CERT_DIR = `${__dirname}/cert`;
const app = express()

app.get('*', (req, res) => {
  res
    .status(200)
    .json({message: 'Welcome to node HTTP2'})
})
const options = {
    key: fs.readFileSync(`${CERT_DIR}/server.key`),
    cert: fs.readFileSync(`${CERT_DIR}/server.crt`)
}

spdy
  .createServer(options, app)
  .listen(port, (error) => {
    if (error) {
      console.error(error)
    } else {
      console.log('Listening on port: ' + port + '.')
    }
  })

I have created self signed certificate and key using openssl command, here is the output in the browser locally

enter image description here

but after I deploy it to cloud run and enable use HTTP/2 end-to-end as shown below.

enter image description here

It gives me the below response

enter image description here

I have checked the logs of cloud run, and the error message says The request failed because either the HTTP response was malformed or connection to the instance had an error. Additional troubleshooting documentation can be found at: https://cloud.google.com/run/docs/troubleshooting#malformed-response-or-connection-error

enter image description here

If you ask me, why am I turning my app into HTTP2, it is because I want to make request of size more than 32MB, which is a limitation of cloud run if you are using HTTP/1 server

enter image description here

my question is, is it possible to deploy my NodeJS app with HTTP/2 server enabled to cloud run ? if so where exactly have I gone wrong above?

my curl output of localhost url

enter image description here

my curl output of cloud run url

enter image description here

2

There are 2 answers

0
Robert G On

Based on this documentation on using HTTP/2 (services):

For Cloud Run services, Cloud Run by default downgrades HTTP/2 requests to HTTP/1 when those requests are sent to the container. If you want to explicitly set your service to use HTTP/2 end-to-end, with no such downgrading, you can configure it for HTTP/2.

Your Cloud Run service must handle requests in HTTP/2 cleartext (h2c) format, because TLS is still terminated automatically by Cloud Run.

To confirm that your service supports h2c requests, test the service locally using this cURL command:

curl -i --http2-prior-knowledge http://localhost:[PORT]

EDIT:

Since you're using a self-signed certificate and CA wasn't able to verify the legitimacy of the server, there are workarounds that you may perform in order to disable strict certificate checking:

  1. You may use the -k switch to disable curl strict certificate checking.

    curl -k https://random.com
    
  2. You may also use --insecure, achieving the same result.

    curl -insecure https://random.com
    
  3. Or, you can disable SSL certificate validating by writing it to .curlrc configuration file.

    echo insecure >> $HOME/.curlrc
    

You may check the documentations below for your reference:

0
Umamaheswararao Meka On

I have taken out spdy, express and certs, I have tried using only http2 package.

const http2 = require('http2');
const fs = require("fs")
const PORT = process.env.PORT || 8080;

const server = http2.createServer();

server.on('stream', (stream, headers) => {
  const method = headers[':method']
  const path = headers[':path']

  if (method === 'GET' && path === '/') {
    stream.respond({ ':status': 200, 'content-type': 'text/plain' });
    stream.end('Hello, Welcome to HTTP2 Endpoint')
  } else if (method === 'GET' && path === '/users') {
    fs.readFile('users.json', 'utf8', (err, data) => {
      if (err) {
        // Handle error, e.g., file not found
        stream.respond({ ':status': 500, 'content-type': 'text/plain' });
        stream.end('Internal Server Error', err)
        return;
      }
      stream.respond({':status': 200,'content-type': 'application/json',})
      stream.end(data)
    })
  }
  else {
    // Handle other endpoints or return a 404
    stream.respond({ ':status': 404, 'content-type': 'text/plain' });
    stream.end('Not Found')
  }
});

server.listen(PORT, () => {
  console.log(`Server is listening on port ${PORT}`);
})

That worked for me, here is my output using curl locally.

enter image description here

Here is in the browser after deploying it to cloud run

enter image description here

I am able to make the request for the file which is more than 32MB

enter image description here

Not sure why chrome is showing h3 instead of h2 as protocol, bit it worked for me.

Please correct me if I am wrong here.

The part which I didn't like is writing if else conditions for endpoints. I am not sure if there is any better way to do this.