Pipe an MJPEG stream through a Node.js proxy

Problem

Using Motion on linux, every webcam is served up as a stream on its own port. I now want to serve up those streams, all on the same port, using Node.js.

  • Edit: This solution now works. I needed to get the boundary string from the original mjpeg stream (which was "BoundaryString" in my Motion config)

app.get('/motion', function(req, res) {

    var boundary = "BoundaryString";

  var options = {
    // host to forward to
    host:   '192.168.1.2',
    // port to forward to
    port:   8302,
    // path to forward to
    path:   '/',
    // request method
    method: 'GET',
    // headers to send
    headers: req.headers
  };

  var creq = http.request(options, function(cres) {

        res.setHeader('Content-Type', 'multipart/x-mixed-replace;boundary="' + boundary + '"');
        res.setHeader('Connection', 'close');
        res.setHeader('Pragma', 'no-cache');
        res.setHeader('Cache-Control', 'no-cache, private');
        res.setHeader('Expires', 0);
        res.setHeader('Max-Age', 0);

    // wait for data
    cres.on('data', function(chunk){
      res.write(chunk);
    });

    cres.on('close', function(){
      // closed, let's end client request as well 
      res.writeHead(cres.statusCode);
      res.end();
    });

  }).on('error', function(e) {
    // we got an error, return 500 error to client and log error
    console.log(e.message);
    res.writeHead(500);
    res.end();
  });

  creq.end();

});

I would think this serves up the mjpeg stream at 192.168.1.2:8302 as /motion, but it does not. Maybe because it never ends, and this proxy example wasn't really a streaming example?

Problem courtesy of: skerit

Solution

Streaming over HTTP isn't the issue. I do that with Node regularly. I think the problem you're having is that you aren't sending a content type header to the client. You go right to writing data without sending any response headers, actually.

Be sure to send the right content type header back to the client making the request, before sending any actual content data.

You may need to handle multipart responses, if Node's HTTP client doesn't already do it for you.

Also, I recommend debugging this with Wireshark so you can see exactly what is being sent and received. That will help you narrow down problems like this quickly.

I should also note that some clients have a problem with chunked encoding, which is what Node will send if you don't specify a content length (which you can't because it's indefinite). If you need to disable chunked encoding, see my answer here: https://stackoverflow.com/a/11589937/362536 Basically, you just need to disable it: response.useChunkedEncodingByDefault = false;. Don't do this unless you need to though! And make sure to send a Connection: close in your headers with it!

Solution courtesy of: Brad

Discussion

What you need to do is request the mjpeg stream when it's necessary just in one thread and response each client with mjpeg or jpeg (if you need IE support).

Discussion courtesy of: Karén

This recipe can be found in it's original form on Stack Over Flow.