alchemymvc 1.2.8 → 1.3.1
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/lib/app/behaviour/sluggable_behaviour.js +4 -2
- package/lib/app/conduit/http_conduit.js +7 -2
- package/lib/app/conduit/loopback_conduit.js +2 -2
- package/lib/app/conduit/socket_conduit.js +20 -5
- package/lib/app/controller/alchemy_info_controller.js +4 -8
- package/lib/app/helper/backed_map.js +2 -2
- package/lib/app/helper/router_helper.js +98 -24
- package/lib/app/helper_controller/controller.js +45 -30
- package/lib/app/helper_datasource/00-nosql_datasource.js +44 -10
- package/lib/app/helper_field/enum_field.js +4 -4
- package/lib/app/helper_field/schema_field.js +50 -36
- package/lib/app/helper_model/document.js +81 -46
- package/lib/app/helper_model/field_set.js +11 -0
- package/lib/app/helper_model/model.js +107 -53
- package/lib/app/helper_validator/00_validator.js +38 -6
- package/lib/app/helper_validator/not_empty_validator.js +1 -3
- package/lib/app/routes.js +7 -1
- package/lib/bootstrap.js +1 -0
- package/lib/class/conduit.js +438 -290
- package/lib/class/controller.js +18 -15
- package/lib/class/datasource.js +19 -8
- package/lib/class/document.js +3 -3
- package/lib/class/field.js +34 -3
- package/lib/class/inode.js +27 -0
- package/lib/class/inode_file.js +204 -4
- package/lib/class/migration.js +2 -1
- package/lib/class/model.js +16 -5
- package/lib/class/path_definition.js +76 -120
- package/lib/class/path_param_definition.js +202 -0
- package/lib/class/postponement.js +573 -0
- package/lib/class/route.js +193 -33
- package/lib/class/router.js +22 -4
- package/lib/class/schema.js +47 -11
- package/lib/class/schema_client.js +65 -35
- package/lib/class/session.js +138 -12
- package/lib/class/sitemap.js +341 -0
- package/lib/core/base.js +13 -3
- package/lib/core/client_alchemy.js +78 -7
- package/lib/core/client_base.js +16 -10
- package/lib/core/middleware.js +56 -45
- package/lib/init/alchemy.js +124 -11
- package/lib/init/constants.js +11 -0
- package/lib/init/functions.js +163 -86
- package/lib/stages.js +18 -3
- package/package.json +6 -6
package/lib/class/conduit.js
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
1
|
+
const FILECACHE = alchemy.getCache('served_files'),
|
|
2
|
+
RX_TEXT = /svg|xml|javascript|text/i;
|
|
3
|
+
|
|
4
|
+
var libstream = alchemy.use('stream'),
|
|
3
5
|
libpath = alchemy.use('path'),
|
|
4
|
-
libmime = alchemy.use('mime'),
|
|
5
6
|
libua = alchemy.use('useragent'),
|
|
6
7
|
zlib = alchemy.use('zlib'),
|
|
7
8
|
BODY = Symbol('body'),
|
|
@@ -345,7 +346,7 @@ Conduit.setMethod(function setRequestFiles(files) {
|
|
|
345
346
|
*
|
|
346
347
|
* @author Jelle De Loecker <jelle@elevenways.be>
|
|
347
348
|
* @since 1.2.0
|
|
348
|
-
* @version 1.
|
|
349
|
+
* @version 1.3.0
|
|
349
350
|
*
|
|
350
351
|
* @param {Conduit} conduit
|
|
351
352
|
* @param {Array} files
|
|
@@ -370,7 +371,7 @@ function _setRequestFiles(conduit, files, target) {
|
|
|
370
371
|
|
|
371
372
|
_setRequestFiles(conduit, entry, context);
|
|
372
373
|
} else {
|
|
373
|
-
target[key] = Classes.Alchemy.Inode.File.
|
|
374
|
+
target[key] = Classes.Alchemy.Inode.File.fromUntrusted(entry);
|
|
374
375
|
}
|
|
375
376
|
}
|
|
376
377
|
}
|
|
@@ -980,7 +981,7 @@ Conduit.setMethod(function getRouteByName(name) {
|
|
|
980
981
|
*
|
|
981
982
|
* @author Jelle De Loecker <jelle@develry.be>
|
|
982
983
|
* @since 0.2.0
|
|
983
|
-
* @version 1.
|
|
984
|
+
* @version 1.3.0
|
|
984
985
|
*
|
|
985
986
|
* @param {Route} after_route Only check routes after this one
|
|
986
987
|
*
|
|
@@ -1009,7 +1010,7 @@ Conduit.setMethod(async function parseRoute(after_route) {
|
|
|
1009
1010
|
|
|
1010
1011
|
if (temp) {
|
|
1011
1012
|
this.route = temp.route;
|
|
1012
|
-
this.
|
|
1013
|
+
this.setRouteParameters(temp.parameters);
|
|
1013
1014
|
this.route_string_parameters = temp.original_parameters;
|
|
1014
1015
|
this.path_definition = temp.definition;
|
|
1015
1016
|
} else {
|
|
@@ -1048,11 +1049,11 @@ Conduit.setMethod(async function parseRoute(after_route) {
|
|
|
1048
1049
|
}
|
|
1049
1050
|
|
|
1050
1051
|
if (temp) {
|
|
1051
|
-
this.
|
|
1052
|
+
this.setRouteParameters(temp.parameters);
|
|
1052
1053
|
this.route_string_parameters = temp.original_parameters || {};
|
|
1053
1054
|
this.path_definition = temp.definition;
|
|
1054
1055
|
} else {
|
|
1055
|
-
this.
|
|
1056
|
+
this.setRouteParameters();
|
|
1056
1057
|
}
|
|
1057
1058
|
}
|
|
1058
1059
|
});
|
|
@@ -1230,19 +1231,67 @@ Conduit.setMethod(function callHandler() {
|
|
|
1230
1231
|
this.route.callHandler(this);
|
|
1231
1232
|
});
|
|
1232
1233
|
|
|
1234
|
+
/**
|
|
1235
|
+
* Put this request in a queue
|
|
1236
|
+
*
|
|
1237
|
+
* @author Jelle De Loecker <jelle@elevenways.be>
|
|
1238
|
+
* @since 1.3.1
|
|
1239
|
+
* @version 1.3.1
|
|
1240
|
+
*
|
|
1241
|
+
* @param {Object} options Options or url
|
|
1242
|
+
*/
|
|
1243
|
+
Conduit.setMethod(function postponeAndQueue(options) {
|
|
1244
|
+
|
|
1245
|
+
if (!options) {
|
|
1246
|
+
options = {};
|
|
1247
|
+
}
|
|
1248
|
+
|
|
1249
|
+
const postponement = this.postponeRequest({
|
|
1250
|
+
put_in_queue : true,
|
|
1251
|
+
});
|
|
1252
|
+
|
|
1253
|
+
return postponement;
|
|
1254
|
+
});
|
|
1255
|
+
|
|
1256
|
+
/**
|
|
1257
|
+
* Postpone the response and the request
|
|
1258
|
+
*
|
|
1259
|
+
* This does not stop the current request from processing.
|
|
1260
|
+
*
|
|
1261
|
+
* @author Jelle De Loecker <jelle@elevenways.be>
|
|
1262
|
+
* @since 1.3.1
|
|
1263
|
+
* @version 1.3.1
|
|
1264
|
+
*
|
|
1265
|
+
* @param {Number|Object} options Options or time to wait
|
|
1266
|
+
*
|
|
1267
|
+
* @return {Alchemy.Conduit.Postponement}
|
|
1268
|
+
*/
|
|
1269
|
+
Conduit.setMethod(function postponeRequest(options) {
|
|
1270
|
+
|
|
1271
|
+
let postponement = this.postponeResponse(options);
|
|
1272
|
+
|
|
1273
|
+
this.afterOnce('get-postponed-response', () => {
|
|
1274
|
+
this.callMiddleware();
|
|
1275
|
+
});
|
|
1276
|
+
|
|
1277
|
+
return postponement;
|
|
1278
|
+
});
|
|
1279
|
+
|
|
1233
1280
|
/**
|
|
1234
1281
|
* End the current request with a 202 status
|
|
1235
|
-
* and tell the client to look at another url later
|
|
1282
|
+
* and tell the client to look at another url later.
|
|
1236
1283
|
*
|
|
1237
|
-
*
|
|
1284
|
+
* This does not stop the current request from processing.
|
|
1285
|
+
*
|
|
1286
|
+
* @author Jelle De Loecker <jelle@elevenways.be>
|
|
1238
1287
|
* @since 1.1.0
|
|
1239
|
-
* @version 1.
|
|
1288
|
+
* @version 1.3.1
|
|
1240
1289
|
*
|
|
1241
|
-
* @param {
|
|
1290
|
+
* @param {Number|Object} options Options or time to wait
|
|
1291
|
+
*
|
|
1292
|
+
* @return {Alchemy.Conduit.Postponement}
|
|
1242
1293
|
*/
|
|
1243
|
-
Conduit.setMethod(function
|
|
1244
|
-
|
|
1245
|
-
let session = this.getSession();
|
|
1294
|
+
Conduit.setMethod(function postponeResponse(options) {
|
|
1246
1295
|
|
|
1247
1296
|
if (typeof options == 'number') {
|
|
1248
1297
|
options = {
|
|
@@ -1252,55 +1301,100 @@ Conduit.setMethod(function postpone(options) {
|
|
|
1252
1301
|
options = {};
|
|
1253
1302
|
}
|
|
1254
1303
|
|
|
1255
|
-
|
|
1256
|
-
|
|
1304
|
+
return this._postpone(options);
|
|
1305
|
+
});
|
|
1257
1306
|
|
|
1258
|
-
|
|
1259
|
-
|
|
1260
|
-
|
|
1261
|
-
|
|
1307
|
+
/**
|
|
1308
|
+
* Handle the postponement
|
|
1309
|
+
*
|
|
1310
|
+
* @author Jelle De Loecker <jelle@elevenways.be>
|
|
1311
|
+
* @since 1.3.1
|
|
1312
|
+
* @version 1.3.1
|
|
1313
|
+
*
|
|
1314
|
+
* @param {Object} options
|
|
1315
|
+
*
|
|
1316
|
+
* @return {Alchemy.Conduit.Postponement}
|
|
1317
|
+
*/
|
|
1318
|
+
Conduit.setMethod(function _postpone(options) {
|
|
1262
1319
|
|
|
1263
|
-
let
|
|
1264
|
-
url = '/alchemy/postponed/' + postponed_id;
|
|
1320
|
+
let session = this.getSession();
|
|
1265
1321
|
|
|
1266
|
-
|
|
1267
|
-
this.response.setHeader('Location', url);
|
|
1268
|
-
this.response.setHeader('Content-Type', 'text/html');
|
|
1322
|
+
let postponement = session.getExistingPostponement(this);
|
|
1269
1323
|
|
|
1270
|
-
if (
|
|
1271
|
-
|
|
1324
|
+
if (postponement) {
|
|
1325
|
+
return postponement.showPostponementMessage(this);
|
|
1272
1326
|
}
|
|
1273
1327
|
|
|
1274
|
-
|
|
1275
|
-
this.response.writeHead(202);
|
|
1328
|
+
let response = this.response;
|
|
1276
1329
|
|
|
1277
|
-
|
|
1278
|
-
|
|
1330
|
+
this.postponed_response = response;
|
|
1331
|
+
|
|
1332
|
+
// Make sure the scene id exists
|
|
1333
|
+
this.createScene();
|
|
1334
|
+
|
|
1335
|
+
postponement = session.postpone(this, options);
|
|
1336
|
+
|
|
1337
|
+
if (options.put_in_queue) {
|
|
1338
|
+
postponement.putInQueue();
|
|
1339
|
+
}
|
|
1340
|
+
|
|
1341
|
+
if (options.show_postponement_message !== false) {
|
|
1342
|
+
postponement.showPostponementMessage();
|
|
1343
|
+
}
|
|
1279
1344
|
|
|
1280
1345
|
// Nullify the response
|
|
1281
1346
|
this.response = null;
|
|
1282
1347
|
|
|
1283
1348
|
// Set the original url
|
|
1284
|
-
this.overrideResponseUrl(this.url)
|
|
1349
|
+
this.overrideResponseUrl(this.url);
|
|
1350
|
+
|
|
1351
|
+
// Return the postponement
|
|
1352
|
+
return postponement;
|
|
1285
1353
|
});
|
|
1286
1354
|
|
|
1287
1355
|
/**
|
|
1288
1356
|
* Set the response url
|
|
1289
1357
|
*
|
|
1358
|
+
* @deprecated Use {@link #setResponseUrl} instead
|
|
1359
|
+
*
|
|
1290
1360
|
* @author Jelle De Loecker <jelle@develry.be>
|
|
1291
1361
|
* @since 1.2.5
|
|
1292
|
-
* @version 1.
|
|
1362
|
+
* @version 1.3.0
|
|
1293
1363
|
*
|
|
1294
1364
|
* @param {String|RURL} url
|
|
1295
1365
|
*/
|
|
1296
1366
|
Conduit.setMethod(function overrideResponseUrl(url) {
|
|
1367
|
+
return this.setResponseUrl(url);
|
|
1368
|
+
});
|
|
1297
1369
|
|
|
1298
|
-
|
|
1299
|
-
|
|
1370
|
+
/**
|
|
1371
|
+
* Set the response url
|
|
1372
|
+
*
|
|
1373
|
+
* @author Jelle De Loecker <jelle@elevenways.be>
|
|
1374
|
+
* @since 1.3.0
|
|
1375
|
+
* @version 1.3.0
|
|
1376
|
+
*
|
|
1377
|
+
* @param {String|RURL|Boolean} new_url
|
|
1378
|
+
*/
|
|
1379
|
+
Conduit.setMethod(function setResponseUrl(new_url) {
|
|
1380
|
+
|
|
1381
|
+
if (new_url == null) {
|
|
1382
|
+
return;
|
|
1300
1383
|
}
|
|
1301
1384
|
|
|
1302
|
-
|
|
1303
|
-
|
|
1385
|
+
if (!new_url) {
|
|
1386
|
+
this.renderer.history = false;
|
|
1387
|
+
return;
|
|
1388
|
+
} else {
|
|
1389
|
+
this.renderer.history = true;
|
|
1390
|
+
}
|
|
1391
|
+
|
|
1392
|
+
if (typeof new_url != 'string') {
|
|
1393
|
+
new_url = String(new_url);
|
|
1394
|
+
}
|
|
1395
|
+
|
|
1396
|
+
this.setHeader('x-history-url', new_url);
|
|
1397
|
+
this.expose('redirected_to', new_url);
|
|
1304
1398
|
});
|
|
1305
1399
|
|
|
1306
1400
|
/**
|
|
@@ -1409,15 +1503,17 @@ Conduit.setMethod(function redirect(status, options) {
|
|
|
1409
1503
|
/**
|
|
1410
1504
|
* Respond with an error
|
|
1411
1505
|
*
|
|
1412
|
-
* @author Jelle De Loecker <jelle@
|
|
1506
|
+
* @author Jelle De Loecker <jelle@elevenways.be>
|
|
1413
1507
|
* @since 0.2.0
|
|
1414
|
-
* @version 1.1
|
|
1508
|
+
* @version 1.3.1
|
|
1415
1509
|
*
|
|
1416
1510
|
* @param {Nulber} status Response statuscode
|
|
1417
1511
|
* @param {Error} message Optional error to send
|
|
1418
|
-
* @param {Boolean}
|
|
1512
|
+
* @param {Boolean} print_error Print the error, defaults to true
|
|
1419
1513
|
*/
|
|
1420
|
-
Conduit.setMethod(function error(status, message,
|
|
1514
|
+
Conduit.setMethod(function error(status, message, print_error) {
|
|
1515
|
+
|
|
1516
|
+
let print_dev = false;
|
|
1421
1517
|
|
|
1422
1518
|
if (status instanceof Classes.Alchemy.Error.HTTP) {
|
|
1423
1519
|
message = status;
|
|
@@ -1439,14 +1535,33 @@ Conduit.setMethod(function error(status, message, printError) {
|
|
|
1439
1535
|
message = error;
|
|
1440
1536
|
}
|
|
1441
1537
|
|
|
1442
|
-
let
|
|
1538
|
+
let is_400 = (status >= 400 && status <= 500);
|
|
1443
1539
|
|
|
1444
|
-
if (
|
|
1445
|
-
|
|
1446
|
-
}
|
|
1447
|
-
|
|
1448
|
-
|
|
1449
|
-
|
|
1540
|
+
if (alchemy.settings.environment == 'dev') {
|
|
1541
|
+
print_dev = true;
|
|
1542
|
+
}
|
|
1543
|
+
|
|
1544
|
+
if (print_error == null) {
|
|
1545
|
+
if (is_400) {
|
|
1546
|
+
print_error = false;
|
|
1547
|
+
} else {
|
|
1548
|
+
print_error = true;
|
|
1549
|
+
}
|
|
1550
|
+
}
|
|
1551
|
+
|
|
1552
|
+
if (print_dev) {
|
|
1553
|
+
let subject = 'Error found on ' + this.original_path + '';
|
|
1554
|
+
log.error(subject + ':\n' + message, this);
|
|
1555
|
+
} else if (print_error) {
|
|
1556
|
+
let subject = 'Error found on ' + this.original_path + '';
|
|
1557
|
+
|
|
1558
|
+
if (is_400) {
|
|
1559
|
+
log.error(subject + ':\n' + message);
|
|
1560
|
+
} else if (message instanceof Error) {
|
|
1561
|
+
alchemy.printLog('error', [subject, String(message), message], {err: message, level: -2});
|
|
1562
|
+
} else {
|
|
1563
|
+
log.error(subject + ':\n' + message);
|
|
1564
|
+
}
|
|
1450
1565
|
}
|
|
1451
1566
|
|
|
1452
1567
|
// Make sure the client doesn't expect compression
|
|
@@ -1470,7 +1585,12 @@ Conduit.setMethod(function error(status, message, printError) {
|
|
|
1470
1585
|
} else {
|
|
1471
1586
|
this.set('status', status);
|
|
1472
1587
|
this.set('message', message);
|
|
1473
|
-
|
|
1588
|
+
|
|
1589
|
+
if (alchemy.isTooBusyForRequests()) {
|
|
1590
|
+
this._end(`Error ${status}:\n${message}`);
|
|
1591
|
+
} else {
|
|
1592
|
+
this.render(['error/' + status, 'error/unknown']);
|
|
1593
|
+
}
|
|
1474
1594
|
}
|
|
1475
1595
|
} else {
|
|
1476
1596
|
// Requests for images or scripts just get a non-expensive string response
|
|
@@ -1583,9 +1703,9 @@ Conduit.setMethod(function notModified() {
|
|
|
1583
1703
|
/**
|
|
1584
1704
|
* Respond with text. Objects get JSON-dry encoded
|
|
1585
1705
|
*
|
|
1586
|
-
* @author Jelle De Loecker <jelle@
|
|
1706
|
+
* @author Jelle De Loecker <jelle@elevenways.be>
|
|
1587
1707
|
* @since 0.2.0
|
|
1588
|
-
* @version 1.
|
|
1708
|
+
* @version 1.3.0
|
|
1589
1709
|
*
|
|
1590
1710
|
* @param {String|Object} message
|
|
1591
1711
|
*/
|
|
@@ -1659,7 +1779,7 @@ Conduit.setMethod(function end(message) {
|
|
|
1659
1779
|
|
|
1660
1780
|
// Compress the output if the client accepts it,
|
|
1661
1781
|
// but only if the file is at least 150 bytes
|
|
1662
|
-
if (alchemy.settings.compression && message.length > 150 && this.accepts('gzip')) {
|
|
1782
|
+
if (alchemy.settings.compression !== false && message.length > 150 && this.accepts('gzip')) {
|
|
1663
1783
|
|
|
1664
1784
|
// Set the decompressed content-length for use in progress bars
|
|
1665
1785
|
this.setHeader('x-decompressed-content-length', Buffer.byteLength(message));
|
|
@@ -1683,9 +1803,9 @@ Conduit.setMethod(function end(message) {
|
|
|
1683
1803
|
/**
|
|
1684
1804
|
* Call the actual end method
|
|
1685
1805
|
*
|
|
1686
|
-
* @author Jelle De Loecker <jelle@
|
|
1806
|
+
* @author Jelle De Loecker <jelle@elevenways.be>
|
|
1687
1807
|
* @since 0.2.0
|
|
1688
|
-
* @version 1.1
|
|
1808
|
+
* @version 1.3.1
|
|
1689
1809
|
*/
|
|
1690
1810
|
Conduit.setMethod(function _end(message, encoding = 'utf-8') {
|
|
1691
1811
|
|
|
@@ -1701,6 +1821,8 @@ Conduit.setMethod(function _end(message, encoding = 'utf-8') {
|
|
|
1701
1821
|
|
|
1702
1822
|
this._end_arguments = args;
|
|
1703
1823
|
|
|
1824
|
+
this.emit('after-postponed-end', args);
|
|
1825
|
+
|
|
1704
1826
|
return;
|
|
1705
1827
|
}
|
|
1706
1828
|
|
|
@@ -1865,295 +1987,237 @@ function bufferToStream(buffer) {
|
|
|
1865
1987
|
}
|
|
1866
1988
|
|
|
1867
1989
|
/**
|
|
1868
|
-
* Send a file to the browser
|
|
1869
|
-
* Uses cache-control by default.
|
|
1990
|
+
* Send a file to the browser
|
|
1870
1991
|
*
|
|
1871
|
-
* @author Jelle De Loecker <jelle@
|
|
1992
|
+
* @author Jelle De Loecker <jelle@elevenways.be>
|
|
1872
1993
|
* @since 0.2.0
|
|
1873
|
-
* @version 1.
|
|
1994
|
+
* @version 1.3.0
|
|
1874
1995
|
*
|
|
1875
1996
|
* @param {String} path The path on the server to send to the browser
|
|
1876
1997
|
* @param {Object} options Options, including headers
|
|
1877
1998
|
*/
|
|
1878
|
-
Conduit.
|
|
1999
|
+
Conduit.setTypedMethod([Types.String, Types.Object.optional()], function serveFile(path, options = {}) {
|
|
1879
2000
|
|
|
1880
|
-
|
|
1881
|
-
tasks = [],
|
|
1882
|
-
stats,
|
|
1883
|
-
isStream;
|
|
1884
|
-
|
|
1885
|
-
// Create an options object if it doesn't exist yet
|
|
1886
|
-
if (options == null) {
|
|
1887
|
-
options = {};
|
|
1888
|
-
}
|
|
2001
|
+
let file = FILECACHE.get(path);
|
|
1889
2002
|
|
|
1890
|
-
|
|
1891
|
-
|
|
1892
|
-
options.onError = function onError(err) {
|
|
1893
|
-
that.notFound(err);
|
|
1894
|
-
};
|
|
2003
|
+
if (!file) {
|
|
2004
|
+
file = new Classes.Alchemy.Inode.File(path);
|
|
1895
2005
|
}
|
|
1896
2006
|
|
|
1897
|
-
|
|
1898
|
-
|
|
1899
|
-
|
|
1900
|
-
if (Buffer.isBuffer(path)) {
|
|
1901
|
-
path = bufferToStream(path);
|
|
1902
|
-
}
|
|
2007
|
+
return this.serveFile(file, options);
|
|
2008
|
+
});
|
|
1903
2009
|
|
|
1904
|
-
|
|
1905
|
-
|
|
1906
|
-
|
|
1907
|
-
|
|
1908
|
-
|
|
1909
|
-
|
|
1910
|
-
|
|
1911
|
-
|
|
1912
|
-
|
|
1913
|
-
|
|
2010
|
+
/**
|
|
2011
|
+
* Send a file to the browser
|
|
2012
|
+
*
|
|
2013
|
+
* @author Jelle De Loecker <jelle@elevenways.be>
|
|
2014
|
+
* @since 0.2.0
|
|
2015
|
+
* @version 1.3.0
|
|
2016
|
+
*
|
|
2017
|
+
* @param {Alchemy.Inode.File} file The file to serve
|
|
2018
|
+
* @param {Object} options Options, including headers
|
|
2019
|
+
*/
|
|
2020
|
+
Conduit.setTypedMethod([Types.Alchemy.Inode.File, Types.Object.optional()], async function serveFile(file, options = {}) {
|
|
1914
2021
|
|
|
1915
|
-
|
|
1916
|
-
|
|
1917
|
-
|
|
1918
|
-
|
|
1919
|
-
|
|
2022
|
+
if (file.path && !FILECACHE.has(file.path)) {
|
|
2023
|
+
FILECACHE.set(file.path, file);
|
|
2024
|
+
}
|
|
2025
|
+
|
|
2026
|
+
if (alchemy.settings.cache && (!options.cache_time && options.cache_time !== false)) {
|
|
2027
|
+
let stats = await file.getStats();
|
|
2028
|
+
options.cache_time = stats.mtime;
|
|
1920
2029
|
}
|
|
1921
2030
|
|
|
1922
|
-
|
|
1923
|
-
|
|
1924
|
-
|
|
1925
|
-
if (!stats.path) {
|
|
1926
|
-
return options.onError(new Error('No file to serve'));
|
|
1927
|
-
}
|
|
1928
|
-
|
|
1929
|
-
// Make sure the stats object is in the cache
|
|
1930
|
-
if (fileCache[stats.path] == null) {
|
|
1931
|
-
fileCache[stats.path] = stats;
|
|
1932
|
-
}
|
|
1933
|
-
|
|
1934
|
-
// Get file stats if it isn't available yet
|
|
1935
|
-
if (stats.mtime == null) {
|
|
1936
|
-
tasks.push(function getFileStats(next) {
|
|
1937
|
-
|
|
1938
|
-
fs.stat(stats.path, function gotStats(err, fileStats) {
|
|
1939
|
-
|
|
1940
|
-
if (err) {
|
|
1941
|
-
stats.err = err;
|
|
1942
|
-
stats.mtime = new Date();
|
|
1943
|
-
} else {
|
|
1944
|
-
Object.assign(stats, fileStats);
|
|
1945
|
-
}
|
|
1946
|
-
|
|
1947
|
-
next();
|
|
1948
|
-
});
|
|
1949
|
-
});
|
|
1950
|
-
}
|
|
1951
|
-
|
|
1952
|
-
// Get the mimetype if it isn't available yet
|
|
1953
|
-
if (!options.mimetype && stats.mimetype == null) {
|
|
1954
|
-
tasks.push(function getMimetype(next) {
|
|
1955
|
-
|
|
1956
|
-
// Don't use libmime if it isn't loaded,
|
|
1957
|
-
// that could be the case on NW.js
|
|
1958
|
-
if (!libmime) {
|
|
1959
|
-
return next();
|
|
1960
|
-
}
|
|
1961
|
-
|
|
1962
|
-
// Lookup the mimetype by the extension alone
|
|
1963
|
-
stats.mimetype = libmime.getType(stats.path);
|
|
1964
|
-
|
|
1965
|
-
// Return the result if a valid mimetype was found
|
|
1966
|
-
if (stats.mimetype !== 'application/octet-stream') {
|
|
1967
|
-
return next();
|
|
1968
|
-
}
|
|
2031
|
+
if (!options.mimetype) {
|
|
2032
|
+
options.mimetype = await file.getMimetype();
|
|
2033
|
+
}
|
|
1969
2034
|
|
|
1970
|
-
|
|
1971
|
-
|
|
1972
|
-
|
|
1973
|
-
|
|
2035
|
+
if (!options.filename) {
|
|
2036
|
+
options.filename = file.name;
|
|
2037
|
+
}
|
|
2038
|
+
|
|
2039
|
+
return this.serveFile(file.createReadStream(), options);
|
|
2040
|
+
});
|
|
1974
2041
|
|
|
1975
|
-
|
|
1976
|
-
|
|
1977
|
-
|
|
1978
|
-
|
|
2042
|
+
/**
|
|
2043
|
+
* Send a buffer to the client
|
|
2044
|
+
*
|
|
2045
|
+
* @author Jelle De Loecker <jelle@elevenways.be>
|
|
2046
|
+
* @since 1.3.0
|
|
2047
|
+
* @version 1.3.0
|
|
2048
|
+
*
|
|
2049
|
+
* @param {Stream} buffer The buffer to send
|
|
2050
|
+
* @param {Object} options Options, including headers
|
|
2051
|
+
*/
|
|
2052
|
+
Conduit.setTypedMethod([Buffer, Types.Object.optional()], function serveFile(buffer, options = {}) {
|
|
2053
|
+
return this.serveFile(bufferToStream(buffer), options);
|
|
2054
|
+
});
|
|
1979
2055
|
|
|
1980
|
-
|
|
1981
|
-
|
|
1982
|
-
|
|
1983
|
-
|
|
2056
|
+
/**
|
|
2057
|
+
* Send a stream to the client
|
|
2058
|
+
*
|
|
2059
|
+
* @author Jelle De Loecker <jelle@elevenways.be>
|
|
2060
|
+
* @since 1.3.0
|
|
2061
|
+
* @version 1.3.0
|
|
2062
|
+
*
|
|
2063
|
+
* @param {Stream} stream The stream to send
|
|
2064
|
+
* @param {Object} options Options, including headers
|
|
2065
|
+
*/
|
|
2066
|
+
Conduit.setTypedMethod([Types.Stream, Types.Object.optional()], function serveFile(stream, options = {}) {
|
|
1984
2067
|
|
|
1985
|
-
|
|
1986
|
-
if (!getMagic()) {
|
|
1987
|
-
return next();
|
|
1988
|
-
}
|
|
2068
|
+
let is_text = false;
|
|
1989
2069
|
|
|
1990
|
-
|
|
1991
|
-
|
|
2070
|
+
if (options.mimetype && RX_TEXT.test(options.mimetype)) {
|
|
2071
|
+
options.mimetype += '; charset=utf-8';
|
|
2072
|
+
is_text = true;
|
|
2073
|
+
}
|
|
1992
2074
|
|
|
1993
|
-
|
|
1994
|
-
|
|
1995
|
-
|
|
2075
|
+
if (alchemy.settings.cache === false) {
|
|
2076
|
+
options.cache_time = null;
|
|
2077
|
+
}
|
|
1996
2078
|
|
|
1997
|
-
|
|
1998
|
-
|
|
1999
|
-
});
|
|
2000
|
-
}
|
|
2079
|
+
if (options.compress == null) {
|
|
2080
|
+
options.compress = is_text;
|
|
2001
2081
|
}
|
|
2002
2082
|
|
|
2003
|
-
|
|
2083
|
+
if (options.compress && (alchemy.settings.compression === false || !this.accepts('gzip'))) {
|
|
2084
|
+
options.compress = false;
|
|
2085
|
+
}
|
|
2004
2086
|
|
|
2005
|
-
|
|
2006
|
-
|
|
2007
|
-
|
|
2008
|
-
headers,
|
|
2009
|
-
isText,
|
|
2010
|
-
since,
|
|
2011
|
-
key;
|
|
2087
|
+
if (options.cache_time && !alchemy.settings.cache) {
|
|
2088
|
+
options.cache_time = false;
|
|
2089
|
+
}
|
|
2012
2090
|
|
|
2013
|
-
|
|
2014
|
-
|
|
2015
|
-
|
|
2091
|
+
if (!options.onError) {
|
|
2092
|
+
options.onError = err => this.notFound(err);
|
|
2093
|
+
}
|
|
2016
2094
|
|
|
2017
|
-
|
|
2018
|
-
|
|
2019
|
-
}
|
|
2095
|
+
return this._sendStream(stream, options);
|
|
2096
|
+
});
|
|
2020
2097
|
|
|
2021
|
-
|
|
2022
|
-
|
|
2023
|
-
|
|
2098
|
+
/**
|
|
2099
|
+
* Send a stream to the client
|
|
2100
|
+
*
|
|
2101
|
+
* @author Jelle De Loecker <jelle@elevenways.be>
|
|
2102
|
+
* @since 1.3.0
|
|
2103
|
+
* @version 1.3.0
|
|
2104
|
+
*
|
|
2105
|
+
* @param {Stream} stream The stream to send
|
|
2106
|
+
* @param {Object} options Options, including headers
|
|
2107
|
+
*/
|
|
2108
|
+
Conduit.setMethod(function _sendStream(stream, options) {
|
|
2024
2109
|
|
|
2025
|
-
|
|
2026
|
-
|
|
2110
|
+
if (options.cache_time) {
|
|
2111
|
+
let modified_since = this.headers['if-modified-since'];
|
|
2027
2112
|
|
|
2028
|
-
|
|
2029
|
-
since = new Date(
|
|
2113
|
+
if (modified_since != null) {
|
|
2114
|
+
let since = new Date(modified_since);
|
|
2030
2115
|
|
|
2031
2116
|
// If the file's modifytime is smaller or equal to the since time,
|
|
2032
2117
|
// don't serve the contents!
|
|
2033
|
-
if (
|
|
2034
|
-
return
|
|
2035
|
-
}
|
|
2036
|
-
}
|
|
2037
|
-
|
|
2038
|
-
mimetype = stats.mimetype;
|
|
2039
|
-
|
|
2040
|
-
// If we get a general mimetype, and an alternative is provided, use that one
|
|
2041
|
-
if (!mimetype || mimetype === 'application/octet-stream') {
|
|
2042
|
-
if (options.mimetype != null) {
|
|
2043
|
-
mimetype = options.mimetype;
|
|
2118
|
+
if (options.cache_time <= since) {
|
|
2119
|
+
return this.notModified();
|
|
2044
2120
|
}
|
|
2045
2121
|
}
|
|
2046
2122
|
|
|
2047
|
-
|
|
2048
|
-
|
|
2049
|
-
//
|
|
2050
|
-
|
|
2051
|
-
|
|
2052
|
-
|
|
2053
|
-
|
|
2054
|
-
|
|
2055
|
-
|
|
2056
|
-
// Setting the disposition makes the browser download the file
|
|
2057
|
-
// This is on by default, but can be disabled
|
|
2058
|
-
if (options.disposition == 'inline') {
|
|
2059
|
-
disposition = 'inline';
|
|
2060
|
-
|
|
2061
|
-
if (options.filename) {
|
|
2062
|
-
disposition += '; filename=' + JSON.stringify(options.filename)
|
|
2063
|
-
}
|
|
2064
|
-
|
|
2065
|
-
that.setHeader('content-disposition', disposition);
|
|
2066
|
-
} else if (options.disposition !== false) {
|
|
2067
|
-
if (options.filename) {
|
|
2068
|
-
disposition = 'attachment; filename=' + JSON.stringify(options.filename);
|
|
2069
|
-
} else {
|
|
2070
|
-
disposition = 'attachment';
|
|
2071
|
-
}
|
|
2123
|
+
// Allow the browser to cache this for 60 minutes,
|
|
2124
|
+
// after which it has to revalidate the content
|
|
2125
|
+
// by seeing if it has been modified
|
|
2126
|
+
this.setHeader('cache-control', 'public, max-age=3600, must-revalidate');
|
|
2127
|
+
this.setHeader('last-modified', options.cache_time.toGMTString());
|
|
2128
|
+
} else {
|
|
2129
|
+
this.setHeader('cache-control', 'no-cache');
|
|
2130
|
+
}
|
|
2072
2131
|
|
|
2073
|
-
|
|
2074
|
-
|
|
2132
|
+
let disposition,
|
|
2133
|
+
key;
|
|
2075
2134
|
|
|
2076
|
-
|
|
2077
|
-
|
|
2078
|
-
|
|
2079
|
-
// by seeing if it has been modified
|
|
2080
|
-
that.setHeader('cache-control', 'public, max-age=3600, must-revalidate');
|
|
2081
|
-
that.setHeader('last-modified', stats.mtime.toGMTString());
|
|
2082
|
-
} else if (!alchemy.settings.cache) {
|
|
2083
|
-
that.setHeader('cache-control', 'no-cache');
|
|
2084
|
-
}
|
|
2135
|
+
if (options.mimetype) {
|
|
2136
|
+
this.setHeader('content-type', options.mimetype);
|
|
2137
|
+
}
|
|
2085
2138
|
|
|
2086
|
-
|
|
2087
|
-
|
|
2088
|
-
|
|
2139
|
+
// Setting the disposition makes the browser download the file
|
|
2140
|
+
// This is on by default, but can be disabled
|
|
2141
|
+
if (options.disposition == 'inline') {
|
|
2142
|
+
disposition = 'inline';
|
|
2089
2143
|
|
|
2090
|
-
|
|
2091
|
-
|
|
2092
|
-
return that.end();
|
|
2144
|
+
if (options.filename) {
|
|
2145
|
+
disposition += '; filename=' + JSON.stringify(options.filename)
|
|
2093
2146
|
}
|
|
2094
2147
|
|
|
2095
|
-
|
|
2096
|
-
|
|
2148
|
+
this.setHeader('content-disposition', disposition);
|
|
2149
|
+
} else if (options.disposition !== false) {
|
|
2150
|
+
if (options.filename) {
|
|
2151
|
+
disposition = 'attachment; filename=' + JSON.stringify(options.filename);
|
|
2097
2152
|
} else {
|
|
2098
|
-
|
|
2099
|
-
|
|
2100
|
-
// Listen for file errors
|
|
2101
|
-
outStream.on('error', options.onError);
|
|
2153
|
+
disposition = 'attachment';
|
|
2102
2154
|
}
|
|
2103
2155
|
|
|
2104
|
-
|
|
2105
|
-
|
|
2156
|
+
this.setHeader('content-disposition', disposition);
|
|
2157
|
+
}
|
|
2158
|
+
|
|
2159
|
+
// Set all the headers
|
|
2160
|
+
for (key in options.headers) {
|
|
2161
|
+
this.setHeader(key, options.headers[key]);
|
|
2162
|
+
}
|
|
2106
2163
|
|
|
2107
|
-
|
|
2108
|
-
|
|
2109
|
-
|
|
2164
|
+
// Don't send anything if it's a HEAD request
|
|
2165
|
+
if (this.method == 'head') {
|
|
2166
|
+
return this.end();
|
|
2167
|
+
}
|
|
2110
2168
|
|
|
2111
|
-
|
|
2112
|
-
|
|
2113
|
-
}
|
|
2169
|
+
const response = this.response;
|
|
2170
|
+
let out_stream = stream;
|
|
2114
2171
|
|
|
2115
|
-
|
|
2116
|
-
|
|
2117
|
-
|
|
2118
|
-
|
|
2119
|
-
that.response.on('error', cleanup);
|
|
2120
|
-
that.response.on('close', cleanup);
|
|
2121
|
-
}
|
|
2172
|
+
if (options.compress) {
|
|
2173
|
+
// Set the gzip header
|
|
2174
|
+
this.setHeader('content-encoding', 'gzip');
|
|
2175
|
+
this.setHeader('vary', 'accept-encoding');
|
|
2122
2176
|
|
|
2123
|
-
|
|
2177
|
+
// Create the gzip stream
|
|
2178
|
+
out_stream = out_stream.pipe(zlib.createGzip());
|
|
2179
|
+
}
|
|
2124
2180
|
|
|
2181
|
+
if (options.cleanup_stream) {
|
|
2182
|
+
const cleanup = function cleanupOriginalStream() {
|
|
2125
2183
|
// Remove all pipes
|
|
2126
|
-
|
|
2184
|
+
stream.unpipe();
|
|
2127
2185
|
|
|
2128
|
-
if (
|
|
2129
|
-
|
|
2130
|
-
} else if (
|
|
2131
|
-
|
|
2186
|
+
if (stream.destroy) {
|
|
2187
|
+
stream.destroy();
|
|
2188
|
+
} else if (stream.end) {
|
|
2189
|
+
stream.end();
|
|
2132
2190
|
}
|
|
2133
|
-
}
|
|
2191
|
+
};
|
|
2134
2192
|
|
|
2135
|
-
|
|
2136
|
-
|
|
2137
|
-
|
|
2138
|
-
|
|
2193
|
+
response.on('end', cleanup);
|
|
2194
|
+
response.on('finish', cleanup);
|
|
2195
|
+
response.on('error', cleanup);
|
|
2196
|
+
response.on('close', cleanup);
|
|
2197
|
+
}
|
|
2139
2198
|
|
|
2140
|
-
|
|
2141
|
-
|
|
2142
|
-
|
|
2199
|
+
// Set the response headers
|
|
2200
|
+
for (key in this.response_headers) {
|
|
2201
|
+
response.setHeader(key, this.response_headers[key]);
|
|
2202
|
+
}
|
|
2203
|
+
|
|
2204
|
+
if (this.new_cookie_header.length) {
|
|
2205
|
+
response.setHeader('set-cookie', this.new_cookie_header);
|
|
2206
|
+
}
|
|
2143
2207
|
|
|
2144
|
-
|
|
2208
|
+
// If we got this far, the file has been found!
|
|
2209
|
+
response.statusCode = 200;
|
|
2145
2210
|
|
|
2146
|
-
|
|
2147
|
-
|
|
2148
|
-
});
|
|
2211
|
+
// Actually stream the contents to the client
|
|
2212
|
+
out_stream.pipe(response);
|
|
2149
2213
|
});
|
|
2150
2214
|
|
|
2151
2215
|
/**
|
|
2152
2216
|
* Create a session
|
|
2153
2217
|
*
|
|
2154
|
-
* @author Jelle De Loecker <jelle@
|
|
2218
|
+
* @author Jelle De Loecker <jelle@elevenways.be>
|
|
2155
2219
|
* @since 0.2.0
|
|
2156
|
-
* @version 1.1
|
|
2220
|
+
* @version 1.3.1
|
|
2157
2221
|
*
|
|
2158
2222
|
* @param {Boolean} create Create a session if none exist
|
|
2159
2223
|
*
|
|
@@ -2161,16 +2225,16 @@ Conduit.setMethod(function serveFile(path, options) {
|
|
|
2161
2225
|
*/
|
|
2162
2226
|
Conduit.setMethod(function getSession(allow_create = true) {
|
|
2163
2227
|
|
|
2164
|
-
var cookie_name,
|
|
2165
|
-
fingerprint,
|
|
2166
|
-
session_id,
|
|
2167
|
-
session;
|
|
2168
|
-
|
|
2169
2228
|
// Only do this once per request
|
|
2170
2229
|
if (this.sessionData != null) {
|
|
2171
2230
|
return this.sessionData;
|
|
2172
2231
|
}
|
|
2173
2232
|
|
|
2233
|
+
let cookie_name,
|
|
2234
|
+
fingerprint,
|
|
2235
|
+
session_id,
|
|
2236
|
+
session;
|
|
2237
|
+
|
|
2174
2238
|
// Set the name of the cookie (could change in the future)
|
|
2175
2239
|
cookie_name = alchemy.settings.session_key || 'alchemy_sid';
|
|
2176
2240
|
|
|
@@ -2307,6 +2371,24 @@ Conduit.setMethod(function routeParam(name) {
|
|
|
2307
2371
|
return this.params[name];
|
|
2308
2372
|
});
|
|
2309
2373
|
|
|
2374
|
+
/**
|
|
2375
|
+
* Set route parameters
|
|
2376
|
+
*
|
|
2377
|
+
* @author Jelle De Loecker <jelle@elevenways.be>
|
|
2378
|
+
* @since 1.3.0
|
|
2379
|
+
* @version 1.3.0
|
|
2380
|
+
*/
|
|
2381
|
+
Conduit.setMethod(function setRouteParameters(data) {
|
|
2382
|
+
|
|
2383
|
+
if (!this.params) {
|
|
2384
|
+
this.params = {};
|
|
2385
|
+
}
|
|
2386
|
+
|
|
2387
|
+
if (data) {
|
|
2388
|
+
Object.assign(this.params, data);
|
|
2389
|
+
}
|
|
2390
|
+
});
|
|
2391
|
+
|
|
2310
2392
|
/**
|
|
2311
2393
|
* Get/set a cookie
|
|
2312
2394
|
*
|
|
@@ -2370,7 +2452,7 @@ Conduit.setMethod(function cookie(name, value, options) {
|
|
|
2370
2452
|
*
|
|
2371
2453
|
* @author Jelle De Loecker <jelle@develry.be>
|
|
2372
2454
|
* @since 0.2.0
|
|
2373
|
-
* @version 1.
|
|
2455
|
+
* @version 1.3.0
|
|
2374
2456
|
*
|
|
2375
2457
|
* @param {String} name
|
|
2376
2458
|
* @param {Mixed} value
|
|
@@ -2382,7 +2464,7 @@ Conduit.setMethod(function setHeader(name, value) {
|
|
|
2382
2464
|
}
|
|
2383
2465
|
|
|
2384
2466
|
if (this.websocket) {
|
|
2385
|
-
throw new Error("Can't set
|
|
2467
|
+
throw new Error("Can't set header `" + name + "` on a websocket connection");
|
|
2386
2468
|
}
|
|
2387
2469
|
|
|
2388
2470
|
this.response_headers[name] = value;
|
|
@@ -2511,6 +2593,72 @@ Conduit.setMethod(function supports(feature) {
|
|
|
2511
2593
|
return null;
|
|
2512
2594
|
});
|
|
2513
2595
|
|
|
2596
|
+
/**
|
|
2597
|
+
* Should this request be delayed because the server is too busy?
|
|
2598
|
+
* This performs an early check, the controller might also check this later.
|
|
2599
|
+
* This is mainly so we can prevent Server-Side-Renders from happening
|
|
2600
|
+
* when the system is already overloaded.
|
|
2601
|
+
*
|
|
2602
|
+
* @author Jelle De Loecker <jelle@elevenways.be>
|
|
2603
|
+
* @since 1.3.1
|
|
2604
|
+
* @version 1.3.1
|
|
2605
|
+
*
|
|
2606
|
+
* @return {Boolean}
|
|
2607
|
+
*/
|
|
2608
|
+
Conduit.setMethod(function shouldBePostponed() {
|
|
2609
|
+
|
|
2610
|
+
if (!alchemy.settings.postpone_requests_on_overload) {
|
|
2611
|
+
return false;
|
|
2612
|
+
}
|
|
2613
|
+
|
|
2614
|
+
const route = this.route;
|
|
2615
|
+
|
|
2616
|
+
// If no route is found, it can be allowed.
|
|
2617
|
+
// Most of the time this means it's a middleware-request
|
|
2618
|
+
// (like stylesheets, images, scripts, ...)
|
|
2619
|
+
// But also 404s (This can be delayed later by the controller)
|
|
2620
|
+
if (route == null) {
|
|
2621
|
+
return false;
|
|
2622
|
+
}
|
|
2623
|
+
|
|
2624
|
+
// Some routes can't even be postponed
|
|
2625
|
+
if (!route.can_be_postponed) {
|
|
2626
|
+
return false;
|
|
2627
|
+
}
|
|
2628
|
+
|
|
2629
|
+
// If alchemy isn't even too busy, it's obviously allowed
|
|
2630
|
+
if (!alchemy.isTooBusyForRequests()) {
|
|
2631
|
+
return false;
|
|
2632
|
+
}
|
|
2633
|
+
|
|
2634
|
+
// Some users have the permission to skip postponements
|
|
2635
|
+
if (this.hasPermission('alchemy.queue.skip')) {
|
|
2636
|
+
return false;
|
|
2637
|
+
}
|
|
2638
|
+
|
|
2639
|
+
let session = this.getSession(false);
|
|
2640
|
+
|
|
2641
|
+
if (session) {
|
|
2642
|
+
|
|
2643
|
+
// Do not postpone clients that have already been in the queue
|
|
2644
|
+
if (session.hasAlreadyQueued()) {
|
|
2645
|
+
return false;
|
|
2646
|
+
}
|
|
2647
|
+
|
|
2648
|
+
// Do not postpone clients that have already rendered something
|
|
2649
|
+
if (session.render_counter > 0 && session.is_active) {
|
|
2650
|
+
return false;
|
|
2651
|
+
}
|
|
2652
|
+
}
|
|
2653
|
+
|
|
2654
|
+
// AJAX requests are allowed a bit more
|
|
2655
|
+
if (this.ajax) {
|
|
2656
|
+
return alchemy.isTooBusyForAjax();
|
|
2657
|
+
}
|
|
2658
|
+
|
|
2659
|
+
return true;
|
|
2660
|
+
});
|
|
2661
|
+
|
|
2514
2662
|
/**
|
|
2515
2663
|
* Broadcast data to every connected user
|
|
2516
2664
|
*
|