1

I am implementing HTTP2/SPDY push resourcing using the node module spdy. I have followed indutny's doc and have been doing test runs implementing his example into my server.js.

The problem is two fold; I am not getting any errors in the log(s) nor am I seeing the alert in the browser. I don't see any change in the Developers Console as well. If I set a bogus push URL, I get no response/errors,etc. I believe in theory, the Priority should change from medium to High(?). See Screen shoot.

Is there another way for me to test if the push is being made to the browser? Or, do I have something wrong in my script (Please check for inconsistencies)? Also, what to throw in stream.on('error', function() {});?

Testing in Chrome (on a ChromeBook), nodejs v5.1.0, npm v3.3.12 - H2 enabled is verified in Chrome.

server.js:

var environment = '../env/' + process.env.NODE_ENV
    // Process User config
  , fS = require('fs')
  , jsonFile = fS.readFileSync(environment + '/config.json')
  , jsonString, hostIp, hostPort, cacheExp, cps;

    try {
      jsonString              = JSON.parse(jsonFile);
      var SERV_HOST           = jsonString['hostIp']
        , SERV_PORT           = jsonString['hostPort']
        , CACHE_EXP           = jsonString['cacheExp']
        , CPS                 = jsonString['cps']
        , xPowerBy            = 'OceanPress'
        , xFrameOptions       = 'DENY'
        , xXSSProtection      = '1; mode=block'
        , xContentTypeOption  = 'nosniff';
    } catch (err) {
      console.log('There is an error parsing the json file : ' + err);
    }

// Load modules
var fs          = require('fs')
  , watch       = require('staticsmith-watch')
  , buffet      = require('buffet')({root: environment + '/_public'})
  , spdy        = require('spdy')
    // spdy options
  , options = {
      key: fs.readFileSync(environment + '/keys/key.pem')
    , cert: fs.readFileSync(environment + '/keys/cert.pem')
    // SPDY-specific options
    , spdy: {
          protocols: [ 'h2','spdy/3.1', 'spdy/3', 'spdy/2','http/1.1', 'http/1.0' ]
        , plain: false
        , maxStreams: 200
        , connection: {
              windowSize: 1024 * 1024
            , autoSpdy31: false
          }
      }
      // Set ip and port
    , host:   SERV_HOST
    , port:   SERV_PORT
  }
    // Security header options
  , security  = [
      { name:   'X-Powered-By',
        option: xPowerBy }
    , { name:   'x-frame-options',
        option: xFrameOptions }
    , { name:   'X-XSS-Protection',
        option: xXSSProtection }
    , { name:   'X-Content-Type-Options',
        option: xContentTypeOption }
    , { name:   'Cache-Control',
        option: CACHE_EXP }
    , { name:   'Content-Security-Policy',
        option: CPS }
    , { name:   'server',
        option: 'Who knows' }
  ];

if (process.env.NODE_ENV == 'production') {

  spdy.createServer(options, function(req, res) {

    // Add Content Security Rules
    for(var i = 0; i < security.length; i++) {
      res.setHeader(security[i].name, security[i].option);
    }

    // @see https://www.npmjs.com/package/buffet
    buffet(req, res, function (err, result) {

      // Push JavaScript asset (main.js) to the client
      var stream = res.push('/js/main.js', {
        req: {'accept': '*/*'},
        res: {'content-type': 'application/javascript'}
      });
      stream.on('acknowledge', function() {
          console.log("Stream ACK");
      });
      stream.on('error', function() {
        console.error("stream ERR");
      });
      stream.end('alert("hello from push stream!");');
      // write main response body and terminate stream
      res.end('<script src="/js/main.js"></script>');

      // There was an error serving the file? Throw it!
      if (err) {
        console.error("Error serving " + req.url + " - " + err.message);
        // Respond to the client
        res.writeHead(err.status, err.headers);
      }
    });
  }).listen(options.port, options.host);

  console.log("serving at https://" + options.host + ":" + options.port);
  console.log("On Node v" + process.versions.node);
  console.log("On npm v" + process.versions.npm);

  watch({
    pattern:    '**/*',
    livereload: true,
  });
}

UPDATE: I have also added:

  stream.on('acknowledge', function() {
      console.log('stream ACK');
  });

There is no console log written - It's like the function is dead.

Dev Console with push-stream (main.js):
Dev Console with push-stream (main.js)

  • In your index.html, do you have ` – josh3736 Dec 02 '15 at 18:11
  • Yes I do. I tried running with out it in the doc and the app fails. –  Dec 02 '15 at 18:16
  • Is `stream` emitting an error? At the minimum, you should log out error events. Silently swallowing them is an excellent way to shoot yourself in the foot. – josh3736 Dec 02 '15 at 18:18
  • Yes I agree. Hence the question; I am unsure on testing or what to throw in ```stream.on('error', function() {});``` Suggestions? Thnx. –  Dec 02 '15 at 18:21
  • I'd just throw `console.error(err)` in there so you can see if it is `stream` emitting an error. – josh3736 Dec 02 '15 at 18:23
  • Funny, it was there originally. I added it back in and retested - Throws nothing ;( –  Dec 02 '15 at 18:27

2 Answers2

2

There are a few problems here.

  1. The buffet callback is only invoked when the requested URL does not match a file on disk. Just like express middleware, it's essentially a next function. Thus, you're never actually pushing anything.

  2. The first argument to res.push is a URL, not a filesystem path.

  3. res.push will not exist on ≤ HTTP/1.1 connections; you need to make sure it's there or you'll throw an uncaught exception (and crash).

Here's a reduced working example.

spdy.createServer({
    key: fs.readFileSync('./s.key'),
    cert: fs.readFileSync('./s.crt')
}, function(req, res) {

    if (req.url == '/') {
        res.writeHead(200, { 'Content-Length': 42 });
        res.end('<h1>Hi</h1><script src="main.js"></script>');

        if (res.push) {

            // Push JavaScript asset (main.js) to the client
            var stream = res.push('/main.js', {
                req: {'accept': '**/*'},
                res: {'content-type': 'application/javascript'}
            });

            stream.on('error', function() {
                console.error(err);
            });
            stream.end('alert("hello from push stream!");');
        }
    } else {
        res.writeHead(404);
        res.end();
    }



}).listen(777);

As far as actually verifying in Chrome that things are being pushed, open a new tab and type chrome://net-internals/#http2. Click the ID of the HTTP/2 session with your server, then click the session in the left-hand pane. Mixed in with the initial request, you'll see something like:

t=  3483 [st=    19]  HTTP2_SESSION_RECV_PUSH_PROMISE
                      --> :method: GET
                          :path: /main.js
                          :scheme: https
                          :authority: localhost:777
                      --> id = 3
                      --> promised_stream_id = 4
t=  3483 [st=    19]  HTTP2_SESSION_RECV_HEADERS
                      --> fin = false
                      --> :status: 200
                      --> stream_id = 4
t=  3483 [st=    19]  HTTP2_SESSION_RECV_DATA
                      --> fin = true
                      --> size = 0
                      --> stream_id = 4
t=  3546 [st=    82]  HTTP2_STREAM_ADOPTED_PUSH_STREAM
                      --> stream_id = 4
                      --> url = "https://localhost:777/main.js"

(I did not see the Priority of main.js change in the dev tools -- it was still Medium.)

josh3736
  • 139,160
  • 33
  • 216
  • 263
  • Thnx 4 the input. 1. [Buffet](https://www.npmjs.com/package/buffet). 2. `res.push`:I read somewhere that id was CWD based.Ran your examp w/my dir setup & `res.end` is CWD and `res.push` is URL -THNX! 3. `res.push` will not exist on ≤ HTTP/1.1: I understood node-spdy took care of this (I'm prob wrong); I havent tested that far yet, just needing Push 2 work 1st. The issue I believe is that I am serving a static index file. [Does this matter?](http://stackoverflow.com/questions/33511203/http2-push-placing-script-tags-in-res-end?rq=1) How would I inject the –  Dec 02 '15 at 21:39
  • You need to push *before* handing the request off to buffet. If buffet sees a request for a static file, your callback is never invoked. Also make sure `req.url == '/'` (or whatever the URL of your main page is) -- you don't want to push the JS file along with every request for every single file on your sever. – josh3736 Dec 02 '15 at 21:57
0

Within the Chrome inspector, I discovered it is quite easily recognized when a resource has been pushed by the server.

First: Within the network view/tab, the resource(s) in question will show virtually no request sent and 'waiting(TTFB)' in the waterfall (See image below).

The theme.min.css & theme.min.js resources are pushed:

enter image description here

Second: After clicking on the pushed the resource(s), opening the "Headers" pane and inspecting the "Request Headers" panel at the bottom, check for Provisional headers are shown. If the warning is shown for the resource, then it was pushed. See this SO answer to why you will see this warning.

Headers Inspector:

enter image description here

If you need a little more detailed information about the pushed resource(s), using the chrome://net-internals/#http2 method as stated in the second part of @josh3736 answer would work too. But if you need a quick way to verify that the resource(s) has been pushed and excepted by the client, viewing the waterfall will show this.

Community
  • 1
  • 1