orator-static-server 1.0.0 → 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 +152 -59
- 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 -72
- package/debug/serve/index.html +0 -9
- package/test/Orator-Static-Server-basic_tests.js +0 -62
|
@@ -1,51 +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
|
-
|
|
31
|
+
this.serviceType = 'OratorStaticServer';
|
|
32
|
+
|
|
33
|
+
// Keep track of registered static routes for introspection
|
|
34
|
+
this.routes = [];
|
|
35
|
+
|
|
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)
|
|
25
41
|
{
|
|
26
|
-
this.
|
|
27
|
-
this.options.proxyUrl = this.fable.settings.APIProxyUrl;
|
|
42
|
+
this.oldLibMime = true;
|
|
28
43
|
}
|
|
29
44
|
}
|
|
30
45
|
|
|
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;
|
|
55
|
+
|
|
56
|
+
if (this.oldLibMime)
|
|
57
|
+
{
|
|
58
|
+
tmpHeader = libMime.lookup(pFileName);
|
|
59
|
+
}
|
|
60
|
+
else
|
|
61
|
+
{
|
|
62
|
+
tmpHeader = libMime.getType(pFileName);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
if (!tmpHeader)
|
|
66
|
+
{
|
|
67
|
+
tmpHeader = 'application/octet-stream';
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
pResponse.setHeader('Content-Type', tmpHeader);
|
|
71
|
+
}
|
|
31
72
|
|
|
32
73
|
/**
|
|
33
|
-
*
|
|
74
|
+
* Add a static file serving route to the Orator instance.
|
|
34
75
|
*
|
|
35
|
-
* @param {
|
|
36
|
-
* @param {string}
|
|
37
|
-
* @param {string
|
|
38
|
-
* @param {string
|
|
39
|
-
* @param {
|
|
40
|
-
* @
|
|
41
|
-
* @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.
|
|
42
82
|
*/
|
|
43
|
-
addStaticRoute(
|
|
83
|
+
addStaticRoute(pFilePath, pDefaultFile, pRoute, pRouteStrip, pParams)
|
|
44
84
|
{
|
|
85
|
+
if (!this.fable.Orator)
|
|
86
|
+
{
|
|
87
|
+
this.log.error('OratorStaticServer requires an Orator instance to be registered with Fable.');
|
|
88
|
+
return false;
|
|
89
|
+
}
|
|
90
|
+
|
|
45
91
|
if (typeof(pFilePath) !== 'string')
|
|
46
92
|
{
|
|
47
|
-
|
|
48
|
-
|
|
93
|
+
this.fable.log.error('A file path must be passed in as part of the server.');
|
|
94
|
+
return false;
|
|
49
95
|
}
|
|
50
96
|
|
|
51
97
|
// Default to just serving from root
|
|
@@ -55,44 +101,91 @@ class OratorStaticFileService extends FableServiceProviderBase
|
|
|
55
101
|
// Default to serving index.html
|
|
56
102
|
const tmpDefaultFile = (typeof(pDefaultFile) === 'undefined') ? 'index.html' : pDefaultFile;
|
|
57
103
|
|
|
58
|
-
|
|
104
|
+
let tmpOrator = this.fable.Orator;
|
|
59
105
|
|
|
60
|
-
|
|
61
|
-
|
|
106
|
+
this.fable.log.info('Orator mapping static route to files: '+tmpRoute+' ==> '+pFilePath+' '+tmpDefaultFile);
|
|
107
|
+
|
|
108
|
+
// Ensure FilePersistence is available for the magic subdomain subfolder check
|
|
109
|
+
if (!this.fable.FilePersistence)
|
|
62
110
|
{
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
111
|
+
this.fable.serviceManager.instantiateServiceProvider('FilePersistence');
|
|
112
|
+
}
|
|
113
|
+
|
|
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
|
+
{
|
|
138
|
+
// See if there is a magic subdomain put at the beginning of a request.
|
|
139
|
+
// If there is, then we need to see if there is a subfolder and add that to the file path
|
|
140
|
+
let tmpHostSet = pRequest.headers.host.split('.');
|
|
141
|
+
let tmpPotentialSubfolderMagicHost = false;
|
|
142
|
+
let servePath = pFilePath;
|
|
143
|
+
// Check if there are more than one host in the host header (this will be 127 a lot)
|
|
144
|
+
if (tmpHostSet.length > 1)
|
|
145
|
+
{
|
|
146
|
+
tmpPotentialSubfolderMagicHost = tmpHostSet[0];
|
|
147
|
+
}
|
|
148
|
+
if (tmpPotentialSubfolderMagicHost)
|
|
149
|
+
{
|
|
150
|
+
// Check if the subfolder exists -- this is only one dimensional for now
|
|
151
|
+
let tmpPotentialSubfolder = servePath + tmpPotentialSubfolderMagicHost;
|
|
152
|
+
if (this.fable.FilePersistence.libFS.existsSync(tmpPotentialSubfolder))
|
|
153
|
+
{
|
|
154
|
+
// If it does, then we need to add it to the file path
|
|
155
|
+
servePath = `${tmpPotentialSubfolder}/`;
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
pRequest.url = pRequest.url.split('?')[0].substr(tmpRouteStrip.length) || '/';
|
|
159
|
+
pRequest.path = function()
|
|
160
|
+
{
|
|
161
|
+
return pRequest.url;
|
|
162
|
+
};
|
|
163
|
+
|
|
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)
|
|
81
168
|
{
|
|
82
|
-
|
|
83
|
-
servePath = `${tmpPotentialSubfolder}/`;
|
|
169
|
+
tmpMimeTarget = tmpDefaultFile;
|
|
84
170
|
}
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
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
|
+
});
|
|
94
187
|
return true;
|
|
95
188
|
}
|
|
96
189
|
}
|
|
97
190
|
|
|
98
|
-
module.exports =
|
|
191
|
+
module.exports = OratorStaticServer;
|