Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Node.js HTTP2 server Error: socket hang up

Given the latest version of Node.js with experimental HTTP2 support:

$ node -v
v9.2.0

An HTTP2 server:

var options = {
  key: getKey(),
  cert: getCert(),
  allowHTTP1: true
}

var server = http2.createSecureServer(options)
server.on('stream', onstream)
server.on('error', onerror)
server.on('connect', onconnect)
server.on('socketError', onsocketerror)
server.on('frameError', onframeerror)
server.on('remoteSettings', onremotesettings)
server.listen(8443)

function onconnect() {
  console.log('connect')
}

function onremotesettings(settings) {
  console.log('remote settings', settings)
}

function onframeerror(error) {
  console.log('frame error', error)
}

function onsocketerror(error) {
  console.log('socket error', error)
}

function onerror(error) {
  console.log(error)
}

function onstream(stream, headers) {
  console.log('stream')
}

And a request made to it:

var https = require('https')

var options = {
  method: 'GET',
  hostname: 'localhost',
  port: '8443',
  path: '/',
  protocol: 'https:',
  rejectUnauthorized: false,
  agent: false
}

var req = https.request(options, function(res){
  var body = ''
  res.setEncoding('utf8')
  res.on('data', function(data){
    body += data;
  });
  res.on('end', function(){
    callback(null, body)
  })
})

req.end()

It just hangs and eventually says:

Error: socket hang up
at createHangUpError (_http_client.js:330:15)
    at TLSSocket.socketOnEnd (_http_client.js:423:23)
    at TLSSocket.emit (events.js:164:20)
    at endReadableNT (_stream_readable.js:1054:12)
    at _combinedTickCallback (internal/process/next_tick.js:138:11)
    at process._tickCallback (internal/process/next_tick.js:180:9)

If rejectUnauthorized: true is set, then it errors:

Error: self signed certificate
    at TLSSocket.onConnectSecure (_tls_wrap.js:1036:34)
    at TLSSocket.emit (events.js:159:13)
    at TLSSocket._finishInit (_tls_wrap.js:637:8)

Not sure what is going wrong and why it won't get to the point of logging stream.

If I go in the browser and visit https://localhost:8443, and click through the warning messages, it does actually log stream and successfully make the request. But haven't been able to get node to make the request.

I would like to treat this as an HTTP1 server, so don't want to use the HTTP2 client to make the request. But tried using that and same thing.

like image 398
Lance Avatar asked Nov 23 '17 17:11

Lance


People also ask

Why does error socket hang up?

The error code [socket hang up][ECONNRESET] indicates that the target server has closed the connection with Edge Microgateway. This can be searched in the logs to determine how often it is happening.

Does Nodejs support HTTP 2?

js tutorial, we will learn about HTTP/2 and server push, how it works, and its impact on performance. We will use Node. js to build our HTTP/2 server. The HTTP protocol has been the foundation of internet communication for a long time.


1 Answers

HTTP/1 doesn't share the same request semantics as HTTP/2 so HTTP/1 clients need to be detected and handled in a HTTP/2 server. To support both you need to use the HTTP2 Compatibility API.

The "hang" occurs when a HTTP1 client connects to a HTTP/2 server with allowHTTP1: true set but doesn't handle the HTTP/1 request.

The examples are based on the Node documentation example code.

HTTP/1 and /2 Mixed Server

const http2 = require('http2')
const fs = require('fs')

var options = {
  key: fs.readFileSync('server-key.pem'), 
  cert: fs.readFileSync('server-crt.pem'), 
  //ca: fs.readFileSync('ca-crt.pem'), 
  allowHTTP1: true,
}

var server = http2.createSecureServer(options, (req, res) => {
  // detects if it is a HTTPS request or HTTP/2
  const { socket: { alpnProtocol } } = (req.httpVersion === '2.0')
    ? req.stream.session 
    : req

  res.writeHead(200, { 'content-type': 'application/json' })
  res.end(JSON.stringify({
    alpnProtocol,
    httpVersion: req.httpVersion
  }))
})

server.listen(8443)

HTTP/2 Client

const http2 = require('http2')
const fs = require('fs')

const client = http2.connect('https://localhost:8443', {
    ca: fs.readFileSync('ca-crt.pem'),
    rejectUnauthorized: true,
})
client.on('socketError', (err) => console.error(err))
client.on('error', (err) => console.error(err))

const req = client.request({ ':path': '/' })

req.on('response', (headers, flags) => {
  for (const name in headers) {
    console.log('Header: "%s" "%s"', name, headers[name])
  }
})

req.setEncoding('utf8')
let data = ''
req.on('data', chunk => data += chunk)
req.on('end', () => {
  console.log('Data:', data)
  client.destroy()
})
req.end()

Then running:

→ node http2_client.js 
(node:34542) ExperimentalWarning: The http2 module is an experimental API.
Header: ":status" "200"
Header: "content-type" "application/json"
Header: "date" "Sat, 02 Dec 2017 23:27:21 GMT"
Data: {"alpnProtocol":"h2","httpVersion":"2.0"}

HTTP/1 Client

const https = require('https')
const fs = require('fs')

var options = {
  method: 'GET',
  hostname: 'localhost',
  port: '8443',
  path: '/',
  protocol: 'https:',
  ca: fs.readFileSync('ca-crt.pem'),
  rejectUnauthorized: true,
  //agent: false
}

var req = https.request(options, function(res){
  var body = ''
  res.setEncoding('utf8')
  res.on('data', data => body += data)
  res.on('end', ()=> console.log('Body:', body))
})

req.on('response', response => {
  for (const name in response.headers) {
    console.log('Header: "%s" "%s"', name, response.headers[name])
  }
})

req.end()

Then running

→ node http1_client.js 
Header: "content-type" "application/json"
Header: "date" "Sat, 02 Dec 2017 23:27:08 GMT"
Header: "connection" "close"
Header: "transfer-encoding" "chunked"
Body: {"alpnProtocol":false,"httpVersion":"1.1"}

HTTP/2 Server

Using the plain HTTP/2 Server will work with the http2_client but will "hang" for a http1_client. The TLS connection from a HTTP/1 client will be closed when you remove allowHTTP1: true.

const http2 = require('http2')
const fs = require('fs')

var options = {
  key: fs.readFileSync('server-key.pem'), 
  cert: fs.readFileSync('server-crt.pem'), 
  ca: fs.readFileSync('ca-crt.pem'), 
  allowHTTP1: true,
}

var server = http2.createSecureServer(options)
server.on('error', error => console.log(error))
server.on('connect', conn => console.log('connect', conn))
server.on('socketError', error => console.log('socketError', error))
server.on('frameError', error => console.log('frameError', error))
server.on('remoteSettings', settings => console.log('remote settings', settings))

server.on('stream', (stream, headers) => {
  console.log('stream', headers)
  stream.respond({
    'content-type': 'application/html',
    ':status': 200
  })
  console.log(stream.session)
  stream.end(JSON.stringify({
    alpnProtocol: stream.session.socket.alpnProtocol,
    httpVersion: "2"
  }))
})

server.listen(8443)

Certs

With the extended intermediate certificate setup detailed in the gist, the complete certificate chain for the CA needs to be supplied to the clients.

cat ca/x/certs/x.public.pem > caxy.pem
cat ca/y/certs/y.public.pem >> caxy.pem

Then in the clients use this ca in the options.

{ 
  ca: fs.readFileSync('caxy.pem'),
}

These examples were run withe the following simple CA setup from this circle.com article:

To simplify the configuration, let’s grab the following CA configuration file.

wget https://raw.githubusercontent.com/anders94/https-authorized-clients/master/keys/ca.cnf

Next, we’ll create a new certificate authority using this configuration.

openssl req -new -x509 \
  -days 9999 \
  -config ca.cnf \
  -keyout ca-key.pem \
  -out ca-crt.pem

Now that we have our certificate authority in ca-key.pem and ca-crt.pem, let’s generate a private key for the server.

openssl genrsa \
  -out server-key.pem \
  4096

Our next move is to generate a certificate signing request. Again to simplify configuration, let’s use server.cnf as a configuration shortcut.

wget https://raw.githubusercontent.com/anders94/https-authorized-clients/master/keys/server.cnf

Now we’ll generate the certificate signing request.

openssl req -new \
  -config server.cnf \
  -key server-key.pem \
  -out server-csr.pem

Now let’s sign the request.

openssl x509 -req -extfile server.cnf \
  -days 999 \
  -passin "pass:password" \
  -in server-csr.pem \
  -CA ca-crt.pem \
  -CAkey ca-key.pem \
  -CAcreateserial \
  -out server-crt.pem
like image 176
Matt Avatar answered Oct 17 '22 12:10

Matt