orator-static-server 1.0.1 → 1.0.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +1 -1
- package/README.md +102 -3
- package/docs/.nojekyll +0 -0
- package/docs/README.md +49 -0
- package/docs/_sidebar.md +9 -0
- package/docs/api-reference.md +68 -0
- package/docs/cover.md +11 -0
- package/docs/getting-started.md +84 -0
- package/docs/index.html +39 -0
- package/docs/subdomain-routing.md +63 -0
- package/package.json +10 -7
- package/source/Orator-Static-Server.js +125 -90
- package/test/Orator-Static-Server_tests.js +1413 -0
- package/test/static_content/about.html +1 -0
- package/test/static_content/app.js +1 -0
- package/test/static_content/data.json +1 -0
- package/test/static_content/index.html +1 -0
- package/test/static_content/style.css +1 -0
- package/test/static_content/subsite/index.html +1 -0
- package/.vscode/launch.json +0 -46
- package/debug/Harness.js +0 -75
- package/debug/serve/index.html +0 -9
- package/test/Orator-Static-Server-basic_tests.js +0 -62
|
@@ -1,89 +1,97 @@
|
|
|
1
|
-
|
|
1
|
+
/**
|
|
2
|
+
* Orator Static Server
|
|
3
|
+
*
|
|
4
|
+
* Static file serving for Orator API servers. Handles MIME type detection,
|
|
5
|
+
* route stripping, default files, magic subdomain folder mapping, and
|
|
6
|
+
* Content-Type headers so browsers render HTML instead of downloading it.
|
|
7
|
+
*
|
|
8
|
+
* @license MIT
|
|
9
|
+
*
|
|
10
|
+
* @author Steven Velozo <steven@velozo.com>
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
const libFableServiceProviderBase = require('fable-serviceproviderbase');
|
|
2
14
|
|
|
3
15
|
const libServeStatic = require('serve-static');
|
|
4
16
|
const libFinalHandler = require('finalhandler');
|
|
17
|
+
const libMime = require('mime');
|
|
5
18
|
|
|
6
19
|
/**
|
|
7
|
-
*
|
|
20
|
+
* @class OratorStaticServer
|
|
21
|
+
* @extends libFableServiceProviderBase
|
|
22
|
+
*
|
|
23
|
+
* A service provider that manages static file serving routes on an Orator instance.
|
|
8
24
|
*/
|
|
9
|
-
class
|
|
25
|
+
class OratorStaticServer extends libFableServiceProviderBase
|
|
10
26
|
{
|
|
11
|
-
/**
|
|
12
|
-
* Construct a service instance.
|
|
13
|
-
*
|
|
14
|
-
* @param {object} pFable The fable instance for the application. Used for logging and settings.
|
|
15
|
-
* @param {object} pOptions Custom settings for this service instance.
|
|
16
|
-
* @param {string} pServiceHash The hash for this service instance.
|
|
17
|
-
*
|
|
18
|
-
* @return a static file service instance.
|
|
19
|
-
*/
|
|
20
27
|
constructor(pFable, pOptions, pServiceHash)
|
|
21
28
|
{
|
|
22
29
|
super(pFable, pOptions, pServiceHash);
|
|
23
30
|
|
|
24
|
-
|
|
25
|
-
{
|
|
26
|
-
this.log.trace('API proxy url falling back to settings...', { badUrl: this.options.proxyUrl });
|
|
27
|
-
this.options.proxyUrl = this.fable.settings.APIProxyUrl;
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
// By jacking up the log level, the static server will become more and more communicative.
|
|
31
|
-
this.logLevel = (`LogLevel` in this.options) ? this.options.LogLevel
|
|
32
|
-
: `OratorStaticServerLogLevel` in this.fable.settings ? this.fable.settings.OratorStaticServerLogLevel
|
|
33
|
-
: 0;
|
|
31
|
+
this.serviceType = 'OratorStaticServer';
|
|
34
32
|
|
|
35
|
-
//
|
|
36
|
-
this.
|
|
37
|
-
: `OratorStaticServerMagicHosts` in this.fable.settings ? this.fable.settings.OratorStaticServerLogLevel
|
|
38
|
-
: false;
|
|
33
|
+
// Keep track of registered static routes for introspection
|
|
34
|
+
this.routes = [];
|
|
39
35
|
|
|
40
|
-
//
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
36
|
+
// This is here because libMime has a breaking change from v1 to v2 and the lookup function was update to be getType per https://stackoverflow.com/a/60741078
|
|
37
|
+
// We don't want to introspect properties on this library every single time we need to check mime types.
|
|
38
|
+
// Therefore we are setting this boolean here and using it to branch.
|
|
39
|
+
this.oldLibMime = false;
|
|
40
|
+
if ('lookup' in libMime)
|
|
41
|
+
{
|
|
42
|
+
this.oldLibMime = true;
|
|
43
|
+
}
|
|
44
|
+
}
|
|
44
45
|
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
46
|
+
/**
|
|
47
|
+
* Set the Content-Type header on a response based on the file name.
|
|
48
|
+
*
|
|
49
|
+
* @param {string} pFileName - The file name (or path) to detect MIME type from.
|
|
50
|
+
* @param {Object} pResponse - The HTTP response object.
|
|
51
|
+
*/
|
|
52
|
+
setMimeHeader(pFileName, pResponse)
|
|
53
|
+
{
|
|
54
|
+
let tmpHeader;
|
|
49
55
|
|
|
50
|
-
this.
|
|
56
|
+
if (this.oldLibMime)
|
|
57
|
+
{
|
|
58
|
+
tmpHeader = libMime.lookup(pFileName);
|
|
59
|
+
}
|
|
60
|
+
else
|
|
61
|
+
{
|
|
62
|
+
tmpHeader = libMime.getType(pFileName);
|
|
63
|
+
}
|
|
51
64
|
|
|
52
|
-
if (
|
|
65
|
+
if (!tmpHeader)
|
|
53
66
|
{
|
|
54
|
-
|
|
55
|
-
this.addStaticRoute(this.defaultFolder);
|
|
67
|
+
tmpHeader = 'application/octet-stream';
|
|
56
68
|
}
|
|
57
|
-
}
|
|
58
69
|
|
|
70
|
+
pResponse.setHeader('Content-Type', tmpHeader);
|
|
71
|
+
}
|
|
59
72
|
|
|
60
73
|
/**
|
|
61
|
-
*
|
|
74
|
+
* Add a static file serving route to the Orator instance.
|
|
62
75
|
*
|
|
63
|
-
* @param {
|
|
64
|
-
* @param {string}
|
|
65
|
-
* @param {string
|
|
66
|
-
* @param {string
|
|
67
|
-
* @param {
|
|
68
|
-
* @
|
|
69
|
-
* @return {boolean} true if the handler was successfully installed, otherwise false.
|
|
76
|
+
* @param {string} pFilePath - The path on disk to serve files from.
|
|
77
|
+
* @param {string} [pDefaultFile='index.html'] - The default file for directory requests.
|
|
78
|
+
* @param {string} [pRoute='/*'] - The route pattern to match.
|
|
79
|
+
* @param {string} [pRouteStrip='/'] - URL prefix to strip before filesystem lookup.
|
|
80
|
+
* @param {object} [pParams={}] - Additional parameters passed to serve-static.
|
|
81
|
+
* @returns {boolean} true if the route was successfully installed.
|
|
70
82
|
*/
|
|
71
83
|
addStaticRoute(pFilePath, pDefaultFile, pRoute, pRouteStrip, pParams)
|
|
72
84
|
{
|
|
73
|
-
if (!
|
|
74
|
-
{
|
|
75
|
-
this.fable.log.error('Orator must be initialized before adding a static route.');
|
|
76
|
-
return false;
|
|
77
|
-
}
|
|
78
|
-
if (!'serviceServer' in this.fable.Orator)
|
|
85
|
+
if (!this.fable.Orator)
|
|
79
86
|
{
|
|
80
|
-
this.
|
|
87
|
+
this.log.error('OratorStaticServer requires an Orator instance to be registered with Fable.');
|
|
81
88
|
return false;
|
|
82
89
|
}
|
|
90
|
+
|
|
83
91
|
if (typeof(pFilePath) !== 'string')
|
|
84
92
|
{
|
|
85
|
-
|
|
86
|
-
|
|
93
|
+
this.fable.log.error('A file path must be passed in as part of the server.');
|
|
94
|
+
return false;
|
|
87
95
|
}
|
|
88
96
|
|
|
89
97
|
// Default to just serving from root
|
|
@@ -93,19 +101,45 @@ class OratorStaticFileService extends FableServiceProviderBase
|
|
|
93
101
|
// Default to serving index.html
|
|
94
102
|
const tmpDefaultFile = (typeof(pDefaultFile) === 'undefined') ? 'index.html' : pDefaultFile;
|
|
95
103
|
|
|
104
|
+
let tmpOrator = this.fable.Orator;
|
|
105
|
+
|
|
96
106
|
this.fable.log.info('Orator mapping static route to files: '+tmpRoute+' ==> '+pFilePath+' '+tmpDefaultFile);
|
|
97
107
|
|
|
98
|
-
//
|
|
99
|
-
this.fable.
|
|
108
|
+
// Ensure FilePersistence is available for the magic subdomain subfolder check
|
|
109
|
+
if (!this.fable.FilePersistence)
|
|
100
110
|
{
|
|
101
|
-
|
|
111
|
+
this.fable.serviceManager.instantiateServiceProvider('FilePersistence');
|
|
112
|
+
}
|
|
102
113
|
|
|
103
|
-
|
|
104
|
-
|
|
114
|
+
// Try the service server's built-in serveStatic first (e.g. restify's serveStaticFiles plugin).
|
|
115
|
+
// This handles MIME types, caching headers, and streaming correctly without our manual intervention.
|
|
116
|
+
if (typeof(tmpOrator.serviceServer.serveStatic) === 'function')
|
|
117
|
+
{
|
|
118
|
+
let tmpServeStaticOptions = Object.assign({ directory: pFilePath, default: tmpDefaultFile }, pParams);
|
|
119
|
+
if (tmpOrator.serviceServer.serveStatic(tmpRoute, tmpServeStaticOptions))
|
|
120
|
+
{
|
|
121
|
+
this.routes.push(
|
|
122
|
+
{
|
|
123
|
+
filePath: pFilePath,
|
|
124
|
+
defaultFile: tmpDefaultFile,
|
|
125
|
+
route: tmpRoute,
|
|
126
|
+
routeStrip: tmpRouteStrip,
|
|
127
|
+
params: pParams || {}
|
|
128
|
+
});
|
|
129
|
+
return true;
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
// Fall back to the serve-static library approach (used by the IPC service server and other
|
|
134
|
+
// service servers that don't have a built-in serveStatic implementation).
|
|
135
|
+
tmpOrator.serviceServer.get(tmpRoute,
|
|
136
|
+
(pRequest, pResponse, fNext) =>
|
|
137
|
+
{
|
|
105
138
|
// See if there is a magic subdomain put at the beginning of a request.
|
|
106
139
|
// If there is, then we need to see if there is a subfolder and add that to the file path
|
|
107
140
|
let tmpHostSet = pRequest.headers.host.split('.');
|
|
108
141
|
let tmpPotentialSubfolderMagicHost = false;
|
|
142
|
+
let servePath = pFilePath;
|
|
109
143
|
// Check if there are more than one host in the host header (this will be 127 a lot)
|
|
110
144
|
if (tmpHostSet.length > 1)
|
|
111
145
|
{
|
|
@@ -113,44 +147,45 @@ class OratorStaticFileService extends FableServiceProviderBase
|
|
|
113
147
|
}
|
|
114
148
|
if (tmpPotentialSubfolderMagicHost)
|
|
115
149
|
{
|
|
116
|
-
// Check if the subfolder exists
|
|
150
|
+
// Check if the subfolder exists -- this is only one dimensional for now
|
|
117
151
|
let tmpPotentialSubfolder = servePath + tmpPotentialSubfolderMagicHost;
|
|
118
152
|
if (this.fable.FilePersistence.libFS.existsSync(tmpPotentialSubfolder))
|
|
119
153
|
{
|
|
120
154
|
// If it does, then we need to add it to the file path
|
|
121
155
|
servePath = `${tmpPotentialSubfolder}/`;
|
|
122
|
-
if (this.logLevel > 1)
|
|
123
|
-
{
|
|
124
|
-
this.fable.log.trace(`Orator static magic mapped subdomain ${tmpPotentialSubfolderMagicHost}, altering servepath to [${servePath}]`);
|
|
125
|
-
}
|
|
126
156
|
}
|
|
127
157
|
}
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
return pRequest.url;
|
|
134
|
-
};
|
|
158
|
+
pRequest.url = pRequest.url.split('?')[0].substr(tmpRouteStrip.length) || '/';
|
|
159
|
+
pRequest.path = function()
|
|
160
|
+
{
|
|
161
|
+
return pRequest.url;
|
|
162
|
+
};
|
|
135
163
|
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
164
|
+
// When the URL is a directory (e.g. '/' or '/docs/'), use the default file for MIME detection
|
|
165
|
+
// so the browser gets text/html instead of application/octet-stream
|
|
166
|
+
let tmpMimeTarget = pRequest.url;
|
|
167
|
+
if (tmpMimeTarget.endsWith('/') || tmpMimeTarget.indexOf('.') < 0)
|
|
168
|
+
{
|
|
169
|
+
tmpMimeTarget = tmpDefaultFile;
|
|
170
|
+
}
|
|
171
|
+
this.setMimeHeader(tmpMimeTarget, pResponse);
|
|
172
|
+
|
|
173
|
+
const tmpServe = libServeStatic(servePath, Object.assign({ index: tmpDefaultFile }, pParams));
|
|
174
|
+
tmpServe(pRequest, pResponse, libFinalHandler(pRequest, pResponse));
|
|
175
|
+
// TODO: This may break things if a post request send handler is setup...
|
|
176
|
+
//fNext();
|
|
177
|
+
});
|
|
178
|
+
|
|
179
|
+
this.routes.push(
|
|
180
|
+
{
|
|
181
|
+
filePath: pFilePath,
|
|
182
|
+
defaultFile: tmpDefaultFile,
|
|
183
|
+
route: tmpRoute,
|
|
184
|
+
routeStrip: tmpRouteStrip,
|
|
185
|
+
params: pParams || {}
|
|
186
|
+
});
|
|
152
187
|
return true;
|
|
153
188
|
}
|
|
154
189
|
}
|
|
155
190
|
|
|
156
|
-
module.exports =
|
|
191
|
+
module.exports = OratorStaticServer;
|