@jsenv/server 17.7.0 → 17.7.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.
|
@@ -853,6 +853,18 @@ main {
|
|
|
853
853
|
font-weight: 700;
|
|
854
854
|
}
|
|
855
855
|
|
|
856
|
+
.route_also_method {
|
|
857
|
+
color: #fff;
|
|
858
|
+
text-shadow: 0 1px #0000001a;
|
|
859
|
+
background: var(--method-color);
|
|
860
|
+
border-radius: 3px;
|
|
861
|
+
margin-left: 6px;
|
|
862
|
+
padding: 3px 6px;
|
|
863
|
+
font-family: sans-serif;
|
|
864
|
+
font-size: 11px;
|
|
865
|
+
font-weight: 700;
|
|
866
|
+
}
|
|
867
|
+
|
|
856
868
|
.route_resource {
|
|
857
869
|
word-break: break-word;
|
|
858
870
|
margin-top: 0;
|
|
@@ -897,7 +909,7 @@ main {
|
|
|
897
909
|
opacity: .85;
|
|
898
910
|
}
|
|
899
911
|
|
|
900
|
-
.route_details[data-forbidden] .route_method {
|
|
912
|
+
.route_details[data-forbidden] .route_method, .route_details[data-forbidden] .route_also_method {
|
|
901
913
|
opacity: .7;
|
|
902
914
|
filter: grayscale(60%);
|
|
903
915
|
}
|
|
@@ -1835,6 +1847,7 @@ const routesLoadingSignal = M$1(false);
|
|
|
1835
1847
|
const RouteDetails = ({
|
|
1836
1848
|
meta,
|
|
1837
1849
|
method,
|
|
1850
|
+
methods,
|
|
1838
1851
|
resource,
|
|
1839
1852
|
description,
|
|
1840
1853
|
availableMediaTypes,
|
|
@@ -1858,6 +1871,10 @@ const routesLoadingSignal = M$1(false);
|
|
|
1858
1871
|
if (selectedPackageName && packageName !== selectedPackageName) {
|
|
1859
1872
|
return null;
|
|
1860
1873
|
}
|
|
1874
|
+
// what the router answers beyond the declared method (a GET route
|
|
1875
|
+
// also answers HEAD); a "*" route answers everything, its chip says so
|
|
1876
|
+
const alsoMethods =
|
|
1877
|
+
method === "*" ? [] : methods.filter((m) => m !== method);
|
|
1861
1878
|
|
|
1862
1879
|
return p$3`<details
|
|
1863
1880
|
class="route_details"
|
|
@@ -1867,6 +1884,14 @@ const routesLoadingSignal = M$1(false);
|
|
|
1867
1884
|
>
|
|
1868
1885
|
<summary class="route_summary_container">
|
|
1869
1886
|
<span class="route_method">${method}</span>
|
|
1887
|
+
${alsoMethods.map(
|
|
1888
|
+
(alsoMethod) => p$3`<span
|
|
1889
|
+
class="route_also_method"
|
|
1890
|
+
style="--method-color: var(--method-${alsoMethod.toLowerCase()}-color)"
|
|
1891
|
+
title="Also answers ${alsoMethod}"
|
|
1892
|
+
>+${alsoMethod}</span
|
|
1893
|
+
>`,
|
|
1894
|
+
)}
|
|
1870
1895
|
<p class="route_resource">${resource}</p>
|
|
1871
1896
|
${method === "GET" && !forbidden
|
|
1872
1897
|
? p$3`<a
|
package/dist/jsenv_server.js
CHANGED
|
@@ -6317,9 +6317,7 @@ const createRouter = (
|
|
|
6317
6317
|
};
|
|
6318
6318
|
};
|
|
6319
6319
|
const forEachMethodAllowed = (route, onMethodAllowed) => {
|
|
6320
|
-
const
|
|
6321
|
-
route.method === "*" ? HTTP_METHODS : [route.method];
|
|
6322
|
-
for (const supportedMethod of supportedMethods) {
|
|
6320
|
+
for (const supportedMethod of route.methods) {
|
|
6323
6321
|
onMethodAllowed(supportedMethod);
|
|
6324
6322
|
}
|
|
6325
6323
|
};
|
|
@@ -6555,7 +6553,9 @@ It should be should be one of route.${routePropertyName}: ${availableValues.join
|
|
|
6555
6553
|
// matching every resource ("GET *", "GET /*") names no resource, so
|
|
6556
6554
|
// it cannot make that assertion and an unknown address stays a 404
|
|
6557
6555
|
if (!route.isFallback && !route.matchesEveryResource) {
|
|
6558
|
-
|
|
6556
|
+
for (const routeMethod of route.methods) {
|
|
6557
|
+
wouldHaveMatched.methodSet.add(routeMethod);
|
|
6558
|
+
}
|
|
6559
6559
|
}
|
|
6560
6560
|
continue;
|
|
6561
6561
|
}
|
|
@@ -6876,6 +6876,14 @@ const createRoute = ({
|
|
|
6876
6876
|
) {
|
|
6877
6877
|
availableMediaTypes.push(extensionWellKnownContentType);
|
|
6878
6878
|
}
|
|
6879
|
+
// HEAD is GET without the body (RFC 9110 §9.3.2): a GET route answers it and
|
|
6880
|
+
// the server drops the body when sending
|
|
6881
|
+
const methods =
|
|
6882
|
+
method === "*"
|
|
6883
|
+
? HTTP_METHODS
|
|
6884
|
+
: method === "GET"
|
|
6885
|
+
? ["GET", "HEAD"]
|
|
6886
|
+
: [method];
|
|
6879
6887
|
const resourcePattern = createResourcePattern(resource);
|
|
6880
6888
|
const headersPattern = headers ? createHeadersPattern(headers) : null;
|
|
6881
6889
|
|
|
@@ -6895,8 +6903,11 @@ const createRoute = ({
|
|
|
6895
6903
|
availableVersions,
|
|
6896
6904
|
availableEncodings,
|
|
6897
6905
|
acceptedMediaTypes,
|
|
6906
|
+
methods,
|
|
6898
6907
|
matchMethod:
|
|
6899
|
-
method === "*"
|
|
6908
|
+
method === "*"
|
|
6909
|
+
? () => true
|
|
6910
|
+
: (requestMethod) => methods.includes(requestMethod),
|
|
6900
6911
|
matchResource:
|
|
6901
6912
|
resource === "*"
|
|
6902
6913
|
? () => true
|
|
@@ -6948,6 +6959,7 @@ const createRoute = ({
|
|
|
6948
6959
|
}
|
|
6949
6960
|
return {
|
|
6950
6961
|
method,
|
|
6962
|
+
methods,
|
|
6951
6963
|
resource,
|
|
6952
6964
|
description,
|
|
6953
6965
|
availableMediaTypes,
|
|
@@ -31,6 +31,8 @@ await startServer({
|
|
|
31
31
|
| `GET /*` | anything, with the path in `request.params[0]` |
|
|
32
32
|
| `GET *` | anything: a catch-all, put it last |
|
|
33
33
|
|
|
34
|
+
A `GET` route also answers `HEAD`: the response is built the same way and sent without its body. When building that body is costly, declare a `HEAD` route for the same resource before the `GET` one: it answers `HEAD` in its place.
|
|
35
|
+
|
|
34
36
|
Only `*` widens a pattern. Routes are tried in order, so a route covering others (`GET /docs/*`, `GET *`) goes after them. `fetch` can be async; returning `null` or `undefined` declines the request and the next route is tried.
|
|
35
37
|
|
|
36
38
|
When no route answers, the router builds the response from what almost matched:
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@jsenv/server",
|
|
3
|
-
"version": "17.7.
|
|
3
|
+
"version": "17.7.2",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "A modern Node.js HTTP server with declarative routing, content negotiation, and WebSocket support",
|
|
6
6
|
"repository": {
|
|
@@ -37,7 +37,7 @@
|
|
|
37
37
|
"prepublishOnly": "npm run build"
|
|
38
38
|
},
|
|
39
39
|
"dependencies": {
|
|
40
|
-
"launch-editor": "2.14.
|
|
40
|
+
"launch-editor": "2.14.2"
|
|
41
41
|
},
|
|
42
42
|
"devDependencies": {
|
|
43
43
|
"@jsenv/abort": "../../tooling/abort",
|
|
@@ -443,6 +443,17 @@
|
|
|
443
443
|
background: var(--method-color);
|
|
444
444
|
border-radius: 3px;
|
|
445
445
|
}
|
|
446
|
+
.route_also_method {
|
|
447
|
+
margin-left: 6px;
|
|
448
|
+
padding: 3px 6px;
|
|
449
|
+
color: #fff;
|
|
450
|
+
font-weight: 700;
|
|
451
|
+
font-size: 11px;
|
|
452
|
+
font-family: sans-serif;
|
|
453
|
+
text-shadow: 0 1px 0 rgba(0, 0, 0, 0.1);
|
|
454
|
+
background: var(--method-color);
|
|
455
|
+
border-radius: 3px;
|
|
456
|
+
}
|
|
446
457
|
.route_resource {
|
|
447
458
|
margin-top: 0;
|
|
448
459
|
margin-bottom: 0;
|
|
@@ -486,7 +497,8 @@
|
|
|
486
497
|
border-color: var(--forbidden-border-color);
|
|
487
498
|
opacity: 0.85;
|
|
488
499
|
}
|
|
489
|
-
.route_details[data-forbidden] .route_method
|
|
500
|
+
.route_details[data-forbidden] .route_method,
|
|
501
|
+
.route_details[data-forbidden] .route_also_method {
|
|
490
502
|
opacity: 0.7;
|
|
491
503
|
filter: grayscale(60%);
|
|
492
504
|
}
|
|
@@ -1428,6 +1440,7 @@
|
|
|
1428
1440
|
const RouteDetails = ({
|
|
1429
1441
|
meta,
|
|
1430
1442
|
method,
|
|
1443
|
+
methods,
|
|
1431
1444
|
resource,
|
|
1432
1445
|
description,
|
|
1433
1446
|
availableMediaTypes,
|
|
@@ -1451,6 +1464,10 @@
|
|
|
1451
1464
|
if (selectedPackageName && packageName !== selectedPackageName) {
|
|
1452
1465
|
return null;
|
|
1453
1466
|
}
|
|
1467
|
+
// what the router answers beyond the declared method (a GET route
|
|
1468
|
+
// also answers HEAD); a "*" route answers everything, its chip says so
|
|
1469
|
+
const alsoMethods =
|
|
1470
|
+
method === "*" ? [] : methods.filter((m) => m !== method);
|
|
1454
1471
|
|
|
1455
1472
|
return html`<details
|
|
1456
1473
|
class="route_details"
|
|
@@ -1460,6 +1477,14 @@
|
|
|
1460
1477
|
>
|
|
1461
1478
|
<summary class="route_summary_container">
|
|
1462
1479
|
<span class="route_method">${method}</span>
|
|
1480
|
+
${alsoMethods.map(
|
|
1481
|
+
(alsoMethod) => html`<span
|
|
1482
|
+
class="route_also_method"
|
|
1483
|
+
style="--method-color: var(--method-${alsoMethod.toLowerCase()}-color)"
|
|
1484
|
+
title="Also answers ${alsoMethod}"
|
|
1485
|
+
>+${alsoMethod}</span
|
|
1486
|
+
>`,
|
|
1487
|
+
)}
|
|
1463
1488
|
<p class="route_resource">${resource}</p>
|
|
1464
1489
|
${method === "GET" && !forbidden
|
|
1465
1490
|
? html`<a
|
package/src/router/router.js
CHANGED
|
@@ -121,9 +121,7 @@ export const createRouter = (
|
|
|
121
121
|
};
|
|
122
122
|
};
|
|
123
123
|
const forEachMethodAllowed = (route, onMethodAllowed) => {
|
|
124
|
-
const
|
|
125
|
-
route.method === "*" ? HTTP_METHODS : [route.method];
|
|
126
|
-
for (const supportedMethod of supportedMethods) {
|
|
124
|
+
for (const supportedMethod of route.methods) {
|
|
127
125
|
onMethodAllowed(supportedMethod);
|
|
128
126
|
}
|
|
129
127
|
};
|
|
@@ -359,7 +357,9 @@ It should be should be one of route.${routePropertyName}: ${availableValues.join
|
|
|
359
357
|
// matching every resource ("GET *", "GET /*") names no resource, so
|
|
360
358
|
// it cannot make that assertion and an unknown address stays a 404
|
|
361
359
|
if (!route.isFallback && !route.matchesEveryResource) {
|
|
362
|
-
|
|
360
|
+
for (const routeMethod of route.methods) {
|
|
361
|
+
wouldHaveMatched.methodSet.add(routeMethod);
|
|
362
|
+
}
|
|
363
363
|
}
|
|
364
364
|
continue;
|
|
365
365
|
}
|
|
@@ -683,6 +683,14 @@ const createRoute = ({
|
|
|
683
683
|
) {
|
|
684
684
|
availableMediaTypes.push(extensionWellKnownContentType);
|
|
685
685
|
}
|
|
686
|
+
// HEAD is GET without the body (RFC 9110 §9.3.2): a GET route answers it and
|
|
687
|
+
// the server drops the body when sending
|
|
688
|
+
const methods =
|
|
689
|
+
method === "*"
|
|
690
|
+
? HTTP_METHODS
|
|
691
|
+
: method === "GET"
|
|
692
|
+
? ["GET", "HEAD"]
|
|
693
|
+
: [method];
|
|
686
694
|
const resourcePattern = createResourcePattern(resource);
|
|
687
695
|
const headersPattern = headers ? createHeadersPattern(headers) : null;
|
|
688
696
|
|
|
@@ -702,8 +710,11 @@ const createRoute = ({
|
|
|
702
710
|
availableVersions,
|
|
703
711
|
availableEncodings,
|
|
704
712
|
acceptedMediaTypes,
|
|
713
|
+
methods,
|
|
705
714
|
matchMethod:
|
|
706
|
-
method === "*"
|
|
715
|
+
method === "*"
|
|
716
|
+
? () => true
|
|
717
|
+
: (requestMethod) => methods.includes(requestMethod),
|
|
707
718
|
matchResource:
|
|
708
719
|
resource === "*"
|
|
709
720
|
? () => true
|
|
@@ -755,6 +766,7 @@ const createRoute = ({
|
|
|
755
766
|
}
|
|
756
767
|
return {
|
|
757
768
|
method,
|
|
769
|
+
methods,
|
|
758
770
|
resource,
|
|
759
771
|
description,
|
|
760
772
|
availableMediaTypes,
|