@fastly/expressly 1.0.0--canary.4.2464710183.0
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/.github/ISSUE_TEMPLATE/bug_report.md +16 -0
- package/.github/workflows/release.yml +85 -0
- package/.husky/pre-commit +4 -0
- package/.nvmrc +1 -0
- package/LICENSE +21 -0
- package/README.md +56 -0
- package/SECURITY.MD +9 -0
- package/dist/index.d.ts +4 -0
- package/dist/index.js +5 -0
- package/dist/lib/routing/common.d.ts +11 -0
- package/dist/lib/routing/common.js +36 -0
- package/dist/lib/routing/error-middleware.d.ts +10 -0
- package/dist/lib/routing/error-middleware.js +12 -0
- package/dist/lib/routing/errors.d.ts +9 -0
- package/dist/lib/routing/errors.js +16 -0
- package/dist/lib/routing/request/cookie-map.d.ts +9 -0
- package/dist/lib/routing/request/cookie-map.js +37 -0
- package/dist/lib/routing/request/index.d.ts +27 -0
- package/dist/lib/routing/request/index.js +47 -0
- package/dist/lib/routing/request-handler.d.ts +10 -0
- package/dist/lib/routing/request-handler.js +12 -0
- package/dist/lib/routing/response/index.d.ts +24 -0
- package/dist/lib/routing/response/index.js +101 -0
- package/dist/lib/routing/response/status-codes.d.ts +56 -0
- package/dist/lib/routing/response/status-codes.js +57 -0
- package/dist/lib/routing/response/surrogate-keys.d.ts +9 -0
- package/dist/lib/routing/response/surrogate-keys.js +29 -0
- package/dist/lib/routing/router.d.ts +32 -0
- package/dist/lib/routing/router.js +165 -0
- package/docs/.nvmrc +1 -0
- package/docs/README.md +27 -0
- package/docs/babel.config.js +3 -0
- package/docs/docs/config.md +52 -0
- package/docs/docs/handling-data/_category_.json +4 -0
- package/docs/docs/handling-data/cookies.md +103 -0
- package/docs/docs/handling-data/headers.md +110 -0
- package/docs/docs/handling-data/request.md +82 -0
- package/docs/docs/handling-data/response.md +162 -0
- package/docs/docs/handling-data/search-params.md +45 -0
- package/docs/docs/intro.md +61 -0
- package/docs/docs/middleware/_category_.json +5 -0
- package/docs/docs/middleware/controlling-flow.md +41 -0
- package/docs/docs/middleware/error-middleware.md +41 -0
- package/docs/docs/middleware/what-is-middleware.md +31 -0
- package/docs/docs/origin-data/_category_.json +4 -0
- package/docs/docs/origin-data/working-with-origins.md +49 -0
- package/docs/docs/routing.md +163 -0
- package/docs/docusaurus.config.js +119 -0
- package/docs/package.json +48 -0
- package/docs/sidebars.js +31 -0
- package/docs/src/components/HomepageFeatures.module.css +11 -0
- package/docs/src/components/HomepageFeatures.tsx +62 -0
- package/docs/src/css/custom.css +45 -0
- package/docs/src/pages/index.module.css +35 -0
- package/docs/src/pages/index.tsx +109 -0
- package/docs/static/.nojekyll +0 -0
- package/docs/static/img/favicon.ico +0 -0
- package/docs/static/img/logo-black.png +0 -0
- package/docs/static/img/logo-transparent.png +0 -0
- package/docs/static/img/logo.png +0 -0
- package/docs/static-host/Cargo.lock +435 -0
- package/docs/static-host/Cargo.toml +16 -0
- package/docs/static-host/fastly.toml +9 -0
- package/docs/static-host/rust-toolchain +3 -0
- package/docs/static-host/src/main.rs +115 -0
- package/docs/tsconfig.json +7 -0
- package/docs/yarn.lock +8416 -0
- package/package.json +56 -0
- package/src/index.ts +11 -0
- package/src/lib/routing/common.ts +34 -0
- package/src/lib/routing/error-middleware.ts +23 -0
- package/src/lib/routing/errors.ts +18 -0
- package/src/lib/routing/index.d.ts +18 -0
- package/src/lib/routing/request/cookie-map.ts +42 -0
- package/src/lib/routing/request/index.ts +64 -0
- package/src/lib/routing/request-handler.ts +22 -0
- package/src/lib/routing/response/index.ts +113 -0
- package/src/lib/routing/response/status-codes.ts +57 -0
- package/src/lib/routing/response/surrogate-keys.ts +32 -0
- package/src/lib/routing/router.ts +191 -0
- package/tsconfig.json +20 -0
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
// See: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status
|
|
2
|
+
export const statusText = {
|
|
3
|
+
100: "Continue",
|
|
4
|
+
101: "Switching Protocols",
|
|
5
|
+
103: "Early Hints",
|
|
6
|
+
200: "OK",
|
|
7
|
+
201: "Created",
|
|
8
|
+
202: "Accepted",
|
|
9
|
+
203: "Non-Authoritative Information",
|
|
10
|
+
204: "No Content",
|
|
11
|
+
205: "Reset Content",
|
|
12
|
+
206: "Partial Content",
|
|
13
|
+
300: "Multiple Choices",
|
|
14
|
+
301: "Moved Permanently",
|
|
15
|
+
302: "Found",
|
|
16
|
+
303: "See Other",
|
|
17
|
+
304: "Not Modified",
|
|
18
|
+
307: "Temporary Redirect",
|
|
19
|
+
308: "Permanent Redirect",
|
|
20
|
+
400: "Bad Request",
|
|
21
|
+
401: "Unauthorized",
|
|
22
|
+
402: "Payment Required",
|
|
23
|
+
403: "Forbidden",
|
|
24
|
+
404: "Not Found",
|
|
25
|
+
405: "Method Not Allowed",
|
|
26
|
+
406: "Not Acceptable",
|
|
27
|
+
407: "Proxy Authentication Required",
|
|
28
|
+
408: "Request Timeout",
|
|
29
|
+
409: "Conflict",
|
|
30
|
+
410: "Gone",
|
|
31
|
+
411: "Length Required",
|
|
32
|
+
412: "Precondition Failed",
|
|
33
|
+
413: "Payload Too Large",
|
|
34
|
+
414: "URI Too Long",
|
|
35
|
+
415: "Unsupported Media Type",
|
|
36
|
+
416: "Range Not Satisfiable",
|
|
37
|
+
417: "Expectation Failed",
|
|
38
|
+
418: "I'm a teapot",
|
|
39
|
+
422: "Unprocessable Entity",
|
|
40
|
+
425: "Too Early",
|
|
41
|
+
426: "Upgrade Required",
|
|
42
|
+
428: "Precondition Required",
|
|
43
|
+
429: "Too Many Requests",
|
|
44
|
+
431: "Request Header Fields Too Large",
|
|
45
|
+
451: "Unavailable For Legal Reasons",
|
|
46
|
+
500: "Internal Server Error",
|
|
47
|
+
501: "Not Implemented",
|
|
48
|
+
502: "Bad Gateway",
|
|
49
|
+
503: "Service Unavailable",
|
|
50
|
+
504: "Gateway Timeout",
|
|
51
|
+
505: "HTTP Version Not Supported",
|
|
52
|
+
506: "Variant Also Negotiates",
|
|
53
|
+
507: "Insufficient Storage",
|
|
54
|
+
508: "Loop Detected",
|
|
55
|
+
510: "Not Extended",
|
|
56
|
+
511: "Network Authentication Required"
|
|
57
|
+
};
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
export class SurrogateKeys extends Set {
|
|
2
|
+
constructor(headers) {
|
|
3
|
+
super();
|
|
4
|
+
this.headers = headers;
|
|
5
|
+
}
|
|
6
|
+
clear() {
|
|
7
|
+
this.headers.delete("Surrogate-Key");
|
|
8
|
+
super.clear();
|
|
9
|
+
}
|
|
10
|
+
add(key) {
|
|
11
|
+
super.add(key);
|
|
12
|
+
this.serialize();
|
|
13
|
+
return this;
|
|
14
|
+
}
|
|
15
|
+
delete(key) {
|
|
16
|
+
const deleteResult = super.delete(key);
|
|
17
|
+
this.serialize();
|
|
18
|
+
return deleteResult;
|
|
19
|
+
}
|
|
20
|
+
serialize() {
|
|
21
|
+
if (this.size) {
|
|
22
|
+
const keys = [];
|
|
23
|
+
for (const [key] of this.entries()) {
|
|
24
|
+
keys.push(key);
|
|
25
|
+
}
|
|
26
|
+
this.headers.set("Surrogate-Key", keys.join(" "));
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { EConfig } from ".";
|
|
2
|
+
import { RequestHandler, RequestHandlerCallback } from "./request-handler";
|
|
3
|
+
import { ErrorMiddleware, ErrorMiddlewareCallback } from "./error-middleware";
|
|
4
|
+
export declare class Router {
|
|
5
|
+
requestHandlers: Array<RequestHandler>;
|
|
6
|
+
errorHandlers: Array<ErrorMiddleware>;
|
|
7
|
+
config: EConfig;
|
|
8
|
+
constructor(config?: EConfig);
|
|
9
|
+
listen(): void;
|
|
10
|
+
private handler;
|
|
11
|
+
use(path: string | RequestHandlerCallback | ErrorMiddlewareCallback, callback?: RequestHandlerCallback | ErrorMiddlewareCallback): void;
|
|
12
|
+
route(methods: string[], pattern: string, callback: RequestHandlerCallback): void;
|
|
13
|
+
all(pattern: string, callback: RequestHandlerCallback): void;
|
|
14
|
+
get(pattern: string, callback: RequestHandlerCallback): void;
|
|
15
|
+
post(pattern: string, callback: RequestHandlerCallback): void;
|
|
16
|
+
put(pattern: string, callback: RequestHandlerCallback): void;
|
|
17
|
+
delete(pattern: string, callback: RequestHandlerCallback): void;
|
|
18
|
+
head(pattern: string, callback: RequestHandlerCallback): void;
|
|
19
|
+
options(pattern: string, callback: RequestHandlerCallback): void;
|
|
20
|
+
patch(pattern: string, callback: RequestHandlerCallback): void;
|
|
21
|
+
private runRequestHandlers;
|
|
22
|
+
private runErrorHandlers;
|
|
23
|
+
/**
|
|
24
|
+
* Creates a function used to check if the request method and path match a router configuration.
|
|
25
|
+
* @param methods An array of HTTP method(s) or "*" to match all methods.
|
|
26
|
+
* @param pattern A path string compatible with path-to-regexp (see: https://www.npmjs.com/package/path-to-regexp)
|
|
27
|
+
* @param extractRequestParameters Whether to extract parameters from a request
|
|
28
|
+
* @returns 405 if the method is not allowed, 404 if the path doesn't match, 0 otherwise.
|
|
29
|
+
*/
|
|
30
|
+
private routeMatcher;
|
|
31
|
+
private static serializeResponse;
|
|
32
|
+
}
|
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
import { match } from "path-to-regexp";
|
|
2
|
+
import { RequestHandler } from "./request-handler";
|
|
3
|
+
import { ErrorMiddleware } from "./error-middleware";
|
|
4
|
+
import { ErrorNotFound, ErrorMethodNotAllowed } from "./errors";
|
|
5
|
+
import { ERequest } from "./request";
|
|
6
|
+
import { EResponse } from "./response";
|
|
7
|
+
const pathMatcherCache = new Map();
|
|
8
|
+
const defaultErrorHandler = (auto405) => async (err, req, res) => {
|
|
9
|
+
if (err instanceof ErrorNotFound || (err instanceof ErrorMethodNotAllowed && !auto405)) {
|
|
10
|
+
return res.sendStatus(404);
|
|
11
|
+
}
|
|
12
|
+
else if (err instanceof ErrorMethodNotAllowed) {
|
|
13
|
+
res.headers.set("Allow", err.allow);
|
|
14
|
+
return res.sendStatus(405);
|
|
15
|
+
}
|
|
16
|
+
res.withStatus(500).json({ error: err });
|
|
17
|
+
};
|
|
18
|
+
export class Router {
|
|
19
|
+
constructor(config) {
|
|
20
|
+
this.requestHandlers = [];
|
|
21
|
+
this.errorHandlers = [];
|
|
22
|
+
this.config = {
|
|
23
|
+
parseCookie: true,
|
|
24
|
+
auto405: true,
|
|
25
|
+
extractRequestParameters: true,
|
|
26
|
+
autoContentType: false
|
|
27
|
+
};
|
|
28
|
+
this.config = {
|
|
29
|
+
...this.config,
|
|
30
|
+
...config
|
|
31
|
+
};
|
|
32
|
+
}
|
|
33
|
+
listen() {
|
|
34
|
+
addEventListener("fetch", (event) => event.respondWith(this.handler(event)));
|
|
35
|
+
}
|
|
36
|
+
async handler(event) {
|
|
37
|
+
const req = new ERequest(this.config, event);
|
|
38
|
+
const res = new EResponse(this.config);
|
|
39
|
+
try {
|
|
40
|
+
// Run middleware and request handler stack.
|
|
41
|
+
await this.runRequestHandlers(req, res);
|
|
42
|
+
}
|
|
43
|
+
catch (err) {
|
|
44
|
+
// Add default error handler.
|
|
45
|
+
this.use(defaultErrorHandler(this.config.auto405));
|
|
46
|
+
// Run error handler stack.
|
|
47
|
+
await this.runErrorHandlers(err, req, res);
|
|
48
|
+
}
|
|
49
|
+
return Router.serializeResponse(res);
|
|
50
|
+
}
|
|
51
|
+
// Middleware attach point.
|
|
52
|
+
use(path, callback) {
|
|
53
|
+
const cb = path instanceof Function ? path : callback;
|
|
54
|
+
const matcher = path instanceof Function ? () => 0 : this.routeMatcher(["*"], path);
|
|
55
|
+
if (cb.length === 3) {
|
|
56
|
+
this.errorHandlers.push(new ErrorMiddleware(matcher, cb));
|
|
57
|
+
}
|
|
58
|
+
else {
|
|
59
|
+
this.requestHandlers.push(new RequestHandler(matcher, cb));
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
// Router API.
|
|
63
|
+
route(methods, pattern, callback) {
|
|
64
|
+
this.requestHandlers.push(new RequestHandler(this.routeMatcher(methods.map(m => m.toUpperCase()), pattern), callback));
|
|
65
|
+
}
|
|
66
|
+
all(pattern, callback) {
|
|
67
|
+
this.route(["*"], pattern, callback);
|
|
68
|
+
}
|
|
69
|
+
get(pattern, callback) {
|
|
70
|
+
this.route(["GET"], pattern, callback);
|
|
71
|
+
}
|
|
72
|
+
post(pattern, callback) {
|
|
73
|
+
this.route(["POST"], pattern, callback);
|
|
74
|
+
}
|
|
75
|
+
put(pattern, callback) {
|
|
76
|
+
this.route(["PUT"], pattern, callback);
|
|
77
|
+
}
|
|
78
|
+
delete(pattern, callback) {
|
|
79
|
+
this.route(["DELETE"], pattern, callback);
|
|
80
|
+
}
|
|
81
|
+
head(pattern, callback) {
|
|
82
|
+
this.route(["HEAD"], pattern, callback);
|
|
83
|
+
}
|
|
84
|
+
options(pattern, callback) {
|
|
85
|
+
this.route(["OPTIONS"], pattern, callback);
|
|
86
|
+
}
|
|
87
|
+
patch(pattern, callback) {
|
|
88
|
+
this.route(["PATCH"], pattern, callback);
|
|
89
|
+
}
|
|
90
|
+
// Request handler runner.
|
|
91
|
+
async runRequestHandlers(req, res) {
|
|
92
|
+
let checkResult;
|
|
93
|
+
const allowedMethods = [];
|
|
94
|
+
for (let a of this.requestHandlers) {
|
|
95
|
+
if (res.hasEnded) {
|
|
96
|
+
break;
|
|
97
|
+
}
|
|
98
|
+
checkResult = a.check(req);
|
|
99
|
+
if (checkResult === 0) {
|
|
100
|
+
await a.run(req, res);
|
|
101
|
+
}
|
|
102
|
+
else if (checkResult.length) {
|
|
103
|
+
allowedMethods.push(checkResult);
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
if (allowedMethods.length) {
|
|
107
|
+
throw new ErrorMethodNotAllowed(allowedMethods);
|
|
108
|
+
}
|
|
109
|
+
else if (checkResult === 404) {
|
|
110
|
+
throw new ErrorNotFound();
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
// Error handler runner.
|
|
114
|
+
async runErrorHandlers(err, req, res) {
|
|
115
|
+
for (let eH of this.errorHandlers) {
|
|
116
|
+
if (res.hasEnded) {
|
|
117
|
+
break;
|
|
118
|
+
}
|
|
119
|
+
if (eH.check(req) === 0) {
|
|
120
|
+
await eH.run(err, req, res);
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
/**
|
|
125
|
+
* Creates a function used to check if the request method and path match a router configuration.
|
|
126
|
+
* @param methods An array of HTTP method(s) or "*" to match all methods.
|
|
127
|
+
* @param pattern A path string compatible with path-to-regexp (see: https://www.npmjs.com/package/path-to-regexp)
|
|
128
|
+
* @param extractRequestParameters Whether to extract parameters from a request
|
|
129
|
+
* @returns 405 if the method is not allowed, 404 if the path doesn't match, 0 otherwise.
|
|
130
|
+
*/
|
|
131
|
+
routeMatcher(methods, pattern) {
|
|
132
|
+
return (req) => {
|
|
133
|
+
const methodAllowed = methods.some(m => m === "*" || m === req.method);
|
|
134
|
+
if (pattern === "*" || pattern === "(.*)") {
|
|
135
|
+
return methodAllowed ? 0 : methods;
|
|
136
|
+
}
|
|
137
|
+
else {
|
|
138
|
+
// Cache pattern matcher.
|
|
139
|
+
if (!pathMatcherCache.has(pattern)) {
|
|
140
|
+
pathMatcherCache.set(pattern, match(pattern, { decode: decodeURIComponent }));
|
|
141
|
+
}
|
|
142
|
+
// Match on pathname.
|
|
143
|
+
let { path, params } = pathMatcherCache.get(pattern)(req.url.pathname) || {};
|
|
144
|
+
if (path) {
|
|
145
|
+
if (this.config.extractRequestParameters) {
|
|
146
|
+
req.params = params;
|
|
147
|
+
}
|
|
148
|
+
return methodAllowed ? 0 : methods;
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
// Route not found.
|
|
152
|
+
return 404;
|
|
153
|
+
};
|
|
154
|
+
}
|
|
155
|
+
static serializeResponse(res) {
|
|
156
|
+
// Default to 200 / 204 if no status was set by middleware / route handler.
|
|
157
|
+
if (res.status === 0) {
|
|
158
|
+
res.status = Boolean(res.body) ? 200 : 204;
|
|
159
|
+
}
|
|
160
|
+
return new Response(res.body, {
|
|
161
|
+
headers: res.headers,
|
|
162
|
+
status: res.status,
|
|
163
|
+
});
|
|
164
|
+
}
|
|
165
|
+
}
|
package/docs/.nvmrc
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
16
|
package/docs/README.md
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
# expressly documentation
|
|
2
|
+
|
|
3
|
+
The [**expressly** documentation website](https://expressly.edgecompute.app) is built using [Docusaurus](https://docusaurus.io/).
|
|
4
|
+
|
|
5
|
+
### Installation
|
|
6
|
+
|
|
7
|
+
```
|
|
8
|
+
$ yarn install
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
### Local development
|
|
12
|
+
|
|
13
|
+
```
|
|
14
|
+
$ yarn start
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
This command starts a local development server and opens up a browser window. Most changes are reflected live without having to restart the server.
|
|
18
|
+
|
|
19
|
+
To make changes, edit the Markdown files in [`docs/`](./docs).
|
|
20
|
+
|
|
21
|
+
### Build
|
|
22
|
+
|
|
23
|
+
```
|
|
24
|
+
$ yarn build
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
This command generates static content into the `build` directory.
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
---
|
|
2
|
+
sidebar_position: 7
|
|
3
|
+
---
|
|
4
|
+
|
|
5
|
+
# Configuration
|
|
6
|
+
|
|
7
|
+
**expressly**'s router can be initialized with an optional configuration object:
|
|
8
|
+
|
|
9
|
+
```javascript
|
|
10
|
+
const router = new Router({
|
|
11
|
+
parseCookie: true,
|
|
12
|
+
auto405: true,
|
|
13
|
+
extractRequestParameters: true,
|
|
14
|
+
autoContentType: true,
|
|
15
|
+
});
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
## Options
|
|
19
|
+
|
|
20
|
+
### parseCookie
|
|
21
|
+
|
|
22
|
+
> Default 🟢 `true`
|
|
23
|
+
|
|
24
|
+
When set to `true`, enables parsing of the `Cookie` request header and exposes [`req.cookies`](handling-data/cookies.md#request-cookies).
|
|
25
|
+
|
|
26
|
+
### auto405
|
|
27
|
+
|
|
28
|
+
> Default 🟢 `true`
|
|
29
|
+
|
|
30
|
+
When set to `true`, **expressly** will respond with HTTP `405 Method Not Allowed` and automatically set the [`Allow` header](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Allow) if it cannot match the client request method on a matched path.
|
|
31
|
+
|
|
32
|
+
In the example below, a `POST` request to the `/users` path will result in a HTTP 405 response.
|
|
33
|
+
|
|
34
|
+
```javascript
|
|
35
|
+
const router = new Router({ auto405: true });
|
|
36
|
+
router.get("/users", (req, res) => { ... });
|
|
37
|
+
router.listen();
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
Setting `auto405: false` above will cause **expressly** to respond with HTTP 404.
|
|
41
|
+
|
|
42
|
+
### extractRequestParameters
|
|
43
|
+
|
|
44
|
+
> Default 🟢 `true`
|
|
45
|
+
|
|
46
|
+
When set to `true`, exposes `req.params`, an object containing properties mapped to any [named route "parameters"](./routing#path-parameters).
|
|
47
|
+
|
|
48
|
+
### autoContentType
|
|
49
|
+
|
|
50
|
+
> Default 🔴 `false` 🔥 experimental
|
|
51
|
+
|
|
52
|
+
When set to `true`, [`res.send`](handling-data/response.md#ressend) will try to [infer the `Content-Type` header](https://expressjs.com/en/4x/api.html#res.send) from the data it is passed, if the header is not set.
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
---
|
|
2
|
+
sidebar_position: 4
|
|
3
|
+
---
|
|
4
|
+
|
|
5
|
+
# Cookies
|
|
6
|
+
|
|
7
|
+
## Request cookies
|
|
8
|
+
|
|
9
|
+
You can retrieve the cookies sent by the client by accessing the `req.cookies` object, which implements the [Map](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Map) interface.
|
|
10
|
+
|
|
11
|
+
```javascript
|
|
12
|
+
router.get("/", (req, res) => {
|
|
13
|
+
res.json({
|
|
14
|
+
session: req.cookies.get("session")
|
|
15
|
+
})
|
|
16
|
+
})
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
The `req.cookies.entries()` method returns an iterator for all cookie key/value pairs.
|
|
20
|
+
|
|
21
|
+
```javascript
|
|
22
|
+
// List all cookie names and values.
|
|
23
|
+
for (const [name, value] of req.cookies.entries()) {
|
|
24
|
+
console.log(`${name}=${value}`);
|
|
25
|
+
}
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
If you need only the keys or values, use `req.cookies.keys()` or `req.cookies.values()`, respectively.
|
|
29
|
+
|
|
30
|
+
Use `req.cookies.delete(name)` to delete a cookie:
|
|
31
|
+
|
|
32
|
+
```javascript
|
|
33
|
+
router.get("/", (req, res) => {
|
|
34
|
+
req.cookies.delete("auth");
|
|
35
|
+
const beresp = await fetch(req, { backend: "my-origin" });
|
|
36
|
+
// ...
|
|
37
|
+
})
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
## Response cookies
|
|
41
|
+
|
|
42
|
+
Setting response cookies is done by calling `res.cookie(key, value[, options])`.
|
|
43
|
+
|
|
44
|
+
```javascript
|
|
45
|
+
router.get("/", (req, res) => {
|
|
46
|
+
res.cookie("user", "me");
|
|
47
|
+
res.cookie("auth", "AUTH_TOKEN_HERE", { maxAge: 3600, path: "/" })
|
|
48
|
+
res.send("Cookie set!")
|
|
49
|
+
})
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
Calling `res.clearCookie(key[, options])` will expire a response cookie:
|
|
53
|
+
|
|
54
|
+
```javascript
|
|
55
|
+
router.get("/", (req, res) => {
|
|
56
|
+
res.clearCookie("auth", { path: "/" })
|
|
57
|
+
res.send("Cookie expired!")
|
|
58
|
+
})
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
### Options
|
|
62
|
+
|
|
63
|
+
`res.cookie()` accepts the following properties in the `options` object:
|
|
64
|
+
|
|
65
|
+
#### [domain](https://tools.ietf.org/html/rfc6265#section-5.2.3)
|
|
66
|
+
|
|
67
|
+
Specifies the `string` value for the `Domain` attribute. By default, no domain is set, and most clients will consider the cookie to apply to only the current domain.
|
|
68
|
+
|
|
69
|
+
#### [expires](https://tools.ietf.org/html/rfc6265#section-5.2.1)
|
|
70
|
+
|
|
71
|
+
A `Date` object or `string` that determines the value for the `Expires` attribute. By default, this is not set, and most clients will consider this a "non-persistent cookie".
|
|
72
|
+
|
|
73
|
+
> **Note:** The [cookie storage model specification](https://tools.ietf.org/html/rfc6265#section-5.3) states that if both `expires` and
|
|
74
|
+
`maxAge` are set, then `maxAge` takes precedence.
|
|
75
|
+
|
|
76
|
+
#### [maxAge](https://tools.ietf.org/html/rfc6265#section-5.2.2)
|
|
77
|
+
|
|
78
|
+
A `number` (in seconds) that determines the value for the `Max-Age` attribute. By default, no maximum cookie age is set.
|
|
79
|
+
|
|
80
|
+
#### [httpOnly](https://tools.ietf.org/html/rfc6265#section-5.2.6)
|
|
81
|
+
|
|
82
|
+
A `boolean` value. When truthy, the `HttpOnly` attribute is set, otherwise it is not. By default, the `HttpOnly` attribute is not set.
|
|
83
|
+
|
|
84
|
+
#### [path](https://tools.ietf.org/html/rfc6265#section-5.2.4)
|
|
85
|
+
|
|
86
|
+
Specifies the `string` value for the `Path` attribute.
|
|
87
|
+
|
|
88
|
+
#### [sameSite](https://datatracker.ietf.org/doc/html/draft-ietf-httpbis-rfc6265bis-09#section-5.4.7)
|
|
89
|
+
|
|
90
|
+
Specifies the `boolean` or `string` value for the `SameSite` attribute:
|
|
91
|
+
|
|
92
|
+
- `true` will set the `SameSite` attribute to `Strict` for strict same site enforcement.
|
|
93
|
+
- `false` will not set the `SameSite` attribute.
|
|
94
|
+
- `'lax'` will set the `SameSite` attribute to `Lax` for lax same site enforcement.
|
|
95
|
+
- `'none'` will set the `SameSite` attribute to `None` for an explicit cross-site cookie.
|
|
96
|
+
- `'strict'` will set the `SameSite` attribute to `Strict` for strict same site enforcement.
|
|
97
|
+
|
|
98
|
+
> **Note:** This is an attribute that has not yet been fully standardized, and may change in the future.
|
|
99
|
+
This also means many clients may ignore this attribute until they understand it.
|
|
100
|
+
|
|
101
|
+
#### [secure](https://tools.ietf.org/html/rfc6265#section-5.2.5)
|
|
102
|
+
|
|
103
|
+
Specifies the `boolean` value for the `Secure` attribute. When truthy, the `Secure` attribute is set, otherwise it is not. By default, the `Secure` attribute is not set.
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
---
|
|
2
|
+
sidebar_position: 3
|
|
3
|
+
---
|
|
4
|
+
|
|
5
|
+
# Headers
|
|
6
|
+
|
|
7
|
+
**expressly** simplifies header manipulation on `Request` and `Response` objects.
|
|
8
|
+
|
|
9
|
+
> 🚨 **Unlike Express**, both `req.headers` and `res.headers` implement the standard [Headers](https://developer.mozilla.org/en-US/docs/Web/API/Headers) interface of the Fetch API.
|
|
10
|
+
|
|
11
|
+
## Get header values
|
|
12
|
+
|
|
13
|
+
Use `*.headers.get(name)` to retrieve the values of a HTTP header.
|
|
14
|
+
|
|
15
|
+
> 💡 **Header names are case-insensitive**!
|
|
16
|
+
|
|
17
|
+
```javascript
|
|
18
|
+
router.get("/", (req, res) => {
|
|
19
|
+
res.json({
|
|
20
|
+
userAgent: req.headers.get("user-agent"),
|
|
21
|
+
host: req.headers.get("host"),
|
|
22
|
+
});
|
|
23
|
+
});
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
The `.headers.entries()` method returns an iterator for all header key/value pairs.
|
|
27
|
+
|
|
28
|
+
```javascript
|
|
29
|
+
// List all header names and values.
|
|
30
|
+
for (const [name, value] of req.headers.entries()) {
|
|
31
|
+
console.log(`${name}: ${value}`);
|
|
32
|
+
}
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
If you need only the keys or values, use [`*.headers.keys()`](https://developer.mozilla.org/en-US/docs/Web/API/Headers/keys) or [`*.headers.values()`](https://developer.mozilla.org/en-US/docs/Web/API/Headers/values), respectively.
|
|
36
|
+
|
|
37
|
+
## Check if set
|
|
38
|
+
|
|
39
|
+
If you need to check whether a header is set, use `*.headers.has(name)`:
|
|
40
|
+
|
|
41
|
+
```javascript
|
|
42
|
+
router.use((req, res) => {
|
|
43
|
+
if (!req.headers.has("x-api-key")) {
|
|
44
|
+
res.sendStatus(403);
|
|
45
|
+
}
|
|
46
|
+
});
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
## Set headers
|
|
50
|
+
|
|
51
|
+
Set request or response headers by calling `*.headers.set(key, value)`.
|
|
52
|
+
|
|
53
|
+
```javascript
|
|
54
|
+
router.get("/", (req, res) => {
|
|
55
|
+
res.headers.set("content-type", "text/html");
|
|
56
|
+
res.send("<html><body><h1>This is HTML!</h1></body></html>");
|
|
57
|
+
});
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
## Append headers
|
|
61
|
+
|
|
62
|
+
Append to request or response headers by calling `*.headers.append(key, value)`.
|
|
63
|
+
|
|
64
|
+
```javascript
|
|
65
|
+
router.get("/", (req, res) => {
|
|
66
|
+
res.headers.append("set-cookie", "auth=token");
|
|
67
|
+
res.headers.append("set-cookie", "user=me");
|
|
68
|
+
res.send("Hello world!");
|
|
69
|
+
});
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
## Remove headers
|
|
73
|
+
|
|
74
|
+
Remove request or response headers by calling `*.headers.delete(key)`.
|
|
75
|
+
|
|
76
|
+
```javascript
|
|
77
|
+
router.get("/", (req, res) => {
|
|
78
|
+
req.headers.delete("x-api-key");
|
|
79
|
+
// ...
|
|
80
|
+
});
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
## Aliases
|
|
84
|
+
|
|
85
|
+
### req/res.set
|
|
86
|
+
|
|
87
|
+
Aliased helpers to set HTTP header values. To set multiple fields at once, pass an object as the only parameter.
|
|
88
|
+
|
|
89
|
+
```javascript
|
|
90
|
+
res.set("content-type", "text/plain");
|
|
91
|
+
|
|
92
|
+
req.set({
|
|
93
|
+
"x-api-key": "my-api-key",
|
|
94
|
+
"x-debug": "1",
|
|
95
|
+
"host": "example.com"
|
|
96
|
+
});
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
### req/res.append
|
|
100
|
+
|
|
101
|
+
Aliased helpers to append HTTP header values. To set multiple fields at once, pass an object as the only parameter. To append iteratively to a single header, pass an array of strings as its value.
|
|
102
|
+
|
|
103
|
+
```javascript
|
|
104
|
+
res.append("link", ["<http://localhost/>", "<http://localhost:3000/>"]);
|
|
105
|
+
|
|
106
|
+
req.append({
|
|
107
|
+
"warning": "199 Miscellaneous warning",
|
|
108
|
+
"x-forwarded-for": "example.com"
|
|
109
|
+
});
|
|
110
|
+
```
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
---
|
|
2
|
+
sidebar_position: 1
|
|
3
|
+
title: Request
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# The Request object
|
|
7
|
+
|
|
8
|
+
## Properties
|
|
9
|
+
|
|
10
|
+
### req.url
|
|
11
|
+
The [URL](https://developer.mozilla.org/en-US/docs/Web/API/URL) object corresponding to the request URL.
|
|
12
|
+
|
|
13
|
+
### req.query
|
|
14
|
+
Request [query string parameters](search-params.md).
|
|
15
|
+
|
|
16
|
+
### req.params
|
|
17
|
+
An object containing properties mapped to any [named route "parameters"](../routing#path-parameters), if the [`extractRequestParameters`](../config.md#extractRequestParameters) configuration option is enabled.
|
|
18
|
+
|
|
19
|
+
### req.cookies
|
|
20
|
+
A Map containing [request cookies](cookies.md#request-cookies), if the [`parseCookie`](../config.md#parseCookie) configuration option is enabled.
|
|
21
|
+
|
|
22
|
+
### req.headers
|
|
23
|
+
Request [headers](headers.md).
|
|
24
|
+
|
|
25
|
+
### req.path
|
|
26
|
+
The path part of the request URL.
|
|
27
|
+
|
|
28
|
+
### req.ip
|
|
29
|
+
The remote IP address of the request
|
|
30
|
+
|
|
31
|
+
### req.protocol
|
|
32
|
+
The request protocol string: either `http` or (for TLS requests) `https`.
|
|
33
|
+
|
|
34
|
+
### req.secure
|
|
35
|
+
A boolean value that is `true` if a TLS connection is established.
|
|
36
|
+
|
|
37
|
+
### req.subdomains
|
|
38
|
+
An array of subdomains in the domain name of the request.
|
|
39
|
+
|
|
40
|
+
### req.hostname
|
|
41
|
+
The hostname derived from the `Host` HTTP header.
|
|
42
|
+
|
|
43
|
+
## Working with the request body
|
|
44
|
+
|
|
45
|
+
**expressly** can parse the body of a **req**uest in multiple ways.
|
|
46
|
+
|
|
47
|
+
### As plain text
|
|
48
|
+
|
|
49
|
+
```javascript
|
|
50
|
+
router.post("/submit", async (req, res) => {
|
|
51
|
+
let body = await req.text();
|
|
52
|
+
|
|
53
|
+
res.send(`You posted: "${body}"`)
|
|
54
|
+
})
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
### As JSON
|
|
58
|
+
|
|
59
|
+
```javascript
|
|
60
|
+
router.post("/submit", async (req, res) => {
|
|
61
|
+
// Parse body as JSON
|
|
62
|
+
let body = await req.json();
|
|
63
|
+
|
|
64
|
+
// Check if the body contains the key "item"
|
|
65
|
+
if ("item" in body) {
|
|
66
|
+
res.send(`item: ${body.item}`)
|
|
67
|
+
} else {
|
|
68
|
+
res.send("You must include item in your body!")
|
|
69
|
+
}
|
|
70
|
+
})
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
### As an ArrayBuffer
|
|
74
|
+
|
|
75
|
+
```javascript
|
|
76
|
+
router.post("/submit", async (req, res) => {
|
|
77
|
+
// Parse body into an ArrayBuffer
|
|
78
|
+
let body = await req.arrayBuffer();
|
|
79
|
+
|
|
80
|
+
console.debug(body);
|
|
81
|
+
})
|
|
82
|
+
```
|