-
Notifications
You must be signed in to change notification settings - Fork 486
/
Copy pathserver.js
131 lines (118 loc) · 3.48 KB
/
server.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
/* @flow */
// This file triggers https://github.com/prettier/prettier/issues/1151
const http = require('http');
const mime = require('mime');
const pify = require('pify');
const EventEmitter = require('events').EventEmitter;
const liveReload = require('tiny-lr');
const 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;
_disableLiveReload: boolean;
_port: number;
_files: Array<ServerFile>;
_http: http.Server;
constructor(port: number, disableLiveReload?: boolean) {
super();
if (typeof port !== 'number') {
throw new Error('port argument required to initialize a server');
}
this._port = port;
this._files = [];
this._disableLiveReload = !!disableLiveReload;
}
/**
* 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) {
let path = request.url.substring(1);
if (path === '') {
path = 'index.html';
}
for (let i = 0; i < this._files.length; i++) {
const file = this._files[i];
const 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');
}
start(): Promise<Server> {
/*
* Boot up the server's HTTP & LiveReload endpoints. This method
* can be called multiple times.
*
* @returns {Promise} resolved when server starts
*/
return new Promise(resolve => {
// idempotent
if (this._http) {
return resolve(this);
}
if (!this._disableLiveReload) {
this._lr = liveReload();
}
this._http = http.createServer(this.handler.bind(this));
return Promise.all([
this._lr && pify(this._lr.listen.bind(this._lr))(35729),
pify(this._http.listen.bind(this._http))(this._port)
]).then(() => {
this.emit('listening');
return resolve(this);
});
});
}
stop(): Promise<Server> {
/*
* Shut down the server's HTTP & LiveReload endpoints. This method
* can be called multiple times.
*/
return Promise.all([
this._http && this._http.close(),
this._lr && this._lr.close()
]).then(() => {
delete this._http;
delete this._lr;
return this;
});
}
}
module.exports = Server;