documentation/lib/serve/server.js
Tom MacWright 631c6925d4 feat(core): Switch to Promises everywhere. Adopt Node v4 ES6 (#648)
* feat(core): Switch to Promises everywhere. Adopt Node v4 ES6

Big changes:

* Uses template strings where appropriate
* Config and argument parsing is unified and there is no such thing
  as formatterOptions anymore. All user-passed options go through
  mergeConfig.
* The node API surface changed (again): `buildSync` is removed,
  building operations return Promises.
* Now using Flow for internal type annotations.

More changes:

* Remove buildSync command
* feat(inference): Partially implement object shorthand support
* Refs #649
* Use Flow annotations to  enforce types
* Keep flow but switch to comment syntax
* Clarify types
* More flow improvements
* Turn server into class
* LinkerStack becomes class too
* Fix comment description type
* Run flow on lint
* Many more flow fixes
* More intense flow refactoring
* Simplify inference steps
* Update inference tests, flow errors down to 1
* Continue refining types
* Fix more flow issues
* Use 'use strict' everywhere
* Make 'ast' property configurable
* Fix many tests
* Fix more tests
* Fix more tests
* Fix augments
* Test Markdown meta support
* Improve test coverage
* Switch back from for of to for for speed
2017-01-27 16:14:19 -05:00

135 lines
3.4 KiB
JavaScript

/* @flow */
'use strict';
var http = require('http'),
mime = require('mime'),
pify = require('pify'),
EventEmitter = require('events').EventEmitter,
liveReload = require('tiny-lr'),
sep = require('path').sep;
/* ::
declare type ServerFile = {
relative: string,
contents: string
};
*/
/**
* A static file server designed to support documentation.js's --serve
* option. It serves from an array of Vinyl File objects (virtual files in
* memory) and exposes a `setFiles` method that both replaces the set
* of files and notifies any browsers using LiveReload to reload
* and display the new content.
* @class
* @param port server port to serve on.
*/
class Server extends EventEmitter {
/* :: _lr: Object; */
/* :: _port: number; */
/* :: _files: Array<ServerFile>; */
/* :: _http: http.Server; */
constructor(port/*: number*/) {
super();
if (typeof port !== 'number') {
throw new Error('port argument required to initialize a server');
}
this._port = port;
this._files = [];
}
/**
* Update the set of files exposed by this server and notify LiveReload
* clients
*
* @param files new content. replaces any previously-set content.
* @returns {Server} self
*/
setFiles(files/*: Array<ServerFile> */) {
this._files = files;
if (this._lr) {
this._lr.changed({ body: { files: '*' } });
}
return this;
}
/**
* Internal handler for server requests. The server serves
* very few types of things: html, images, and so on, and it
* only handles GET requests.
*
* @param {http.Request} request content wanted
* @param {http.Response} response content returned
* @returns {undefined} nothing
* @private
*/
handler(request/*: http.IncomingMessage */, response/*: http.ServerResponse */) {
var path = request.url.substring(1);
if (path === '') {
path = 'index.html';
}
for (var i = 0; i < this._files.length; i++) {
var file = this._files[i];
var filePath = file.relative.split(sep).join('/');
if (filePath === path) {
response.writeHead(200, { 'Content-Type': mime.lookup(path) });
response.end(file.contents);
return;
}
}
response.writeHead(404, { 'Content-Type': 'text/plain' });
response.end('Not found');
}
/**
* Boot up the server's HTTP & LiveReload endpoints. This method
* can be called multiple times.
*
* @returns {Promise} resolved when server starts
*/
start()/*: Promise<boolean> */ {
return new Promise(resolve => {
// idempotent
if (this._lr) {
return resolve(true);
}
this._lr = liveReload();
this._http = http.createServer(this.handler.bind(this));
return Promise.all([
pify(this._lr.listen.bind(this._lr))(35729),
pify(this._http.listen.bind(this._http))(this._port)
]).then(() => {
this.emit('listening');
return resolve(true);
});
});
}
/**
* Shut down the server's HTTP & LiveReload endpoints. This method
* can be called multiple times.
*/
stop()/*: Promise<boolean> */ {
return new Promise(resolve => {
// idempotent
if (!this._lr) {
return resolve(true);
}
this._http.close(() => {
this._lr.close();
delete this._http;
delete this._lr;
resolve(true);
});
});
}
}
module.exports = Server;