Skip to content

TCP socket yield by http 'upgrade' event stops emitting data #3321

Closed
@jcoglan

Description

I maintain the websocket-driver module. This module implements the WebSocket protocol but does not contain any of its own I/O logic. The user hooks up the driver to an I/O stream of their choosing.

The following script sets up two servers that should be functionally identical, one using the http.Server upgrade event, and one using a net server. Each server runs the following actions:

  • Print all data coming in over the socket to stdout
  • Create a WebSocket driver to handle the connection
  • Pipe data back and forth between socket and driver.io
  • When a message is received, echo it back
  • When the protocol is closed, print the code and reason

Following the servers is a function testServer() to test each server using a client. This function makes a TCP connection to a port, attaches a driver to that connection. When the connection is established, a random message of a certain size is sent. When the message is received, the client closes the connection. And when the connection becomes closed, we print the code and reason.

var crypto    = require('crypto'),
    http      = require('http'),
    net       = require('net'),
    websocket = require('websocket-driver');


var httpServer = http.createServer();

httpServer.on('upgrade', function(request, socket, body) {
  socket.on('data', function(data) {
    console.log('[http received]    ', data);
  });

  var driver = websocket.http(request);

  driver.io.write(body);
  socket.pipe(driver.io).pipe(socket);

  driver.on('message', function(message) { driver.binary(message.data) });

  driver.on('close', function(close) {
    console.log('[http close]       ', close.code, close.reason);
  });

  driver.start();
});

httpServer.listen(4007);


var tcpServer = net.createServer(function(socket) {
  socket.on('data', function(data) {
    console.log('[tcp received]     ', data);
  });

  var driver = websocket.server();

  driver.on('connect', function() {
    if (websocket.isWebSocket(driver)) driver.start();
  });

  socket.pipe(driver.io).pipe(socket);

  driver.on('message', function(message) { driver.binary(message.data) });

  driver.on('close', function(close) {
    console.log('[tcp close]        ', close.code, close.reason);
  });

  driver.start();
});

tcpServer.listen(4008);


function testServer(port) {
  var client  = net.connect(port, 'localhost'),
      driver  = websocket.client('ws://localhost:' + port + '/'),
      payload = crypto.randomBytes(16379);

  client.pipe(driver.io).pipe(client);

  driver.on('open', function() {
    console.log('[client open]      ', port);
    driver.binary(payload);
  });

  driver.on('message', function(message) {
    console.log('[client received]  ', message.data.length);
    driver.close('test done', 1000);
  });

  driver.on('close', function(close) {
    console.log('[client close]     ', close.code, close.reason);
    client.end();
  });

  driver.start();
}

testServer(4007);

The script as given sents a message of 16,379 bytes to the HTTP server on port 4007. Here's what I get when I run this program:

[client open]       4007
[http received]     <Buffer 82 fe 3f fb d2 3c 92 42 2a 50 1e 02 03 fe 55 05 d7 77 e3 c1 75 58 b1 76 cb be 84 5f 82 3a 39 70 41 a4 b2 f8 1c ad 40 8a fa 44 ed d5 ff e7 63 93 28 0c ... >
[client received]   16379
[http received]     <Buffer 88 8b 41 1d dd 76 42 f5 a9 13 32 69 fd 12 2e 73 b8>
[http close]        1000 test done
[client close]      1000 test done

The final buffer beginning with 88 is a WebSocket closing frame, all previous buffers are part of the random message.

If I switch the port to 4008, I get:

[tcp received]      <Buffer 47 45 54 20 2f 20 48 54 54 50 2f 31 2e 31 0d 0a 48 6f 73 74 3a 20 6c 6f 63 61 6c 68 6f 73 74 3a 34 30 30 38 0d 0a 55 70 67 72 61 64 65 3a 20 77 65 62 ... >
[client open]       4008
[tcp received]      <Buffer 82 fe 3f fb 1a a4 d1 cb f5 ab bb d4 df 4e fd b9 c4 59 40 08 37 07 70 85 61 90 b7 aa 21 2f 2b d3 65 66 6b 68 26 fe 4a 48 60 d5 de d6 04 82 77 da cd dd ... >
[client received]   16379
[tcp received]      <Buffer 88 8b 0a df 58 93 09 37 2c f6 79 ab 78 f7 65 b1 3d>
[tcp close]         1000 test done
[client close]      1000 test done

So both servers work as expected. However, if I go back to port 4007 and increase the message size to 16,380:

[client open]       4007
[http received]     <Buffer 82 fe 3f fc 79 58 3a 8a 62 a0 39 9d 12 4e dc 57 70 d6 3d e6 c1 6e 3e 7a 4a df b6 6f ac 0b 66 fc be 20 14 38 52 0a 3f de a7 e9 1b 9b 8e 7c 96 6d 23 40 ... >
[client received]   16380

In this case, the HTTP receives the whole data message and echoes it back to the client, but it never receives the closing frame beginning 88 from the client. Given the client is the same in all experiments, and the TCP server works fine with larger messages, this seems to be an issue with the socket yielded by the HTTP module.

It doesn't seem to be a case that the stream simply stops emitting data after 16,379 bytes because you can send it much larger payloads, and it will receive them in multiple chunks and echo them:

[client open]       4007
[http received]     <Buffer 82 fe ff ff a2 37 a2 9b 91 06 1a 4a 91 fc ac 14 b4 c3 63 99 86 21 0f a8 b2 77 87 95 0c 48 d8 36 aa 16 fb 03 e1 b3 c7 92 8b 87 53 59 00 7f 96 47 d3 76 ... >
[http received]     <Buffer a7 eb df a8 c7 a7 78>
[client received]   65535

I'm sorry I can't make the test case any more simple, I'm not sure what to investigate next.

This problem exists in versions 4.0.0 and 4.1.2, but not in any earlier releases as far as I can tell.

Metadata

Assignees

No one assigned

    Labels

    httpIssues or PRs related to the http subsystem.

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions