@travetto/web 6.0.0-rc.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.
Files changed (50) hide show
  1. package/README.md +734 -0
  2. package/__index__.ts +44 -0
  3. package/package.json +66 -0
  4. package/src/common/global.ts +30 -0
  5. package/src/config.ts +18 -0
  6. package/src/context.ts +49 -0
  7. package/src/decorator/common.ts +87 -0
  8. package/src/decorator/controller.ts +13 -0
  9. package/src/decorator/endpoint.ts +102 -0
  10. package/src/decorator/param.ts +64 -0
  11. package/src/interceptor/accept.ts +70 -0
  12. package/src/interceptor/body-parse.ts +123 -0
  13. package/src/interceptor/compress.ts +119 -0
  14. package/src/interceptor/context.ts +23 -0
  15. package/src/interceptor/cookies.ts +97 -0
  16. package/src/interceptor/cors.ts +94 -0
  17. package/src/interceptor/decompress.ts +91 -0
  18. package/src/interceptor/etag.ts +99 -0
  19. package/src/interceptor/logging.ts +71 -0
  20. package/src/interceptor/respond.ts +26 -0
  21. package/src/interceptor/response-cache.ts +47 -0
  22. package/src/interceptor/trust-proxy.ts +53 -0
  23. package/src/registry/controller.ts +288 -0
  24. package/src/registry/types.ts +229 -0
  25. package/src/registry/visitor.ts +52 -0
  26. package/src/router/base.ts +67 -0
  27. package/src/router/standard.ts +59 -0
  28. package/src/types/cookie.ts +18 -0
  29. package/src/types/core.ts +33 -0
  30. package/src/types/dispatch.ts +23 -0
  31. package/src/types/error.ts +10 -0
  32. package/src/types/filter.ts +7 -0
  33. package/src/types/headers.ts +108 -0
  34. package/src/types/interceptor.ts +54 -0
  35. package/src/types/message.ts +33 -0
  36. package/src/types/request.ts +22 -0
  37. package/src/types/response.ts +20 -0
  38. package/src/util/body.ts +220 -0
  39. package/src/util/common.ts +142 -0
  40. package/src/util/cookie.ts +145 -0
  41. package/src/util/endpoint.ts +277 -0
  42. package/src/util/mime.ts +36 -0
  43. package/src/util/net.ts +61 -0
  44. package/support/test/dispatch-util.ts +90 -0
  45. package/support/test/dispatcher.ts +15 -0
  46. package/support/test/suite/base.ts +61 -0
  47. package/support/test/suite/controller.ts +103 -0
  48. package/support/test/suite/schema.ts +275 -0
  49. package/support/test/suite/standard.ts +178 -0
  50. package/support/transformer.web.ts +207 -0
package/README.md ADDED
@@ -0,0 +1,734 @@
1
+ <!-- This file was generated by @travetto/doc and should not be modified directly -->
2
+ <!-- Please modify https://github.com/travetto/travetto/tree/main/module/web/DOC.tsx and execute "npx trv doc" to rebuild -->
3
+ # Web API
4
+
5
+ ## Declarative api for Web Applications with support for the dependency injection.
6
+
7
+ **Install: @travetto/web**
8
+ ```bash
9
+ npm install @travetto/web
10
+
11
+ # or
12
+
13
+ yarn add @travetto/web
14
+ ```
15
+
16
+ The module provides a declarative API for creating and describing a Web application. Since the framework is declarative, decorators are used to configure almost everything. The general layout of an application is a collection of [@Controller](https://github.com/travetto/travetto/tree/main/module/web/src/decorator/controller.ts#L9)s that employ some combination of [WebInterceptor](https://github.com/travetto/travetto/tree/main/module/web/src/types/interceptor.ts#L15)s to help manage which functionality is executed before the [Endpoint](https://github.com/travetto/travetto/tree/main/module/web/src/decorator/endpoint.ts#L14) code, within the [@Controller](https://github.com/travetto/travetto/tree/main/module/web/src/decorator/controller.ts#L9). This module will look at:
17
+ * Request/Response Pattern
18
+ * Defining a [@Controller](https://github.com/travetto/travetto/tree/main/module/web/src/decorator/controller.ts#L9)
19
+ * Defining an [Endpoint](https://github.com/travetto/travetto/tree/main/module/web/src/decorator/endpoint.ts#L14)s
20
+ * Using [WebInterceptor](https://github.com/travetto/travetto/tree/main/module/web/src/types/interceptor.ts#L15)s
21
+ * Creating a Custom [WebInterceptor](https://github.com/travetto/travetto/tree/main/module/web/src/types/interceptor.ts#L15)
22
+ * Cookies
23
+ * SSL Support
24
+ * Error Handling
25
+
26
+ ## Request/Response Pattern
27
+ Unlike other frameworks (e.g. [express](https://expressjs.com), [fastify](https://www.fastify.io/)), this module takes an approach that is similar to [AWS Lambda](https://aws.amazon.com/lambda/)'s model for requests and responses. What you can see here is that [WebRequest](https://github.com/travetto/travetto/tree/main/module/web/src/types/request.ts#L11) and [WebResponse](https://github.com/travetto/travetto/tree/main/module/web/src/types/response.ts#L3) are very simple objects, with the focus being on the `payload` and `body`. This is intended to provide maximal compatibility with non-HTTP sources. The driving goal is to support more than just standard HTTP servers but also allow for seamless integration with tools like event queues, web sockets, etc.
28
+
29
+ **Code: Base Shape**
30
+ ```typescript
31
+ export class BaseWebMessage<B = unknown, C = unknown> implements WebMessage<B, C> {
32
+ readonly context: C;
33
+ readonly headers: WebHeaders;
34
+ body?: B;
35
+ constructor(o: WebMessageInit<B, C> = {});
36
+ }
37
+ ```
38
+
39
+ **Code: Request Shape**
40
+ ```typescript
41
+
42
+ ```
43
+
44
+ **Code: Response Shape**
45
+ ```typescript
46
+ export class WebResponse<B = unknown> extends BaseWebMessage<B, WebResponseContext> {
47
+ /**
48
+ * Build the redirect
49
+ * @param location Location to redirect to
50
+ * @param statusCode Status code
51
+ */
52
+ static redirect(location: string, statusCode = 302): WebResponse<undefined>;
53
+ }
54
+ ```
55
+
56
+ These objects do not represent the underlying sockets provided by various http servers, but in fact are simple wrappers that track the flow through the call stack of the various [WebInterceptor](https://github.com/travetto/travetto/tree/main/module/web/src/types/interceptor.ts#L15)s and the [Endpoint](https://github.com/travetto/travetto/tree/main/module/web/src/decorator/endpoint.ts#L14) handler. One of the biggest departures here, is that the response is not an entity that is passed around from call-site to call-site, but is is solely a return-value. This doesn't mean the return value has to be static and pre-allocated, on the contrary streams are still supported. The difference here is that the streams/asynchronous values will be consumed until the response is sent back to the user. The [CompressInterceptor](https://github.com/travetto/travetto/tree/main/module/web/src/interceptor/compress.ts#L50) is a good reference for transforming a [WebResponse](https://github.com/travetto/travetto/tree/main/module/web/src/types/response.ts#L3) that can either be a stream or a fixed value.
57
+
58
+ ## Defining a Controller
59
+ To start, we must define a [@Controller](https://github.com/travetto/travetto/tree/main/module/web/src/decorator/controller.ts#L9), which is only allowed on classes. Controllers can be configured with:
60
+ * `path` - The required context path the controller will operate atop
61
+ * `title` - The definition of the controller
62
+ * `description` - High level description fo the controller
63
+ Additionally, the module is predicated upon [Dependency Injection](https://github.com/travetto/travetto/tree/main/module/di#readme "Dependency registration/management and injection support."), and so all standard injection techniques (constructor, fields) work for registering dependencies.
64
+
65
+ [JSDoc](http://usejsdoc.org/about-getting-started.html) comments can also be used to define the `title` attribute.
66
+
67
+ **Code: Basic Controller Registration**
68
+ ```typescript
69
+ import { Controller } from '@travetto/web';
70
+
71
+ @Controller('/simple')
72
+ class SimpleController {
73
+ // endpoints
74
+ }
75
+ ```
76
+
77
+ ## Defining an Endpoint
78
+ Once the controller is declared, each method of the controller is a candidate for being an endpoint. By design, everything is asynchronous, and so async/await is natively supported.
79
+
80
+ The most common pattern is to register HTTP-driven endpoints. The HTTP methods that are currently supported:
81
+ * [@Get](https://github.com/travetto/travetto/tree/main/module/web/src/decorator/endpoint.ts#L43)
82
+ * [@Post](https://github.com/travetto/travetto/tree/main/module/web/src/decorator/endpoint.ts#L50)
83
+ * [@Put](https://github.com/travetto/travetto/tree/main/module/web/src/decorator/endpoint.ts#L57)
84
+ * [@Delete](https://github.com/travetto/travetto/tree/main/module/web/src/decorator/endpoint.ts#L70)
85
+ * [@Patch](https://github.com/travetto/travetto/tree/main/module/web/src/decorator/endpoint.ts#L64)
86
+ * [@Head](https://github.com/travetto/travetto/tree/main/module/web/src/decorator/endpoint.ts#L76)
87
+ * [@Options](https://github.com/travetto/travetto/tree/main/module/web/src/decorator/endpoint.ts#L82)
88
+ Similar to the Controller, each endpoint decorator handles the following config:
89
+ * `title` - The definition of the endpoint
90
+ * `description` - High level description fo the endpoint
91
+ [JSDoc](http://usejsdoc.org/about-getting-started.html) comments can also be used to define the `title` attribute, as well as describing the parameters using `@param` tags in the comment.
92
+
93
+ The return type of the method will also be used to describe the `responseType` if not specified manually.
94
+
95
+ **Code: Controller with Sample Endpoint**
96
+ ```typescript
97
+ import { Get, Controller } from '@travetto/web';
98
+
99
+ class Data { }
100
+
101
+ @Controller('/simple')
102
+ class SimpleController {
103
+
104
+ /**
105
+ * Gets the most basic of data
106
+ */
107
+ @Get('/')
108
+ async simpleGet() {
109
+ let data: Data | undefined;
110
+ //
111
+ return data;
112
+ }
113
+ }
114
+ ```
115
+
116
+ **Note**: In development mode the module supports hot reloading of `class`es. Endpoints can be added/modified/removed at runtime.
117
+
118
+ ### Parameters
119
+ Endpoints can be configured to describe and enforce parameter behavior. Request parameters can be defined in five areas:
120
+ * [@PathParam](https://github.com/travetto/travetto/tree/main/module/web/src/decorator/param.ts#L37) - Path params
121
+ * [@QueryParam](https://github.com/travetto/travetto/tree/main/module/web/src/decorator/param.ts#L43) - Query params - can be either a single value or bind to a whole object
122
+ * [@Body](https://github.com/travetto/travetto/tree/main/module/web/src/decorator/param.ts#L55) - Request body
123
+ * [@HeaderParam](https://github.com/travetto/travetto/tree/main/module/web/src/decorator/param.ts#L49) - Header values
124
+ Each [@Param](https://github.com/travetto/travetto/tree/main/module/web/src/decorator/param.ts#L24) can be configured to indicate:
125
+ * `name` - Name of param, field name, defaults to handler parameter name if necessary
126
+ * `description` - Description of param, pulled from [JSDoc](http://usejsdoc.org/about-getting-started.html), or defaults to name if empty
127
+ * `required?` - Is the field required?, defaults to whether or not the parameter itself is optional
128
+ * `type` - The class of the type to be enforced, pulled from parameter type
129
+ [JSDoc](http://usejsdoc.org/about-getting-started.html) comments can also be used to describe parameters using `@param` tags in the comment.
130
+
131
+ **Code: Full-fledged Controller with Endpoints**
132
+ ```typescript
133
+ import { Get, Controller, Post, QueryParam, WebRequest, ContextParam } from '@travetto/web';
134
+ import { Integer, Min } from '@travetto/schema';
135
+
136
+ import { MockService } from './mock.ts';
137
+
138
+ @Controller('/simple')
139
+ export class Simple {
140
+
141
+ service: MockService;
142
+
143
+ @ContextParam()
144
+ request: WebRequest;
145
+
146
+ constructor(service: MockService) {
147
+ this.service = service;
148
+ }
149
+
150
+ /**
151
+ * Get a random user by name
152
+ */
153
+ @Get('/name')
154
+ async getName() {
155
+ const user = await this.service.fetch();
156
+ return `/simple/name => ${user.first.toLowerCase()}`;
157
+ }
158
+
159
+ /**
160
+ * Get a user by id
161
+ */
162
+ @Get('/:id')
163
+ async getById(id: number) {
164
+ const user = await this.service.fetch(id);
165
+ return `/simple/id => ${user.first.toLowerCase()}`;
166
+ }
167
+
168
+ @Post('/name')
169
+ async createName(person: { name: string }) {
170
+ await this.service.update({ name: person.name });
171
+ return { success: true };
172
+ }
173
+
174
+ @Get('img/*')
175
+ async getImage(
176
+ @QueryParam('w') @Integer() @Min(100) width?: number,
177
+ @QueryParam('h') @Integer() @Min(100) height?: number
178
+ ) {
179
+ const img = await this.service.fetchImage(this.request.context.path, { width, height });
180
+ return img;
181
+ }
182
+ }
183
+ ```
184
+
185
+ ### ContextParam
186
+ In addition to endpoint parameters (i.e. user-provided inputs), there may also be a desire to access indirect contextual information. Specifically you may need access to the entire [WebRequest](https://github.com/travetto/travetto/tree/main/module/web/src/types/request.ts#L11). These are able to be injected using the [@ContextParam](https://github.com/travetto/travetto/tree/main/module/web/src/decorator/param.ts#L61) on a class-level field from the [WebAsyncContext](https://github.com/travetto/travetto/tree/main/module/web/src/context.ts#L11). These are not exposed as endpoint parameters as they cannot be provided when making RPC invocations.
187
+
188
+ **Code: Example ContextParam usage**
189
+ ```typescript
190
+ import { CacheControl, ContextParam, Controller, Get, WebRequest, WebResponse } from '@travetto/web';
191
+
192
+ @Controller('/context')
193
+ class ContextController {
194
+
195
+ @ContextParam()
196
+ request: WebRequest;
197
+
198
+ /**
199
+ * Gets the ip of the user, ensure no caching
200
+ */
201
+ @CacheControl(0)
202
+ @Get('/ip')
203
+ async getIp() {
204
+ return new WebResponse({
205
+ body: { ip: this.request.context.connection?.ip },
206
+ headers: {
207
+ 'Content-Type': 'application/json+ip'
208
+ }
209
+ });
210
+ }
211
+ }
212
+ ```
213
+
214
+ **Note**: When referencing the [@ContextParam](https://github.com/travetto/travetto/tree/main/module/web/src/decorator/param.ts#L61) values, the contract for idempotency needs to be carefully inspected, if expected. You can see in the example above that the [CacheControl](https://github.com/travetto/travetto/tree/main/module/web/src/decorator/common.ts#L55) decorator is used to ensure that the response is not cached.
215
+
216
+ ### Validating Inputs
217
+ The module provides high level access for [Schema](https://github.com/travetto/travetto/tree/main/module/schema#readme "Data type registry for runtime validation, reflection and binding.") support, via decorators, for validating and typing request inputs.
218
+
219
+ By default, all endpoint parameters are validated for type, and any additional constraints added (required, vs optional, minlength, etc). Each parameter location ([@PathParam](https://github.com/travetto/travetto/tree/main/module/web/src/decorator/param.ts#L37), [@Body](https://github.com/travetto/travetto/tree/main/module/web/src/decorator/param.ts#L55), [@QueryParam](https://github.com/travetto/travetto/tree/main/module/web/src/decorator/param.ts#L43), [@HeaderParam](https://github.com/travetto/travetto/tree/main/module/web/src/decorator/param.ts#L49)) primarily provides a source to bind the endpoint arguments from. Once bound, the module will validate that the provided arguments are in fact valid. All validation will occur before the endpoint is ever executed, ensuring a strong contract.
220
+
221
+ **Code: Using Body for POST requests**
222
+ ```typescript
223
+ import { Schema } from '@travetto/schema';
224
+ import { Controller, Post, Body } from '@travetto/web';
225
+
226
+ @Schema()
227
+ class User {
228
+ name: string;
229
+ age: number;
230
+ }
231
+
232
+ @Controller('/user')
233
+ class UserController {
234
+
235
+ private service: {
236
+ update(user: User): Promise<User>;
237
+ };
238
+
239
+ @Post('/saveUser')
240
+ async save(@Body() user: User) {
241
+ const saved = await this.service.update(user);
242
+ return { success: !!saved };
243
+ }
244
+ }
245
+ ```
246
+
247
+ **Code: Using Query + Schema for GET requests**
248
+ ```typescript
249
+ import { Schema } from '@travetto/schema';
250
+ import { Controller, Get } from '@travetto/web';
251
+
252
+ @Schema()
253
+ class SearchParams {
254
+ page: number = 0;
255
+ pageSize: number = 100;
256
+ }
257
+
258
+ @Controller('/user')
259
+ class UserController {
260
+
261
+ private service: {
262
+ search(query: SearchParams): Promise<number[]>;
263
+ };
264
+
265
+ @Get('/search')
266
+ async search(query: SearchParams) {
267
+ return await this.service.search(query);
268
+ }
269
+ }
270
+ ```
271
+
272
+ Additionally, schema related inputs can also be used with `interface`s and `type` literals in lieu of classes. This is best suited for simple types:
273
+
274
+ **Code: Using QuerySchema with a type literal**
275
+ ```typescript
276
+ import { Controller, Get } from '@travetto/web';
277
+
278
+ type Paging = {
279
+ page?: number;
280
+ pageSize?: number;
281
+ };
282
+
283
+ @Controller('/user')
284
+ class UserController {
285
+
286
+ private service: {
287
+ search(query: Paging): Promise<number>;
288
+ };
289
+
290
+ @Get('/search')
291
+ async search(query: Paging = { page: 0, pageSize: 100 }) {
292
+ return await this.service.search(query);
293
+ }
294
+ }
295
+ ```
296
+
297
+ ## Using Interceptors
298
+ [WebInterceptor](https://github.com/travetto/travetto/tree/main/module/web/src/types/interceptor.ts#L15)s are a key part of the web framework, to allow for conditional functionality to be added, across all endpoints.
299
+
300
+ ### Anatomy of an Interceptor
301
+
302
+ **Code: A Simple Interceptor**
303
+ ```typescript
304
+ import { WebChainedContext, WebInterceptor, WebInterceptorCategory, WebInterceptorContext } from '@travetto/web';
305
+ import { Injectable } from '@travetto/di';
306
+
307
+ @Injectable()
308
+ export class HelloWorldInterceptor implements WebInterceptor {
309
+
310
+ category: WebInterceptorCategory = 'application';
311
+
312
+ applies(context: WebInterceptorContext<unknown>): boolean {
313
+ return context.endpoint.httpMethod === 'HEAD';
314
+ }
315
+
316
+ filter(ctx: WebChainedContext) {
317
+ console.log('Hello world!');
318
+ return ctx.next();
319
+ }
320
+ }
321
+ ```
322
+
323
+ In this example you can see the markers of a simple interceptor:
324
+
325
+ #### category
326
+ `category` - This represents the generally request lifecycle phase an interceptor will run in. It can be customized further with `dependsOn` and `runsBefore` to control exact ordering within a category. In this example `application` represents the lowest priority, and will run right before the endpoint is executed.
327
+
328
+ #### applies
329
+ `applies` - This represents ability for the per-endpoint configuration to determine if an interceptor is applicable. By default, all interceptors will auto-register on every endpoint. Some interceptors are opt-in, and control that by setting applies to constantly return `false`.
330
+
331
+ #### filter
332
+ `filter` - This is the actual logic that will be invoked around the endpoint call, represented by `ctx.next()`. The next call passes control to the next interceptor all the way down to the endpoint, and then will pop back up the stack. Code executed before `next()` is generally used for request filtering, and code afterwards is generally used for response control.
333
+
334
+ Out of the box, the web framework comes with a few interceptors, and more are contributed by other modules as needed. The default interceptor set is (in order of execution):
335
+
336
+ ### Order of Execution
337
+
338
+ 1. global - Intended to run outside of the request flow - [AsyncContextInterceptor](https://github.com/travetto/travetto/tree/main/module/web/src/interceptor/context.ts#L13)
339
+ 1. terminal - Handles once request and response are finished building - [LoggingInterceptor](https://github.com/travetto/travetto/tree/main/module/web/src/interceptor/logging.ts#L28), [RespondInterceptor](https://github.com/travetto/travetto/tree/main/module/web/src/interceptor/respond.ts#L12)
340
+ 1. pre-request - Prepares the request for running - [TrustProxyInterceptor](https://github.com/travetto/travetto/tree/main/module/web/src/interceptor/trust-proxy.ts#L23)
341
+ 1. request - Handles inbound request, validation, and body preparation - [DecompressInterceptor](https://github.com/travetto/travetto/tree/main/module/web/src/interceptor/decompress.ts#L53), [AcceptInterceptor](https://github.com/travetto/travetto/tree/main/module/web/src/interceptor/accept.ts#L34), [BodyParseInterceptor](https://github.com/travetto/travetto/tree/main/module/web/src/interceptor/body-parse.ts#L61), [CookiesInterceptor](https://github.com/travetto/travetto/tree/main/module/web/src/interceptor/cookies.ts#L56)
342
+ 1. response - Prepares outbound response - [CompressInterceptor](https://github.com/travetto/travetto/tree/main/module/web/src/interceptor/compress.ts#L50), [CorsInterceptor](https://github.com/travetto/travetto/tree/main/module/web/src/interceptor/cors.ts#L51), [EtagInterceptor](https://github.com/travetto/travetto/tree/main/module/web/src/interceptor/etag.ts#L34), [ResponseCacheInterceptor](https://github.com/travetto/travetto/tree/main/module/web/src/interceptor/response-cache.ts#L30)
343
+ 1. application - Lives outside of the general request/response behavior, [Web Auth](https://github.com/travetto/travetto/tree/main/module/auth-web#readme "Web authentication integration support for the Travetto framework") uses this for login and logout flows.
344
+
345
+ ### Packaged Interceptors
346
+
347
+ #### AsyncContextInterceptor
348
+ [AsyncContextInterceptor](https://github.com/travetto/travetto/tree/main/module/web/src/interceptor/context.ts#L13) is responsible for sharing context across the various layers that may be touched by a request. This
349
+
350
+ #### LoggingInterceptor
351
+ [LoggingInterceptor](https://github.com/travetto/travetto/tree/main/module/web/src/interceptor/logging.ts#L28) is used for logging the request/response, handling any error logging as needed. This interceptor can be noisy, and so can easily be disabled as needed by setting `web.log.applies: false` in your config.
352
+
353
+ **Code: Web Log Config**
354
+ ```typescript
355
+ export class WebLogConfig {
356
+ /**
357
+ * Enable logging of all requests
358
+ */
359
+ applies = true;
360
+ /**
361
+ * Should errors be dumped as full stack traces
362
+ */
363
+ showStackTrace = true;
364
+ }
365
+ ```
366
+
367
+ #### RespondInterceptor
368
+ [RespondInterceptor](https://github.com/travetto/travetto/tree/main/module/web/src/interceptor/respond.ts#L12) is a basic catch-all that forces errors and data alike into a consistent format for sending back to the user.
369
+
370
+ #### TrustProxyInterceptor
371
+ [TrustProxyInterceptor](https://github.com/travetto/travetto/tree/main/module/web/src/interceptor/trust-proxy.ts#L23) allows for overriding connection information (host, ip, protocol) using `X-Forwarded-*` headers. This allows for proxied requests to retain access to the "source" request information as necessary.
372
+
373
+ **Code: TrustProxy Config**
374
+ ```typescript
375
+ export class TrustProxyConfig {
376
+ /**
377
+ * Enforces trust rules for X-Forwarded-* headers
378
+ */
379
+ applies = true;
380
+ /**
381
+ * The accepted ips
382
+ */
383
+ ips: string[] = [];
384
+ }
385
+ ```
386
+
387
+ #### AcceptInterceptor
388
+ [AcceptInterceptor](https://github.com/travetto/travetto/tree/main/module/web/src/interceptor/accept.ts#L34) handles verifying the inbound request matches the allowed content-types. This acts as a standard gate-keeper for spurious input.
389
+
390
+ **Code: Accept Config**
391
+ ```typescript
392
+ export class AcceptConfig {
393
+ /**
394
+ * Accepts certain request content types
395
+ */
396
+ applies = false;
397
+ /**
398
+ * The accepted types
399
+ */
400
+ types: string[] = [];
401
+
402
+ @Ignore()
403
+ matcher: (type: string) => boolean;
404
+ }
405
+ ```
406
+
407
+ #### DecompressInterceptor
408
+ [DecompressInterceptor](https://github.com/travetto/travetto/tree/main/module/web/src/interceptor/decompress.ts#L53) handles decompressing the inbound request, if supported. This relies upon HTTP standards for content encoding, and negotiating the appropriate decompression scheme.
409
+
410
+ **Code: Decompress Config**
411
+ ```typescript
412
+ export class DecompressConfig {
413
+ /**
414
+ * Parse request body
415
+ */
416
+ applies: boolean = true;
417
+ /**
418
+ * Supported encodings
419
+ */
420
+ supportedEncodings: WebDecompressEncoding[] = ['br', 'gzip', 'deflate', 'identity'];
421
+ }
422
+ ```
423
+
424
+ #### CookiesInterceptor
425
+ [CookiesInterceptor](https://github.com/travetto/travetto/tree/main/module/web/src/interceptor/cookies.ts#L56) is responsible for processing inbound cookie headers and populating the appropriate data on the request, as well as sending the appropriate response data
426
+
427
+ **Code: Cookies Config**
428
+ ```typescript
429
+ export class CookieConfig implements CookieSetOptions {
430
+ /**
431
+ * Support reading/sending cookies
432
+ */
433
+ applies = true;
434
+ /**
435
+ * Are they signed
436
+ */
437
+ signed = true;
438
+ /**
439
+ * Supported only via http (not in JS)
440
+ */
441
+ httpOnly = true;
442
+ /**
443
+ * Enforce same site policy
444
+ */
445
+ sameSite: Cookie['sameSite'] = 'lax';
446
+ /**
447
+ * The signing keys
448
+ */
449
+ @Secret()
450
+ keys?: string[];
451
+ /**
452
+ * Is the cookie only valid for https
453
+ */
454
+ secure?: boolean = false;
455
+ /**
456
+ * The domain of the cookie
457
+ */
458
+ domain?: string;
459
+ }
460
+ ```
461
+
462
+ #### BodyParseInterceptor
463
+ [BodyParseInterceptor](https://github.com/travetto/travetto/tree/main/module/web/src/interceptor/body-parse.ts#L61) handles the inbound request, and converting the body payload into an appropriate format.
464
+
465
+ **Code: Body Parse Config**
466
+ ```typescript
467
+ export class BodyParseConfig {
468
+ /**
469
+ * Parse request body
470
+ */
471
+ applies: boolean = true;
472
+ /**
473
+ * Max body size limit
474
+ */
475
+ limit: `${number}${'mb' | 'kb' | 'gb' | 'b' | ''}` = '1mb';
476
+ /**
477
+ * How to interpret different content types
478
+ */
479
+ parsingTypes: Record<string, string> = {
480
+ text: 'text',
481
+ 'application/json': 'json',
482
+ 'application/x-www-form-urlencoded': 'form'
483
+ };
484
+
485
+ @Ignore()
486
+ _limit: number | undefined;
487
+
488
+ postConstruct(): void {
489
+ this._limit = WebCommonUtil.parseByteSize(this.limit);
490
+ }
491
+ }
492
+ ```
493
+
494
+ #### CompressInterceptor
495
+ [CompressInterceptor](https://github.com/travetto/travetto/tree/main/module/web/src/interceptor/compress.ts#L50) by default, will compress all valid outbound responses over a certain size, or for streams will cache every response. This relies on Node's [Buffer](https://nodejs.org/api/zlib.html) support for compression.
496
+
497
+ **Code: Compress Config**
498
+ ```typescript
499
+ export class CompressConfig {
500
+ /**
501
+ * Attempting to compressing responses
502
+ */
503
+ applies: boolean = true;
504
+ /**
505
+ * Raw encoding options
506
+ */
507
+ raw?: (ZlibOptions & BrotliOptions) | undefined;
508
+ /**
509
+ * Preferred encodings
510
+ */
511
+ preferredEncodings?: WebCompressEncoding[] = ['br', 'gzip', 'identity'];
512
+ /**
513
+ * Supported encodings
514
+ */
515
+ supportedEncodings: WebCompressEncoding[] = ['br', 'gzip', 'identity', 'deflate'];
516
+ }
517
+ ```
518
+
519
+ #### EtagInterceptor
520
+ [EtagInterceptor](https://github.com/travetto/travetto/tree/main/module/web/src/interceptor/etag.ts#L34) by default, will tag all cacheable HTTP responses, when the response value/length is known. Streams, and other async data sources do not have a pre-defined length, and so are ineligible for etagging.
521
+
522
+ **Code: ETag Config**
523
+ ```typescript
524
+ export class EtagConfig {
525
+ /**
526
+ * Attempt ETag generation
527
+ */
528
+ applies = true;
529
+ /**
530
+ * Should we generate a weak etag
531
+ */
532
+ weak?: boolean;
533
+
534
+ @Ignore()
535
+ cacheable?: boolean;
536
+ }
537
+ ```
538
+
539
+ #### CorsInterceptor
540
+ [CorsInterceptor](https://github.com/travetto/travetto/tree/main/module/web/src/interceptor/cors.ts#L51) allows cors functionality to be configured out of the box, by setting properties in your `application.yml`, specifically, the `web.cors` config space.
541
+
542
+ **Code: Cors Config**
543
+ ```typescript
544
+ export class CorsConfig {
545
+ /**
546
+ * Send CORS headers on responses
547
+ */
548
+ applies = true;
549
+ /**
550
+ * Allowed origins
551
+ */
552
+ origins?: string[];
553
+ /**
554
+ * Allowed http methods
555
+ */
556
+ methods?: HttpMethod[];
557
+ /**
558
+ * Allowed http headers
559
+ */
560
+ headers?: string[];
561
+ /**
562
+ * Support credentials?
563
+ */
564
+ credentials?: boolean;
565
+
566
+ @Ignore()
567
+ resolved: {
568
+ origins: Set<string>;
569
+ methods: string;
570
+ headers: string;
571
+ credentials: boolean;
572
+ };
573
+ }
574
+ ```
575
+
576
+ #### ResponseCacheInterceptor
577
+ [ResponseCacheInterceptor](https://github.com/travetto/travetto/tree/main/module/web/src/interceptor/response-cache.ts#L30) by default, disables caching for all GET requests if the response does not include caching headers. This can be managed by setting `web.getCache.applies: <boolean>` in your config. This interceptor applies by default.
578
+
579
+ ### Configuring Interceptors
580
+ All framework-provided interceptors, follow the same patterns for general configuration. This falls into three areas:
581
+
582
+ #### Enable/disable of individual interceptors via configuration
583
+ This applies only to interceptors that have opted in, to exposing a config, and tying that configuration to the applies logic.
584
+
585
+ **Code: Sample interceptor disabling configuration**
586
+ ```yaml
587
+ web:
588
+ trustProxy:
589
+ applies: false
590
+ ```
591
+
592
+ **Code: Configurable Interceptor**
593
+ ```typescript
594
+ export class TrustProxyConfig {
595
+ /**
596
+ * Enforces trust rules for X-Forwarded-* headers
597
+ */
598
+ applies = true;
599
+ /**
600
+ * The accepted ips
601
+ */
602
+ ips: string[] = [];
603
+ }
604
+ ```
605
+
606
+ #### Endpoint-enabled control via decorators
607
+
608
+ **Code: Sample controller with endpoint-level allow/deny**
609
+ ```typescript
610
+ import { Controller, Get, QueryParam, ConfigureInterceptor, CorsInterceptor, ExcludeInterceptors } from '@travetto/web';
611
+
612
+ @Controller('/allowDeny')
613
+ @ConfigureInterceptor(CorsInterceptor, { applies: true })
614
+ export class AlowDenyController {
615
+
616
+ @Get('/override')
617
+ @ConfigureInterceptor(CorsInterceptor, { applies: false })
618
+ withoutCors(@QueryParam() value: string) {
619
+
620
+ }
621
+
622
+ @Get('/raw')
623
+ @ExcludeInterceptors(v => v.category === 'response')
624
+ withoutResponse(@QueryParam() value: string) {
625
+
626
+ }
627
+ }
628
+ ```
629
+
630
+ The resolution logic is as follows:
631
+ * Check the resolved [Endpoint](https://github.com/travetto/travetto/tree/main/module/web/src/decorator/endpoint.ts#L14)/[@Controller](https://github.com/travetto/travetto/tree/main/module/web/src/decorator/controller.ts#L9) overrides to see if an interceptor is explicitly allowed or disallowed
632
+ * Default to `applies()` logic for all available interceptors
633
+
634
+ ## Creating a Custom WebInterceptor
635
+ Additionally it may be desirable to create a custom interceptor. Interceptors can be registered with the [Dependency Injection](https://github.com/travetto/travetto/tree/main/module/di#readme "Dependency registration/management and injection support.") by implementing the [WebInterceptor](https://github.com/travetto/travetto/tree/main/module/web/src/types/interceptor.ts#L15) interface and adding an [@Injectable](https://github.com/travetto/travetto/tree/main/module/di/src/decorator.ts#L29) decorator. A simple logging interceptor:
636
+
637
+ **Code: Defining a new Interceptor**
638
+ ```typescript
639
+ import { WebChainedContext, WebInterceptor, WebInterceptorCategory } from '@travetto/web';
640
+ import { Injectable } from '@travetto/di';
641
+
642
+ class Appender {
643
+ write(...args: unknown[]): void { }
644
+ }
645
+
646
+ @Injectable()
647
+ export class CustomLoggingInterceptor implements WebInterceptor {
648
+
649
+ category: WebInterceptorCategory = 'terminal';
650
+
651
+ appender: Appender;
652
+
653
+ constructor(appender: Appender) {
654
+ this.appender = appender;
655
+ }
656
+
657
+ async filter({ request, next }: WebChainedContext) {
658
+ try {
659
+ return await next();
660
+ } finally {
661
+ // Write request to database
662
+ this.appender.write(request.context.httpMethod, request.context.path, request.context.httpQuery);
663
+ }
664
+ }
665
+ }
666
+ ```
667
+
668
+ When running an interceptor, if you chose to skip calling `ctx.next()`, you will bypass all the downstream interceptors and return a response directly.
669
+
670
+ **Code: Defining a fully controlled Interceptor**
671
+ ```typescript
672
+ import { WebInterceptor, WebInterceptorCategory, WebChainedContext, WebError } from '@travetto/web';
673
+ import { Injectable } from '@travetto/di';
674
+
675
+ @Injectable()
676
+ export class SimpleAuthInterceptor implements WebInterceptor {
677
+
678
+ category: WebInterceptorCategory = 'terminal';
679
+
680
+ async filter(ctx: WebChainedContext) {
681
+ if (ctx.request.headers.has('X-Auth')) {
682
+ return await ctx.next();
683
+ } else {
684
+ throw WebError.for('Missing auth', 401, {}, 'authentication');
685
+ }
686
+ }
687
+ }
688
+ ```
689
+
690
+ ## Cookie Support
691
+ [express](https://expressjs.com)/[koa](https://koajs.com/)/[fastify](https://www.fastify.io/) all have their own cookie implementations that are common for each framework but are somewhat incompatible. To that end, cookies are supported for every platform, by using [cookies](https://www.npmjs.com/package/cookies). This functionality is exposed onto the [WebRequest](https://github.com/travetto/travetto/tree/main/module/web/src/types/request.ts#L11) object following the pattern set forth by Koa (this is the library Koa uses). This choice also enables better security support as we are able to rely upon standard behavior when it comes to cookies, and signing.
692
+
693
+ **Code: Sample Cookie Usage**
694
+ ```typescript
695
+ import {
696
+ Controller, Get, QueryParam, WebRequest, ContextParam,
697
+ WebResponse, CookieJar, CookieGetOptions, CookieSetOptions
698
+ } from '@travetto/web';
699
+
700
+ @Controller('/simple')
701
+ export class SimpleEndpoints {
702
+
703
+ private getOptions: CookieGetOptions;
704
+ private setOptions: CookieSetOptions;
705
+
706
+ @ContextParam()
707
+ request: WebRequest;
708
+
709
+ @ContextParam()
710
+ cookies: CookieJar;
711
+
712
+ @Get('/cookies')
713
+ getCookies(@QueryParam() value: string) {
714
+ this.cookies.get('name', this.getOptions);
715
+
716
+ // Set a cookie on response
717
+ this.cookies.set({ name: 'name', value, ...this.setOptions });
718
+ return new WebResponse({ body: null });
719
+ }
720
+ }
721
+ ```
722
+
723
+ ## SSL Support
724
+ Additionally the framework supports SSL out of the box, by allowing you to specify your public and private keys for the cert. In dev mode, the framework will also automatically generate a self-signed cert if:
725
+ * SSL support is configured
726
+ * [node-forge](https://www.npmjs.com/package/node-forge) is installed
727
+ * Not running in prod
728
+ * No keys provided
729
+ This is useful for local development where you implicitly trust the cert.
730
+
731
+ SSL support can be enabled by setting `web.ssl.active: true` in your config. The key/cert can be specified as string directly in the config file/environment variables. The key/cert can also be specified as a path to be picked up by [RuntimeResources](https://github.com/travetto/travetto/tree/main/module/runtime/src/resources.ts#L8).
732
+
733
+ ## Full Config
734
+ The entire [WebConfig](https://github.com/travetto/travetto/tree/main/module/web/src/config.ts#L7) which will show the full set of valid configuration parameters for the web module.