Skip to content
This repository was archived by the owner on Sep 14, 2021. It is now read-only.

Commit bdebb16

Browse files
committed
Refactor to use async/await
1 parent 7924921 commit bdebb16

File tree

1 file changed

+102
-89
lines changed

1 file changed

+102
-89
lines changed

src/start_server.ts

+102-89
Original file line numberDiff line numberDiff line change
@@ -67,10 +67,10 @@ export interface ServerOptions {
6767
pushManifestPath?: string;
6868
}
6969

70-
function applyDefaultOptions(options: ServerOptions): ServerOptions {
70+
async function applyDefaultOptions(options: ServerOptions): Promise<ServerOptions> {
7171
const withDefaults = Object.assign({}, options);
7272
Object.assign(withDefaults, {
73-
port: options.port || 8080,
73+
port: await nextOpenPort(options.port),
7474
hostname: options.hostname || 'localhost',
7575
root: path.resolve(options.root || '.'),
7676
certPath: options.certPath || 'cert.pem',
@@ -80,28 +80,32 @@ function applyDefaultOptions(options: ServerOptions): ServerOptions {
8080
}
8181

8282
/**
83-
* @return {Promise} A Promise that completes when the server has started.
83+
* If port unspecified/negative, finds an open port on localhost
84+
* @param {number} port
85+
* @returns {Promise<number>} Promise of open port
8486
*/
85-
export function startServer(options: ServerOptions): Promise<http.Server> {
86-
return new Promise((resolve) => {
87-
options = options || {};
88-
89-
assertNodeVersion(options);
90-
91-
if (options.port) {
92-
resolve(options);
93-
} else {
94-
findPort(8080, 8180, (ports) => {
95-
options.port = ports[0];
96-
resolve(options);
87+
async function nextOpenPort(port: number): Promise<number> {
88+
if (!port || port < 0) {
89+
port = await new Promise<number>(resolve => {
90+
findPort(8080, 8180, (ports: number[]) => {
91+
resolve(ports[0]);
9792
});
98-
}
99-
})
100-
.then<http.Server>((opts) => startWithPort(opts))
101-
.catch((e) => {
102-
console.error('ERROR: Server failed to start:', e);
103-
return Promise.reject(e);
104-
});
93+
});
94+
}
95+
return port;
96+
}
97+
98+
/**
99+
* @return {Promise} A Promise that completes when the server has started.
100+
*/
101+
export async function startServer(options: ServerOptions): Promise<http.Server> {
102+
options = options || {};
103+
assertNodeVersion(options);
104+
return startWithPort(options)
105+
.catch((e) => {
106+
console.error('ERROR: Server failed to start:', e);
107+
return Promise.reject(e);
108+
});
105109
}
106110

107111
const portInUseMessage = (port: number) => `
@@ -232,59 +236,74 @@ function handleServerReady(options: ServerOptions) {
232236

233237
/**
234238
* Generates a TLS certificate for HTTPS
235-
* @param {string} keyPath path to TLS service key
236-
* @param {string} certPath path to TLS certificate
237-
* @returns {Promise<{}>} Promise of {serviceKey: string, certificate: string}
239+
* @returns {Promise<{}>} Promise of {key: string, cert: string}
238240
*/
239-
function createTLSCertificate(keyPath: string, certPath: string) {
240-
return new Promise<{}>((resolve, reject) => {
241+
async function createTLSCertificate(): Promise<{key: string, cert: string}> {
242+
const keys: any = await new Promise((resolve, reject) => {
241243
console.log('Generating TLS certificate...');
242-
pem.createCertificate({days: 1, selfSigned: true}, (err: any, keys: any) => {
244+
pem.createCertificate({
245+
days: 365,
246+
selfSigned: true
247+
}, (err: any, keys: any) => {
243248
if (err) {
244249
reject(err);
245250
} else {
246-
Promise.all([
247-
fs.writeFile(certPath, keys.certificate),
248-
fs.writeFile(keyPath, keys.serviceKey)
249-
])
250-
.then(() => resolve(keys));
251+
resolve(keys);
251252
}
252253
});
253254
});
255+
256+
return {
257+
cert: keys.certificate,
258+
key: keys.serviceKey,
259+
};
254260
}
255261

256262
/**
257263
* Gets the current TLS certificate (from current directory)
258264
* or generates one if needed
259265
* @param {string} keyPath path to TLS service key
260266
* @param {string} certPath path to TLS certificate
261-
* @returns {Promise<{}>} Promise of {serviceKey: string, certificate: string}
267+
* @returns {Promise<{}>} Promise of {key: string, cert: string}
262268
*/
263-
function getTLSCertificate(keyPath: string, certPath: string) {
264-
let certificate: string;
265-
let serviceKey: string;
269+
async function getTLSCertificate(keyPath: string, certPath: string): Promise<{key: string, cert: string}> {
270+
let certObj: {cert: string, key: string};
271+
272+
if (keyPath && certPath) {
273+
// TODO: Simplify code with ES6 destructuring when TypeScript 2.1 arrives.
274+
//
275+
// While TypeScript 2.0 already supports it, tsc does not transpile async/await
276+
// to ES5, which is scheduled for TypeScript 2.1. The advantages of async/await
277+
// outweigh that of array destructuring, so go the verbose way for now...
278+
279+
const certData = await Promise.all([
280+
fs.readFile(certPath)
281+
.then((value: Buffer) => value.toString().trim()),
282+
fs.readFile(keyPath)
283+
.then((value: Buffer) => value.toString().trim())
284+
]);
285+
const cert: string = certData[0];
286+
const key: string = certData[1];
287+
if (key && cert) {
288+
certObj = {
289+
cert: cert,
290+
key: key,
291+
};
292+
}
293+
}
266294

267-
const validate = (data: string) => {
268-
if (!data) {
269-
throw new Error('invalid data');
270-
} else {
271-
return data;
295+
if (!certObj) {
296+
certObj = await createTLSCertificate();
297+
298+
if (keyPath && certPath) {
299+
const _ = await Promise.all([
300+
fs.writeFile(certPath, certObj.cert),
301+
fs.writeFile(keyPath, certObj.key)
302+
]);
272303
}
273-
};
304+
}
274305

275-
return Promise.all([
276-
fs.readFile(certPath)
277-
.then((value: Buffer) => value.toString().trim())
278-
.then((data: string) => { certificate = validate(data); }),
279-
fs.readFile(keyPath)
280-
.then((value: Buffer) => value.toString().trim())
281-
.then((data: string) => { serviceKey = validate(data); })
282-
])
283-
.then(() => ({
284-
certificate: certificate,
285-
serviceKey: serviceKey
286-
}))
287-
.catch(() => createTLSCertificate(keyPath, certPath));
306+
return certObj;
288307
}
289308

290309
/**
@@ -307,52 +326,46 @@ function assertNodeVersion(options: ServerOptions) {
307326
* @param {ServerOptions} options
308327
* @returns {Promise<http.Server>} Promise of server
309328
*/
310-
function createServer(app: any, options: ServerOptions): Promise<http.Server> {
311-
let p: Promise<http.Server>;
329+
async function createServer(app: any, options: ServerOptions): Promise<http.Server> {
330+
const opt: any = {
331+
spdy: {protocols: [options.protocol]}
332+
};
333+
312334
if (isHttps(options.protocol)) {
313-
p = getTLSCertificate(options.keyPath, options.certPath)
314-
.then((keys: any) => {
315-
let opt = {
316-
spdy: {protocols: [options.protocol]},
317-
key: keys.serviceKey,
318-
cert: keys.certificate
319-
};
320-
let server = http.createServer(opt, app);
321-
return Promise.resolve(server);
322-
});
335+
const keys = await getTLSCertificate(options.keyPath, options.certPath);
336+
opt.key = keys.key;
337+
opt.cert = keys.cert;
323338
} else {
324-
const spdyOptions = {protocols: [options.protocol], plain: true, ssl: false};
325-
const server = http.createServer({spdy: spdyOptions}, app);
326-
p = Promise.resolve(server);
339+
opt.spdy.plain = true;
340+
opt.spdy.ssl = false;
327341
}
328-
return p;
342+
343+
return http.createServer(opt, app);
329344
}
330345

331346
/**
332347
* Starts an HTTP(S) server on a specific port
333348
* @param {ServerOptions} userOptions
334349
* @returns {Promise<http.Server>} Promise of server
335350
*/
336-
function startWithPort(userOptions: ServerOptions): Promise<http.Server> {
337-
const options = applyDefaultOptions(userOptions);
351+
async function startWithPort(userOptions: ServerOptions): Promise<http.Server> {
352+
const options = await applyDefaultOptions(userOptions);
338353
const app = getApp(options);
339354

340-
return createServer(app, options)
341-
.then((server) => new Promise<http.Server>((resolve, reject) => {
342-
server.listen(options.port, options.hostname, () => {
343-
resolve(server);
344-
handleServerReady(options);
345-
});
355+
const server = await createServer(app, options);
356+
server.listen(options.port, options.hostname, () => {
357+
handleServerReady(options);
358+
});
359+
360+
server.on('error', (err: any) => {
361+
if (err.code === 'EADDRINUSE') {
362+
console.error(portInUseMessage(options.port));
363+
}
364+
console.warn('rejecting with err', err);
365+
throw new Error(err);
366+
});
346367

347-
server.on('error', (err: any) => {
348-
if (err.code === 'EADDRINUSE') {
349-
console.error(portInUseMessage(options.port));
350-
}
351-
console.warn('rejecting with err', err);
352-
reject(err);
353-
});
354-
})
355-
);
368+
return server;
356369
}
357370

358371
/**

0 commit comments

Comments
 (0)