-
Notifications
You must be signed in to change notification settings - Fork 48
Conversation
This pullrequest has been opened quite a while ago, but I really would like this feature integrated in the Polymer CLI. Currently we are shipping for HTTP2 and have to do our performance testing on our live server (with prefix, to not test on production 😉 ). Would be a lot better to have it integrated directly :) |
* @returns {Promise<T>} | ||
*/ | ||
function writeFile(filename: string, contents: any) { | ||
let p = new Promise((resolve, reject) => { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
just directly return this Promise
Hey @tony19 just started to take a look at this. Sorry it's been so long. I just realized that since we're compiling to ES6 we can use async functions. They'll make the promise using code much simpler. Also, I don't think we can use destructuring in node 4. The tests pass because those code paths aren't hit yet. |
@@ -71,7 +76,8 @@ export function startServer(options: ServerOptions): Promise<http.Server> { | |||
resolve(options); | |||
}); | |||
} | |||
}).then<http.Server>((opts) => startWithPort(opts)); | |||
}).then<http.Server>((opts) => startWithPort(opts)) | |||
.catch((e) => console.error('ERROR: Server failed to start:', e)); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You need to propagate the error, so throw it after the console.error
@justinfagnani Thanks for the thorough review. I actually forgot about this PR. I'll update it to address your comments soon. |
93f22cd
to
ca7127d
Compare
@justinfagnani I've addressed all comments and added h2 push support. Note that h2 requires ALPN, which is only supported in Node5+. |
@tony19 I just made some changes that require a rebase. Sorry! (if you can give me permissions on the branch, I'll update). Looking at the new changes today :) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looking good with a few changes and a rebase. And I want to try it out :) Thanks for the work!
@@ -0,0 +1,148 @@ | |||
// copied from node's "http2" module, which should have same API as spdy |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Any chance of trying to upstream this into DefinitelyTyped?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Will do. I'll note the PR here when ready.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
typings for pem
: DefinitelyTyped/DefinitelyTyped#11666
typings for spdy
: DefinitelyTyped/DefinitelyTyped#11672
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🎉
@@ -69,4 +69,34 @@ export let args : ArgDescriptor[] = [ | |||
' Defaults to "index.html".', | |||
type: String, | |||
}, | |||
{ | |||
name: 'protocol', | |||
alias: 'h', |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We're using h
as an alias for help
in the CLI. Things are easier if there aren't any clashes. How about P
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ok, done
}, | ||
{ | ||
name: 'cert', | ||
alias: 't', |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
given we share aliases with the CLI by default, how about leaving off alias for options with decent defaults. I'd leave off all but protocol.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ok, done
* @param {ServerOptions} options | ||
* @returns {Promise<http.Server>} Promise of server | ||
*/ | ||
function createServer(app: any, options: ServerOptions): Promise<http.Server> { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
this function would look a bit nicer with async/await
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ok, done
if (isHttps(options.protocol)) { | ||
p = getTLSCertificate(options.keyPath, options.certPath) | ||
.then((keys) => { | ||
let opt = { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
const
for opt
and server
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ok, done
return Promise.resolve(server); | ||
}); | ||
} else { | ||
let spdyOptions = {protocols: [options.protocol], plain: true, ssl: false}; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
const
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ok, done
* @returns {Promise<http.Server>} Promise of server | ||
*/ | ||
function startWithPort(userOptions: ServerOptions): Promise<http.Server> { | ||
let options = applyDefaultOptions(userOptions); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
const
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ok, done
suite('node5+', () => { | ||
// test() requires old-style function to use `this.skip()`, | ||
// where `this` is the current test instance, so don't use | ||
// arrow-function for the test callback. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
you could just change the suite definition function depending on the node version:
const suiteDef = _nodeVersion < 5 ? suite.skip : suite;
suiteDef('node5+', () => {
// ...
});
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ok, done
Added support for serving files over http2, including new CLI flags: -h, --protocol string The server protocol to use {h2, https/1.1, http/1.1}. Defaults to "http/1.1". -k, --key string Path to TLS certificate private key file for https. Defaults to "key.pem". -t, --cert string Path to TLS certificate file for https. Defaults to "cert.pem". Note h2 requires ALPN, which is only supported in Node 5 or newer.
if (!port || port < 0) { | ||
port = await new Promise<number>(resolve => { | ||
findPort(8080, 8180, (ports: number[]) => { | ||
resolve(ports[0]); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It'd be nice if this function threw if no ports were unavailable.
Something like:
let ports = [8080, 8180];
if (!port || port < 0) {
ports = [port];
}
return new Promise<number>((resolve, reject) => {
findPort(ports, (availablePorts: number[]) => {
if (availablePorts.length > 0) {
resolve(ports[0]);
return;
}
reject(new Error(`No available ports for http server to bind to. Tried: ${ports.join(', ')}`));
});
});
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ah, wait, you handle this below when creating the server which is the better time to handle this error. On the other hand, it looks like this returns undefined
if no port
option is given and neither 8080 or 8180 is available. Not sure what the http server library would do with an undefined port.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Interestingly, I think find-port
has a bug because it still resolves with a port number if none are available. The redundant error checking in startWithPort()
catches the issue. The getport
module (which has the same API as find-port
) correctly returns an error in a callback. We could switch if you prefer.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I wouldn't mind switching, but let's just add a TODO for now and get this PR in :)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ok, done
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
it looks like this returns undefined if no port option is given and neither 8080 or 8180 is available.
@rictic When port
is undefined
, !port === true
, so the body of the if
-statement would run to find the next available port.
Not sure what the http server library would do with an undefined port.
@rictic It would throw an error: RangeError: "port" argument must be >= 0 and < 65536
console.error(portInUseMessage(options.port)); | ||
} | ||
console.warn('rejecting with err', err); | ||
throw new Error(err); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This won't reject the startWithPort returned promise, but it would be nice if it did.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah, good catch.
I assumed throwing an error anywhere in async/await would reject, but it seems that doesn't apply inside an event handler. Now, I'm not sure how to use async
/await
with createServer
. I'll probably have to thenify
this section of code (revert to previous promise code). Any suggestions?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yep, good catch!
async/await still helps a little here, in that you can await a Promise created inline and rejections will be wired up correctly. Something like:
await new Promise((resolve, reject) => {
server.listen(options.port, options.hostname, () => {
handleServerReady(options);
resolve();
});
server.on('error', reject);
});
return server;
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Cool, that works. Done
Added support for pushing files with h2 requests, including new CLI flag: --manifest string Path to h2-push manifest Usage: 1. Generate push manifest with http2-push-manifest node module (use multi-file format) 2. Run: polyserve --manifest push_manifest.json -P h2
@tony19 I just tried to run this branch and I'm getting:
|
@justinfagnani Fixed |
Awesome, thanks! |
Inspired by Surma's http2 go server, I thought it would be cool to test Polymer apps with http2 (although this PR uses a Node solution). I'm hoping to get this PR in so that it could be rolled into
polymer-cli
.Note that h2 requires ALPN, which is only supported in Node 5 or newer. The server will not start on an older Node version.
This PR adds a few CLI flags for h2. If the TLS key and certificate are unspecified when using https/h2, they're automatically generated upon starting the server.
Example usage
Start server with http (1.1) (default):
Start server with https (1.1):
Start server with http2:
Start http2 server with push manifest:
Start https server with specific TLS certificate and key:
Verifying HTTP2
polyserve -c . -P h2 -o
Verifying HTTP2 push
Create a push-manifest (multi-file format). For example, use http2-push-manifest to generate a manifest for Polymer CLI's shop-app template:
Specifying
-f index.html
twice "tricks" it into using the multi-file format for that same file. The end result will list many files, including Bower dependencies. In my localhost tests, I noticed pushing many files actually slows down page-load (needs more performance testing).Edit
push_manifest.json
so that only a few files are pushed. Also replace "index.html" with "/src/shop-app.html".For example:
Run:
polyserve -c . -P h2 --manifest push_manifest.json -o
Open Chrome's DevTools > Network tab, and refresh the Shop App's home page.
In DevTools > Network tab, filter for
shop-
files, and verify the Initiator column indicatesPush/Other
for the files specified in the push-manifest.