pipework 0.65.0 → 0.67.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/CHANGELOG.md +9 -0
- package/REFERENCE.md +43 -2
- package/dist/core/errors/http.d.ts +10 -0
- package/dist/core/errors/http.d.ts.map +1 -1
- package/dist/core/errors/http.js +14 -0
- package/dist/core/errors/http.js.map +1 -1
- package/dist/core/errors/index.d.ts +1 -1
- package/dist/core/errors/index.d.ts.map +1 -1
- package/dist/core/errors/index.js +1 -1
- package/dist/core/errors/index.js.map +1 -1
- package/dist/core/errors/namespace.d.ts +5 -1
- package/dist/core/errors/namespace.d.ts.map +1 -1
- package/dist/core/errors/namespace.js +5 -1
- package/dist/core/errors/namespace.js.map +1 -1
- package/dist/index.d.ts +3 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/dist/request/di/builder.d.ts +70 -1
- package/dist/request/di/builder.d.ts.map +1 -1
- package/dist/request/di/builder.js +75 -0
- package/dist/request/di/builder.js.map +1 -1
- package/dist/request/di/index.d.ts +1 -1
- package/dist/request/di/index.d.ts.map +1 -1
- package/dist/request/di/resolve.d.ts +2 -1
- package/dist/request/di/resolve.d.ts.map +1 -1
- package/dist/request/di/resolve.js +8 -0
- package/dist/request/di/resolve.js.map +1 -1
- package/dist/request/di/types.d.ts +46 -0
- package/dist/request/di/types.d.ts.map +1 -1
- package/dist/request/di/types.js.map +1 -1
- package/dist/request/http/file.d.ts +62 -0
- package/dist/request/http/file.d.ts.map +1 -0
- package/dist/request/http/file.js +120 -0
- package/dist/request/http/file.js.map +1 -0
- package/dist/request/http/index.d.ts +1 -0
- package/dist/request/http/index.d.ts.map +1 -1
- package/dist/request/http/index.js.map +1 -1
- package/dist/request/http/namespace.d.ts +5 -2
- package/dist/request/http/namespace.d.ts.map +1 -1
- package/dist/request/http/namespace.js +3 -2
- package/dist/request/http/namespace.js.map +1 -1
- package/dist/request/http/server.d.ts.map +1 -1
- package/dist/request/http/server.js +111 -6
- package/dist/request/http/server.js.map +1 -1
- package/dist/request/http/types.d.ts +3 -1
- package/dist/request/http/types.d.ts.map +1 -1
- package/dist/request/http/upload.d.ts +39 -0
- package/dist/request/http/upload.d.ts.map +1 -0
- package/dist/request/http/upload.js +140 -0
- package/dist/request/http/upload.js.map +1 -0
- package/dist/request/openapi/generate.d.ts.map +1 -1
- package/dist/request/openapi/generate.js +30 -1
- package/dist/request/openapi/generate.js.map +1 -1
- package/package.json +2 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"resolve.js","sourceRoot":"","sources":["../../../src/request/di/resolve.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,UAAU,EAAE,MAAM,YAAY,CAAA;AAMvC,OAAO,EAAE,oBAAoB,EAAE,MAAM,kCAAkC,CAAA;AAEvE,OAAO,EAAE,cAAc,EAAE,MAAM,qBAAqB,CAAA;AACpD,OAAO,EAAE,wBAAwB,EAAE,MAAM,qBAAqB,CAAA;AAC9D,OAAO,EAAE,iBAAiB,EAAE,eAAe,EAAE,MAAM,4BAA4B,CAAA;AAC/E,OAAO,EAAE,KAAK,EAAE,MAAM,4BAA4B,CAAA;AAClD,OAAO,EAAE,cAAc,EAAE,mBAAmB,EAAE,MAAM,gCAAgC,CAAA;
|
|
1
|
+
{"version":3,"file":"resolve.js","sourceRoot":"","sources":["../../../src/request/di/resolve.ts"],"names":[],"mappings":"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"}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import type { Schema } from '../../data/schema/types.js';
|
|
2
2
|
import type { DB } from '../../data/db/index.js';
|
|
3
|
+
import type { Readable } from 'node:stream';
|
|
3
4
|
import type { BackoffConfig } from '../../async/jobs/queue.js';
|
|
4
5
|
/** The default database name constant ('default') used when pipe() is called without a name argument. */
|
|
5
6
|
export declare const DEFAULT_DB: "__pipework_default__";
|
|
@@ -57,6 +58,51 @@ export interface HandlerMeta {
|
|
|
57
58
|
readonly wantsRequest: boolean;
|
|
58
59
|
readonly wantsResponse: boolean;
|
|
59
60
|
readonly wantsRawBody: boolean;
|
|
61
|
+
readonly upload?: UploadConfig | null;
|
|
62
|
+
readonly file?: FileConfig | null;
|
|
63
|
+
}
|
|
64
|
+
/** A route's file response, declared by `.file()`. */
|
|
65
|
+
export interface FileConfig {
|
|
66
|
+
/** Media types the route may answer with; `type/*` wildcards allowed. */
|
|
67
|
+
readonly mediaTypes: readonly string[];
|
|
68
|
+
/** Status of the file response (default 200). */
|
|
69
|
+
readonly status: number;
|
|
70
|
+
}
|
|
71
|
+
/** Upload whose whole request body is the file's bytes. */
|
|
72
|
+
export interface BinaryUploadConfig {
|
|
73
|
+
readonly kind: 'binary';
|
|
74
|
+
/** Accepted request media types; `type/*` wildcards allowed. Others get 415. */
|
|
75
|
+
readonly mediaTypes: readonly string[];
|
|
76
|
+
/** Largest accepted body in bytes. Over it gets 413. */
|
|
77
|
+
readonly maxBytes: number;
|
|
78
|
+
}
|
|
79
|
+
/** Upload whose file is one part of a `multipart/form-data` body. */
|
|
80
|
+
export interface MultipartUploadConfig {
|
|
81
|
+
readonly kind: 'multipart';
|
|
82
|
+
/** Name of the form field carrying the file. */
|
|
83
|
+
readonly file: string;
|
|
84
|
+
/** Accepted media types of the file part; `type/*` wildcards allowed. Others get 415. */
|
|
85
|
+
readonly mediaTypes: readonly string[];
|
|
86
|
+
/** Largest accepted file in bytes. Over it gets 413. */
|
|
87
|
+
readonly maxBytes: number;
|
|
88
|
+
/** Text field names accepted ahead of the file part; any other field gets 422. */
|
|
89
|
+
readonly fields?: readonly string[];
|
|
90
|
+
}
|
|
91
|
+
export type UploadConfig = BinaryUploadConfig | MultipartUploadConfig;
|
|
92
|
+
/** The `upload` dep of a binary upload route. */
|
|
93
|
+
export interface BinaryUpload {
|
|
94
|
+
readonly stream: Readable;
|
|
95
|
+
readonly mediaType: string;
|
|
96
|
+
/** Declared Content-Length, or null for a chunked body. */
|
|
97
|
+
readonly length: number | null;
|
|
98
|
+
}
|
|
99
|
+
/** The `upload` dep of a multipart upload route. */
|
|
100
|
+
export interface MultipartUpload {
|
|
101
|
+
readonly stream: Readable;
|
|
102
|
+
readonly mediaType: string;
|
|
103
|
+
readonly filename: string | null;
|
|
104
|
+
/** Text fields that came before the file part. */
|
|
105
|
+
readonly fields: Readonly<Record<string, string>>;
|
|
60
106
|
}
|
|
61
107
|
/**
|
|
62
108
|
* What a route screen is shown: the request line and headers, plus the declared
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../../src/request/di/types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,4BAA4B,CAAA;AACxD,OAAO,KAAK,EAAE,EAAE,EAAE,MAAM,wBAAwB,CAAA;AAChD,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,2BAA2B,CAAA;AAE9D,yGAAyG;AACzG,eAAO,MAAM,UAAU,EAAG,sBAA+B,CAAA;AAEzD,kFAAkF;AAClF,MAAM,MAAM,eAAe,GAAG,UAAU,GAAG,UAAU,GAAG,MAAM,CAAA;AAE9D,8EAA8E;AAC9E,MAAM,WAAW,cAAc;IAC7B,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAA;IACzB,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAA;IACvB,QAAQ,CAAC,KAAK,CAAC,EAAE,MAAM,CAAA;CACxB;AAED,mFAAmF;AACnF,MAAM,WAAW,cAAc;IAC7B,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAA;IACzB,QAAQ,CAAC,OAAO,CAAC,EAAE,aAAa,CAAA;CACjC;AAED;;;;;;;;;GASG;AACH,MAAM,MAAM,kBAAkB,GAAG,MAAM,GAAG,QAAQ,CAAA;AAElD;;;;;;;GAOG;AACH,MAAM,MAAM,UAAU,CAAC,GAAG,IAAI,CAAC,CAAC,EAAE,EAAE,EAAE,CAAC,GAAG,EAAE,GAAG,KAAK,OAAO,CAAC,CAAC,CAAC,KAAK,OAAO,CAAC,CAAC,CAAC,CAAA;AAE7E,kGAAkG;AAClG,MAAM,MAAM,YAAY,GAAG,CAAC,CAAC,EAAE,EAAE,EAAE,CAAC,GAAG,EAAE,MAAM,CAAC,MAAM,EAAE,EAAE,CAAC,KAAK,OAAO,CAAC,CAAC,CAAC,KAAK,OAAO,CAAC,CAAC,CAAC,CAAA;AAEzF,4GAA4G;AAC5G,MAAM,WAAW,WAAW;IAC1B,QAAQ,CAAC,SAAS,EAAE,SAAS,MAAM,EAAE,CAAA;IACrC,QAAQ,CAAC,YAAY,EAAE,OAAO,CAAA;IAC9B,QAAQ,CAAC,eAAe,EAAE,eAAe,CAAA;IACzC,QAAQ,CAAC,WAAW,EAAE,MAAM,GAAG,IAAI,CAAA;IACnC,QAAQ,CAAC,aAAa,EAAE,WAAW,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;IACnD,QAAQ,CAAC,WAAW,EAAE,MAAM,GAAG,IAAI,CAAA;IACnC,QAAQ,CAAC,YAAY,EAAE,MAAM,GAAG,IAAI,CAAA;IACpC,QAAQ,CAAC,KAAK,EAAE,SAAS,GAAG,IAAI,CAAA;IAChC,QAAQ,CAAC,KAAK,EAAE,OAAO,CAAA;IACvB,QAAQ,CAAC,OAAO,EAAE,MAAM,GAAG,IAAI,CAAA;IAC/B,QAAQ,CAAC,QAAQ,EAAE,cAAc,GAAG,IAAI,CAAA;IACxC,QAAQ,CAAC,UAAU,EAAE,MAAM,GAAG,IAAI,CAAA;IAClC,QAAQ,CAAC,cAAc,EAAE,kBAAkB,CAAA;IAC3C,QAAQ,CAAC,UAAU,EAAE,cAAc,GAAG,IAAI,CAAA;IAC1C,QAAQ,CAAC,YAAY,EAAE,OAAO,CAAA;IAC9B,QAAQ,CAAC,aAAa,EAAE,OAAO,CAAA;IAC/B,QAAQ,CAAC,YAAY,EAAE,OAAO,CAAA;CAC/B;AAED;;;GAGG;AACH,MAAM,WAAW,eAAe;IAC9B,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAA;IACvB,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAA;IACpB,QAAQ,CAAC,OAAO,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,CAAC,CAAC,CAAA;IAC9D,iFAAiF;IACjF,QAAQ,CAAC,aAAa,EAAE,MAAM,GAAG,IAAI,CAAA;CACtC;AAED,yFAAyF;AACzF,MAAM,WAAW,aAAa;IAC5B,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAA;IACvB,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAA;IACvB,qEAAqE;IACrE,QAAQ,CAAC,IAAI,CAAC,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CAAA;CAClD;AAED;;;GAGG;AACH,MAAM,MAAM,WAAW,GAAG,CAAC,OAAO,EAAE,eAAe,KAAK,aAAa,GAAG,IAAI,GAAG,SAAS,GAAG,IAAI,GAAG,OAAO,CAAC,aAAa,GAAG,IAAI,GAAG,SAAS,GAAG,IAAI,CAAC,CAAA;AAElJ,qEAAqE;AACrE,MAAM,WAAW,SAAS;IACxB,QAAQ,CAAC,MAAM,EAAE,KAAK,GAAG,MAAM,GAAG,KAAK,GAAG,OAAO,GAAG,QAAQ,CAAA;IAC5D,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAA;IACrB,sGAAsG;IACtG,QAAQ,CAAC,SAAS,CAAC,EAAE,MAAM,CAAA;IAC3B,mFAAmF;IACnF,QAAQ,CAAC,MAAM,CAAC,EAAE,WAAW,CAAA;CAC9B;AAED,8EAA8E;AAC9E,MAAM,MAAM,UAAU,GAAG,SAAS,CAAC,QAAQ,CAAC,CAAA;AAE5C,iHAAiH;AACjH,MAAM,WAAW,OAAO,CAAC,KAAK,SAAS,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAAE,OAAO;IACrE,OAAO,CAAC,IAAI,EAAE,KAAK,GAAG,OAAO,CAAC,OAAO,CAAC,CAAA;IACtC,QAAQ,CAAC,IAAI,EAAE,WAAW,CAAA;CAC3B;AAED,iHAAiH;AACjH,MAAM,MAAM,SAAS,CAAC,CAAC,IAAI,CAAC,SAAS,OAAO,CAAC,MAAM,CAAC,EAAE,OAAO,CAAC,GAAG,CAAC,GAAG,KAAK,CAAA;AAC1E,kHAAkH;AAClH,MAAM,MAAM,WAAW,CAAC,CAAC,IAAI,CAAC,SAAS,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAAE,MAAM,CAAC,CAAC,GAAG,CAAC,GAAG,KAAK,CAAA;AAE5F,0EAA0E;AAC1E,MAAM,MAAM,YAAY,CAAC,CAAC,SAAS,MAAM,IAAI,MAAM,CAAC,CAAC,EAAE,EAAE,CAAC,CAAA"}
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../../src/request/di/types.ts"],"names":[],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.js","sourceRoot":"","sources":["../../../src/request/di/types.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"types.js","sourceRoot":"","sources":["../../../src/request/di/types.ts"],"names":[],"mappings":"AAKA,yGAAyG;AACzG,MAAM,CAAC,MAAM,UAAU,GAAG,sBAA+B,CAAA"}
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import { Readable } from 'node:stream';
|
|
2
|
+
import type { FileConfig } from '../di/types.js';
|
|
3
|
+
declare const FILE_RESPONSE: unique symbol;
|
|
4
|
+
/** What `http.file()` takes. */
|
|
5
|
+
export interface FileResponseInit {
|
|
6
|
+
/** The bytes, or a stream that is read as it is sent. */
|
|
7
|
+
readonly body: Buffer | Uint8Array | Readable;
|
|
8
|
+
/** Content-Type of the response, e.g. `application/pdf`. */
|
|
9
|
+
readonly mediaType: string;
|
|
10
|
+
/** Name the browser saves the file under. Any characters; the header is escaped. */
|
|
11
|
+
readonly filename?: string;
|
|
12
|
+
/** `'attachment'` (default) downloads; `'inline'` lets the browser display it. */
|
|
13
|
+
readonly disposition?: 'attachment' | 'inline';
|
|
14
|
+
/** Byte length of a stream body, sent as Content-Length. Ignored for a Buffer, whose length is known. */
|
|
15
|
+
readonly length?: number;
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* A file for a route to answer with, made by `http.file()`. Return it from a
|
|
19
|
+
* handler declared with `.file()`; it is sent as the response body, streamed
|
|
20
|
+
* when `body` is a Readable.
|
|
21
|
+
*/
|
|
22
|
+
export interface FileResponse {
|
|
23
|
+
readonly [FILE_RESPONSE]: true;
|
|
24
|
+
readonly body: Buffer | Readable;
|
|
25
|
+
readonly mediaType: string;
|
|
26
|
+
readonly filename: string | undefined;
|
|
27
|
+
readonly disposition: 'attachment' | 'inline';
|
|
28
|
+
readonly length: number | undefined;
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* Builds the value a handler returns to answer with a file: `body` (Buffer,
|
|
32
|
+
* Uint8Array or Readable), `mediaType`, and optionally `filename`,
|
|
33
|
+
* `disposition` (`'attachment'` default, or `'inline'`) and `length` (the byte
|
|
34
|
+
* length of a stream, sent as Content-Length; without it a stream is sent
|
|
35
|
+
* chunked).
|
|
36
|
+
*
|
|
37
|
+
* Declare the route with `.file({ mediaTypes })` so OpenAPI describes it. The
|
|
38
|
+
* response carries Content-Type, Content-Length (Buffer, or stream with
|
|
39
|
+
* `length`), Content-Disposition with an ASCII `filename=` fallback and the
|
|
40
|
+
* exact name in `filename*=UTF-8''...`, and `X-Content-Type-Options: nosniff`.
|
|
41
|
+
*
|
|
42
|
+
* The request transaction (auto mode) has committed before the first byte is
|
|
43
|
+
* sent, so a stream must not need it. A stream that fails before its first
|
|
44
|
+
* byte gets a 500; one that fails after destroys the connection and logs the
|
|
45
|
+
* error, so the client sees an aborted download, never a complete-looking
|
|
46
|
+
* short one. Range requests and redirects are not supported.
|
|
47
|
+
*/
|
|
48
|
+
export declare function file(init: FileResponseInit): FileResponse;
|
|
49
|
+
/** True for a value made by `http.file()`. */
|
|
50
|
+
export declare function isFileResponse(value: unknown): value is FileResponse;
|
|
51
|
+
/** The reason a file response cannot be sent for a route declared with `declared`, or null. */
|
|
52
|
+
export declare function fileResponseProblem(declared: FileConfig | null, file: FileResponse): string | null;
|
|
53
|
+
/** Content-Disposition value per RFC 6266 / RFC 5987: ASCII fallback plus the exact name percent-encoded. */
|
|
54
|
+
export declare function contentDisposition(disposition: 'attachment' | 'inline', filename: string | undefined): string;
|
|
55
|
+
/**
|
|
56
|
+
* Reads a stream's first chunk before anything is sent, so a stream that fails
|
|
57
|
+
* at once can still become a normal error response. Returns the stream to send
|
|
58
|
+
* (the same bytes, replayed), which fails after `length` mismatches or on error.
|
|
59
|
+
*/
|
|
60
|
+
export declare function primeStream(source: Readable, length: number | undefined): Promise<Readable>;
|
|
61
|
+
export {};
|
|
62
|
+
//# sourceMappingURL=file.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"file.d.ts","sourceRoot":"","sources":["../../../src/request/http/file.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,aAAa,CAAA;AACtC,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,gBAAgB,CAAA;AAGhD,QAAA,MAAM,aAAa,eAAsC,CAAA;AAEzD,gCAAgC;AAChC,MAAM,WAAW,gBAAgB;IAC/B,yDAAyD;IACzD,QAAQ,CAAC,IAAI,EAAE,MAAM,GAAG,UAAU,GAAG,QAAQ,CAAA;IAC7C,4DAA4D;IAC5D,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAA;IAC1B,oFAAoF;IACpF,QAAQ,CAAC,QAAQ,CAAC,EAAE,MAAM,CAAA;IAC1B,kFAAkF;IAClF,QAAQ,CAAC,WAAW,CAAC,EAAE,YAAY,GAAG,QAAQ,CAAA;IAC9C,yGAAyG;IACzG,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM,CAAA;CACzB;AAED;;;;GAIG;AACH,MAAM,WAAW,YAAY;IAC3B,QAAQ,CAAC,CAAC,aAAa,CAAC,EAAE,IAAI,CAAA;IAC9B,QAAQ,CAAC,IAAI,EAAE,MAAM,GAAG,QAAQ,CAAA;IAChC,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAA;IAC1B,QAAQ,CAAC,QAAQ,EAAE,MAAM,GAAG,SAAS,CAAA;IACrC,QAAQ,CAAC,WAAW,EAAE,YAAY,GAAG,QAAQ,CAAA;IAC7C,QAAQ,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,CAAA;CACpC;AAED;;;;;;;;;;;;;;;;;GAiBG;AACH,wBAAgB,IAAI,CAAC,IAAI,EAAE,gBAAgB,GAAG,YAAY,CAwBzD;AAED,8CAA8C;AAC9C,wBAAgB,cAAc,CAAC,KAAK,EAAE,OAAO,GAAG,KAAK,IAAI,YAAY,CAEpE;AAED,+FAA+F;AAC/F,wBAAgB,mBAAmB,CAAC,QAAQ,EAAE,UAAU,GAAG,IAAI,EAAE,IAAI,EAAE,YAAY,GAAG,MAAM,GAAG,IAAI,CAMlG;AAED,6GAA6G;AAC7G,wBAAgB,kBAAkB,CAAC,WAAW,EAAE,YAAY,GAAG,QAAQ,EAAE,QAAQ,EAAE,MAAM,GAAG,SAAS,GAAG,MAAM,CAa7G;AAED;;;;GAIG;AACH,wBAAsB,WAAW,CAAC,MAAM,EAAE,QAAQ,EAAE,MAAM,EAAE,MAAM,GAAG,SAAS,GAAG,OAAO,CAAC,QAAQ,CAAC,CA2BjG"}
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
import { Readable } from 'node:stream';
|
|
2
|
+
import { mediaTypeAllowed } from './upload.js';
|
|
3
|
+
const FILE_RESPONSE = Symbol.for('pipework.fileResponse');
|
|
4
|
+
/**
|
|
5
|
+
* Builds the value a handler returns to answer with a file: `body` (Buffer,
|
|
6
|
+
* Uint8Array or Readable), `mediaType`, and optionally `filename`,
|
|
7
|
+
* `disposition` (`'attachment'` default, or `'inline'`) and `length` (the byte
|
|
8
|
+
* length of a stream, sent as Content-Length; without it a stream is sent
|
|
9
|
+
* chunked).
|
|
10
|
+
*
|
|
11
|
+
* Declare the route with `.file({ mediaTypes })` so OpenAPI describes it. The
|
|
12
|
+
* response carries Content-Type, Content-Length (Buffer, or stream with
|
|
13
|
+
* `length`), Content-Disposition with an ASCII `filename=` fallback and the
|
|
14
|
+
* exact name in `filename*=UTF-8''...`, and `X-Content-Type-Options: nosniff`.
|
|
15
|
+
*
|
|
16
|
+
* The request transaction (auto mode) has committed before the first byte is
|
|
17
|
+
* sent, so a stream must not need it. A stream that fails before its first
|
|
18
|
+
* byte gets a 500; one that fails after destroys the connection and logs the
|
|
19
|
+
* error, so the client sees an aborted download, never a complete-looking
|
|
20
|
+
* short one. Range requests and redirects are not supported.
|
|
21
|
+
*/
|
|
22
|
+
export function file(init) {
|
|
23
|
+
if (typeof init.mediaType !== 'string' || init.mediaType.trim() === '' || /[\r\n]/.test(init.mediaType)) {
|
|
24
|
+
throw new Error('[pipework] http.file() needs a mediaType such as "application/pdf" (one line, not empty).');
|
|
25
|
+
}
|
|
26
|
+
const disposition = init.disposition ?? 'attachment';
|
|
27
|
+
if (disposition !== 'attachment' && disposition !== 'inline') {
|
|
28
|
+
throw new Error(`[pipework] http.file() disposition must be 'attachment' or 'inline', got ${String(disposition)}.`);
|
|
29
|
+
}
|
|
30
|
+
if (init.length !== undefined && (!Number.isInteger(init.length) || init.length < 0)) {
|
|
31
|
+
throw new Error(`[pipework] http.file() length must be a non-negative integer number of bytes, got ${String(init.length)}.`);
|
|
32
|
+
}
|
|
33
|
+
let body;
|
|
34
|
+
if (init.body instanceof Readable)
|
|
35
|
+
body = init.body;
|
|
36
|
+
else if (Buffer.isBuffer(init.body))
|
|
37
|
+
body = init.body;
|
|
38
|
+
else if (init.body instanceof Uint8Array)
|
|
39
|
+
body = Buffer.from(init.body.buffer, init.body.byteOffset, init.body.byteLength);
|
|
40
|
+
else
|
|
41
|
+
throw new Error('[pipework] http.file() body must be a Buffer, Uint8Array or Readable stream.');
|
|
42
|
+
return Object.freeze({
|
|
43
|
+
[FILE_RESPONSE]: true,
|
|
44
|
+
body,
|
|
45
|
+
mediaType: init.mediaType.trim(),
|
|
46
|
+
filename: init.filename,
|
|
47
|
+
disposition,
|
|
48
|
+
length: init.length,
|
|
49
|
+
});
|
|
50
|
+
}
|
|
51
|
+
/** True for a value made by `http.file()`. */
|
|
52
|
+
export function isFileResponse(value) {
|
|
53
|
+
return value !== null && typeof value === 'object' && value[FILE_RESPONSE] === true;
|
|
54
|
+
}
|
|
55
|
+
/** The reason a file response cannot be sent for a route declared with `declared`, or null. */
|
|
56
|
+
export function fileResponseProblem(declared, file) {
|
|
57
|
+
if (declared === null)
|
|
58
|
+
return null;
|
|
59
|
+
if (!mediaTypeAllowed(declared.mediaTypes, file.mediaType.split(';')[0].trim().toLowerCase())) {
|
|
60
|
+
return `The file's mediaType ${file.mediaType} is not in the route's .file() list (${declared.mediaTypes.join(', ')}).`;
|
|
61
|
+
}
|
|
62
|
+
return null;
|
|
63
|
+
}
|
|
64
|
+
/** Content-Disposition value per RFC 6266 / RFC 5987: ASCII fallback plus the exact name percent-encoded. */
|
|
65
|
+
export function contentDisposition(disposition, filename) {
|
|
66
|
+
if (filename === undefined || filename === '')
|
|
67
|
+
return disposition;
|
|
68
|
+
const wellFormed = filename.replace(/[\uD800-\uDBFF](?![\uDC00-\uDFFF])|(?<![\uD800-\uDBFF])[\uDC00-\uDFFF]/g, '\uFFFD');
|
|
69
|
+
let fallback = '';
|
|
70
|
+
for (const ch of wellFormed) {
|
|
71
|
+
const code = ch.codePointAt(0);
|
|
72
|
+
if (code > 0x7e)
|
|
73
|
+
fallback += '_';
|
|
74
|
+
else if (code < 0x20 || code === 0x7f || ch === '"' || ch === '\\' || ch === '/')
|
|
75
|
+
continue;
|
|
76
|
+
else
|
|
77
|
+
fallback += ch;
|
|
78
|
+
}
|
|
79
|
+
if (fallback.trim() === '')
|
|
80
|
+
fallback = 'file';
|
|
81
|
+
const encoded = encodeURIComponent(wellFormed).replace(/['()*]/g, (c) => `%${c.charCodeAt(0).toString(16).toUpperCase()}`);
|
|
82
|
+
return `${disposition}; filename="${fallback}"; filename*=UTF-8''${encoded}`;
|
|
83
|
+
}
|
|
84
|
+
/**
|
|
85
|
+
* Reads a stream's first chunk before anything is sent, so a stream that fails
|
|
86
|
+
* at once can still become a normal error response. Returns the stream to send
|
|
87
|
+
* (the same bytes, replayed), which fails after `length` mismatches or on error.
|
|
88
|
+
*/
|
|
89
|
+
export async function primeStream(source, length) {
|
|
90
|
+
const iterator = source[Symbol.asyncIterator]();
|
|
91
|
+
let first;
|
|
92
|
+
try {
|
|
93
|
+
first = await iterator.next();
|
|
94
|
+
}
|
|
95
|
+
catch (error) {
|
|
96
|
+
source.destroy();
|
|
97
|
+
throw error;
|
|
98
|
+
}
|
|
99
|
+
async function* replay() {
|
|
100
|
+
let sent = 0;
|
|
101
|
+
try {
|
|
102
|
+
let step = first;
|
|
103
|
+
while (step.done !== true) {
|
|
104
|
+
const chunk = typeof step.value === 'string' ? Buffer.from(step.value) : step.value;
|
|
105
|
+
sent += chunk.length;
|
|
106
|
+
yield chunk;
|
|
107
|
+
step = await iterator.next();
|
|
108
|
+
}
|
|
109
|
+
if (length !== undefined && sent !== length) {
|
|
110
|
+
throw new Error(`[pipework] The file stream ended after ${sent} bytes but http.file() declared length ${length}.`);
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
finally {
|
|
114
|
+
if (!source.destroyed)
|
|
115
|
+
source.destroy();
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
return Readable.from(replay(), { objectMode: false });
|
|
119
|
+
}
|
|
120
|
+
//# sourceMappingURL=file.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"file.js","sourceRoot":"","sources":["../../../src/request/http/file.ts"],"names":[],"mappings":"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"}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
export { createServer, type PipeworkServer } from './server.js';
|
|
2
2
|
export type { ServerConfig, RouteRegistration, ErrorResponse, HandlerResponse, InjectOptions, InjectResponse, HttpRequest, HttpResponse, CorsConfig, SecurityConfig, RateLimitConfig } from './types.js';
|
|
3
|
+
export type { FileResponse, FileResponseInit } from './file.js';
|
|
3
4
|
export { buildErrorResponse, buildInternalErrorResponse } from './response.js';
|
|
4
5
|
//# sourceMappingURL=index.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/request/http/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,KAAK,cAAc,EAAE,MAAM,aAAa,CAAA;AAC/D,YAAY,EAAE,YAAY,EAAE,iBAAiB,EAAE,aAAa,EAAE,eAAe,EAAE,aAAa,EAAE,cAAc,EAAE,WAAW,EAAE,YAAY,EAAE,UAAU,EAAE,cAAc,EAAE,eAAe,EAAE,MAAM,YAAY,CAAA;AACxM,OAAO,EAAE,kBAAkB,EAAE,0BAA0B,EAAE,MAAM,eAAe,CAAA"}
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/request/http/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,KAAK,cAAc,EAAE,MAAM,aAAa,CAAA;AAC/D,YAAY,EAAE,YAAY,EAAE,iBAAiB,EAAE,aAAa,EAAE,eAAe,EAAE,aAAa,EAAE,cAAc,EAAE,WAAW,EAAE,YAAY,EAAE,UAAU,EAAE,cAAc,EAAE,eAAe,EAAE,MAAM,YAAY,CAAA;AACxM,YAAY,EAAE,YAAY,EAAE,gBAAgB,EAAE,MAAM,WAAW,CAAA;AAC/D,OAAO,EAAE,kBAAkB,EAAE,0BAA0B,EAAE,MAAM,eAAe,CAAA"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/request/http/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAuB,MAAM,aAAa,CAAA;
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/request/http/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAuB,MAAM,aAAa,CAAA;AAG/D,OAAO,EAAE,kBAAkB,EAAE,0BAA0B,EAAE,MAAM,eAAe,CAAA"}
|
|
@@ -1,3 +1,6 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
1
|
+
import { file } from './file.js';
|
|
2
|
+
/** HTTP namespace: `http.file()` builds a file response; the server itself is managed by manifold.start(). */
|
|
3
|
+
export declare const http: {
|
|
4
|
+
readonly file: typeof file;
|
|
5
|
+
};
|
|
3
6
|
//# sourceMappingURL=namespace.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"namespace.d.ts","sourceRoot":"","sources":["../../../src/request/http/namespace.ts"],"names":[],"mappings":"AAAA,
|
|
1
|
+
{"version":3,"file":"namespace.d.ts","sourceRoot":"","sources":["../../../src/request/http/namespace.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAA;AAEhC,8GAA8G;AAC9G,eAAO,MAAM,IAAI;;CAAoB,CAAA"}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
1
|
+
import { file } from './file.js';
|
|
2
|
+
/** HTTP namespace: `http.file()` builds a file response; the server itself is managed by manifold.start(). */
|
|
3
|
+
export const http = { file };
|
|
3
4
|
//# sourceMappingURL=namespace.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"namespace.js","sourceRoot":"","sources":["../../../src/request/http/namespace.ts"],"names":[],"mappings":"AAAA,
|
|
1
|
+
{"version":3,"file":"namespace.js","sourceRoot":"","sources":["../../../src/request/http/namespace.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAA;AAEhC,8GAA8G;AAC9G,MAAM,CAAC,MAAM,IAAI,GAAG,EAAE,IAAI,EAAW,CAAA"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../../../src/request/http/server.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../../../src/request/http/server.ts"],"names":[],"mappings":"AAOA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,wBAAwB,CAAA;AACtD,OAAO,KAAK,EAAE,OAAO,EAAE,UAAU,EAAE,MAAM,gBAAgB,CAAA;AACzD,OAAO,KAAK,EAAE,YAAY,EAAsC,aAAa,EAAE,cAAc,EAAE,MAAM,YAAY,CAAA;AAsBjH,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,qBAAqB,CAAA;AAGnD,qGAAqG;AACrG,MAAM,WAAW,cAAc;IAC7B,KAAK,CAAC,MAAM,EAAE,UAAU,EAAE,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAAE,OAAO,CAAC,GAAG,IAAI,CAAA;IACjG,gBAAgB,CAAC,QAAQ,EAAE,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAAE,OAAO,CAAC,EAAE,GAAG,IAAI,CAAA;IAC7E,gBAAgB,CAAC,QAAQ,EAAE,QAAQ,CAAC,OAAO,CAAC,GAAG,IAAI,CAAA;IACnD,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC,CAAA;IACtB,MAAM,CAAC,IAAI,EAAE,aAAa,GAAG,OAAO,CAAC,cAAc,CAAC,CAAA;IACpD,MAAM,IAAI,OAAO,CAAC,MAAM,CAAC,CAAA;IACzB,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC,CAAA;CACvB;AAED,wHAAwH;AACxH,wBAAgB,YAAY,CAAC,QAAQ,EAAE,QAAQ,EAAE,MAAM,GAAE,YAAiB,GAAG,cAAc,CA+X1F"}
|
|
@@ -4,6 +4,7 @@ import Fastify, {} from 'fastify';
|
|
|
4
4
|
import fastifyCors from '@fastify/cors';
|
|
5
5
|
import fastifyHelmet from '@fastify/helmet';
|
|
6
6
|
import fastifyRateLimit from '@fastify/rate-limit';
|
|
7
|
+
import fastifyMultipart from '@fastify/multipart';
|
|
7
8
|
import { runInContext } from '../context/store.js';
|
|
8
9
|
import { createRequestContext } from '../context/index.js';
|
|
9
10
|
import { executeInTransaction } from '../context/run-in-transaction.js';
|
|
@@ -12,6 +13,8 @@ import { buildContext } from './middleware.js';
|
|
|
12
13
|
import { closeTrace } from '../../trace/entry/close-trace.js';
|
|
13
14
|
import { buildErrorResponse, buildInternalErrorResponse } from './response.js';
|
|
14
15
|
import { isPipeworkError } from '../../core/errors/index.js';
|
|
16
|
+
import { checkUploadHeaders, uploadBodyLimit, binaryUpload, multipartUpload, settleUpload, MULTIPART_FIELD_LIMIT } from './upload.js';
|
|
17
|
+
import { isFileResponse, fileResponseProblem, contentDisposition, primeStream } from './file.js';
|
|
15
18
|
import { toJsonSchema } from '../../data/schema/json-schema.js';
|
|
16
19
|
import { generateOpenAPI } from '../openapi/generate.js';
|
|
17
20
|
import { getBaseLogger, setLoggingConfig } from '../../core/logging/index.js';
|
|
@@ -126,7 +129,11 @@ export function createServer(instance, config = {}) {
|
|
|
126
129
|
schema['params'] = toJsonSchema(handler.meta.paramsSchema);
|
|
127
130
|
}
|
|
128
131
|
const opts = Object.keys(schema).length > 0 ? { schema } : {};
|
|
129
|
-
|
|
132
|
+
const upload = handler.meta.upload ?? null;
|
|
133
|
+
if (upload !== null) {
|
|
134
|
+
opts['bodyLimit'] = uploadBodyLimit(upload);
|
|
135
|
+
}
|
|
136
|
+
else if (handler.meta.route?.bodyLimit !== undefined) {
|
|
130
137
|
opts['bodyLimit'] = handler.meta.route.bodyLimit;
|
|
131
138
|
}
|
|
132
139
|
const screen = handler.meta.route?.screen;
|
|
@@ -169,6 +176,19 @@ export function createServer(instance, config = {}) {
|
|
|
169
176
|
return undefined;
|
|
170
177
|
};
|
|
171
178
|
}
|
|
179
|
+
if (upload !== null) {
|
|
180
|
+
// After the screen: a refusal from headers alone (415, or 413 on a declared
|
|
181
|
+
// length) before anything is read.
|
|
182
|
+
const headerCheck = async (req, reply) => {
|
|
183
|
+
const refusal = checkUploadHeaders(upload, req.headers);
|
|
184
|
+
if (refusal === null)
|
|
185
|
+
return undefined;
|
|
186
|
+
void reply.header('connection', 'close');
|
|
187
|
+
return reply.status(refusal.httpStatus).send(buildErrorResponse(refusal));
|
|
188
|
+
};
|
|
189
|
+
const existing = opts['onRequest'];
|
|
190
|
+
opts['onRequest'] = existing === undefined ? [headerCheck] : [existing, headerCheck];
|
|
191
|
+
}
|
|
172
192
|
if (handler.meta.wantsRawBody) {
|
|
173
193
|
opts['preParsing'] = async (req, _reply, payload) => {
|
|
174
194
|
const chunks = [];
|
|
@@ -179,8 +199,14 @@ export function createServer(instance, config = {}) {
|
|
|
179
199
|
return Readable.from(raw);
|
|
180
200
|
};
|
|
181
201
|
}
|
|
182
|
-
|
|
202
|
+
const routeHandler = async (req, reply) => {
|
|
183
203
|
let ctx;
|
|
204
|
+
let uploaded;
|
|
205
|
+
// Ends the upload stream and closes the connection if the body was left unread.
|
|
206
|
+
const finishUpload = (failed) => {
|
|
207
|
+
if (settleUpload(uploaded?.stream) || (failed && upload !== null))
|
|
208
|
+
void reply.header('connection', 'close');
|
|
209
|
+
};
|
|
184
210
|
try {
|
|
185
211
|
ctx = await buildContext(req, contextOptions, handler.meta.authRequirement);
|
|
186
212
|
if (handler.meta.permission && rbacInstance) {
|
|
@@ -197,6 +223,11 @@ export function createServer(instance, config = {}) {
|
|
|
197
223
|
if (rawBodyBuffer !== undefined) {
|
|
198
224
|
requestObj.rawBody = rawBodyBuffer;
|
|
199
225
|
}
|
|
226
|
+
if (upload !== null) {
|
|
227
|
+
uploaded = upload.kind === 'binary'
|
|
228
|
+
? binaryUpload(req, upload.maxBytes)
|
|
229
|
+
: await multipartUpload(req, upload);
|
|
230
|
+
}
|
|
200
231
|
const httpOptions = {
|
|
201
232
|
request: requestObj,
|
|
202
233
|
response: {
|
|
@@ -204,6 +235,7 @@ export function createServer(instance, config = {}) {
|
|
|
204
235
|
onFinish: (cb) => watchResponse(reply, cb, req.log),
|
|
205
236
|
},
|
|
206
237
|
...(rawBodyBuffer !== undefined ? { rawBody: rawBodyBuffer } : {}),
|
|
238
|
+
...(uploaded !== undefined ? { upload: uploaded } : {}),
|
|
207
239
|
};
|
|
208
240
|
let result;
|
|
209
241
|
if (handler.meta.jobTransaction === 'manual') {
|
|
@@ -222,22 +254,54 @@ export function createServer(instance, config = {}) {
|
|
|
222
254
|
else {
|
|
223
255
|
result = await runInContext(ctx, () => resolveAndExecute(handler, req.body, req.query, req.params, httpOptions));
|
|
224
256
|
}
|
|
257
|
+
finishUpload(false);
|
|
225
258
|
void closeTrace(instance, ctx.trace, 'closed');
|
|
226
|
-
return sendResult(reply, result);
|
|
259
|
+
return await sendResult(reply, req, result, handler.meta.file ?? null, `${method} ${path}`);
|
|
227
260
|
}
|
|
228
261
|
catch (error) {
|
|
262
|
+
finishUpload(true);
|
|
229
263
|
if (ctx !== undefined)
|
|
230
264
|
void closeTrace(instance, ctx.trace, 'error');
|
|
265
|
+
if (error instanceof FileResponseError) {
|
|
266
|
+
req.log.error({ route: `${method} ${path}`, requestId: String(req.id), err: error }, 'File response refused');
|
|
267
|
+
return reply.status(500).send({ error: { code: 'FILE_RESPONSE_INVALID', message: error.message } });
|
|
268
|
+
}
|
|
231
269
|
if (isPipeworkError(error)) {
|
|
232
270
|
const retryAfter = error.retryAfterSeconds;
|
|
233
271
|
if (typeof retryAfter === 'number')
|
|
234
272
|
void reply.header('Retry-After', String(Math.max(0, Math.ceil(retryAfter))));
|
|
235
273
|
return reply.status(error.httpStatus).send(buildErrorResponse(error));
|
|
236
274
|
}
|
|
275
|
+
// A malformed multipart body surfaces as a Fastify client error (4xx).
|
|
276
|
+
const clientStatus = upload !== null ? error?.statusCode : undefined;
|
|
277
|
+
if (typeof clientStatus === 'number' && clientStatus >= 400 && clientStatus < 500) {
|
|
278
|
+
const e = error;
|
|
279
|
+
return reply.status(clientStatus).send({ error: { code: String(e.code ?? 'BAD_REQUEST'), message: String(e.message ?? 'Bad request') } });
|
|
280
|
+
}
|
|
237
281
|
req.log.error(error, 'Unhandled error in route handler');
|
|
238
282
|
return reply.status(500).send(buildInternalErrorResponse(error, config.exposeErrorDetails));
|
|
239
283
|
}
|
|
240
|
-
}
|
|
284
|
+
};
|
|
285
|
+
if (upload === null) {
|
|
286
|
+
app[fastifyMethod](path, opts, routeHandler);
|
|
287
|
+
}
|
|
288
|
+
else {
|
|
289
|
+
// Its own plugin scope: the body parsers here replace the JSON parser for
|
|
290
|
+
// this route only, so no other route's parsing changes.
|
|
291
|
+
void app.register(async (child) => {
|
|
292
|
+
child.removeAllContentTypeParsers();
|
|
293
|
+
if (upload.kind === 'binary') {
|
|
294
|
+
child.addContentTypeParser('*', (_req, payload, done) => { done(null, payload); });
|
|
295
|
+
}
|
|
296
|
+
else {
|
|
297
|
+
await child.register(fastifyMultipart, {
|
|
298
|
+
throwFileSizeLimit: false,
|
|
299
|
+
limits: { fileSize: upload.maxBytes + 1024, fieldSize: MULTIPART_FIELD_LIMIT, files: 1, fields: 100 },
|
|
300
|
+
});
|
|
301
|
+
}
|
|
302
|
+
child[fastifyMethod](path, opts, routeHandler);
|
|
303
|
+
});
|
|
304
|
+
}
|
|
241
305
|
}
|
|
242
306
|
function registerHandlers(handlers) {
|
|
243
307
|
for (const handler of handlers) {
|
|
@@ -283,7 +347,7 @@ export function createServer(instance, config = {}) {
|
|
|
283
347
|
...(opts.remoteAddress !== undefined ? { remoteAddress: opts.remoteAddress } : {}),
|
|
284
348
|
});
|
|
285
349
|
const r = response;
|
|
286
|
-
return { statusCode: r.statusCode, headers: r.headers, body: r.body, json: () => r.json() };
|
|
350
|
+
return { statusCode: r.statusCode, headers: r.headers, body: r.body, rawPayload: r.rawPayload, json: () => r.json() };
|
|
287
351
|
}
|
|
288
352
|
return { route, registerHandlers, registerResource: registerResourceFn, ready, inject, listen, close };
|
|
289
353
|
}
|
|
@@ -339,7 +403,48 @@ function watchResponse(reply, cb, log) {
|
|
|
339
403
|
raw.once('finish', onFinish);
|
|
340
404
|
raw.once('close', onClose);
|
|
341
405
|
}
|
|
342
|
-
|
|
406
|
+
/** A file response the route cannot send; becomes a 500 naming the reason. */
|
|
407
|
+
class FileResponseError extends Error {
|
|
408
|
+
}
|
|
409
|
+
async function sendFile(reply, req, file, declared, route) {
|
|
410
|
+
const problem = fileResponseProblem(declared, file);
|
|
411
|
+
if (problem !== null) {
|
|
412
|
+
if (!Buffer.isBuffer(file.body))
|
|
413
|
+
file.body.destroy();
|
|
414
|
+
throw new FileResponseError(`[pipework] ${route}: ${problem}`);
|
|
415
|
+
}
|
|
416
|
+
let body;
|
|
417
|
+
let length;
|
|
418
|
+
if (Buffer.isBuffer(file.body)) {
|
|
419
|
+
body = file.body;
|
|
420
|
+
length = file.body.length;
|
|
421
|
+
}
|
|
422
|
+
else {
|
|
423
|
+
body = await primeStream(file.body, file.length);
|
|
424
|
+
length = file.length;
|
|
425
|
+
const raw = reply.raw;
|
|
426
|
+
body.on('error', (err) => {
|
|
427
|
+
// Headers are out: the only honest signal left is a dead connection.
|
|
428
|
+
req.log.error({ route, requestId: String(req.id), err }, 'File stream failed while sending');
|
|
429
|
+
raw.destroy();
|
|
430
|
+
});
|
|
431
|
+
raw.once('close', () => { if (!body.destroyed)
|
|
432
|
+
body.destroy(); });
|
|
433
|
+
}
|
|
434
|
+
void reply.status(declared?.status ?? 200);
|
|
435
|
+
void reply.header('content-type', file.mediaType);
|
|
436
|
+
void reply.header('content-disposition', contentDisposition(file.disposition, file.filename));
|
|
437
|
+
void reply.header('x-content-type-options', 'nosniff');
|
|
438
|
+
if (length !== undefined)
|
|
439
|
+
void reply.header('content-length', String(length));
|
|
440
|
+
return reply.send(body);
|
|
441
|
+
}
|
|
442
|
+
async function sendResult(reply, req, result, fileConfig, route) {
|
|
443
|
+
if (isFileResponse(result))
|
|
444
|
+
return sendFile(reply, req, result, fileConfig, route);
|
|
445
|
+
if (fileConfig !== null) {
|
|
446
|
+
throw new FileResponseError(`[pipework] ${route} is declared with .file() and must return http.file({ body, mediaType, filename? }); it returned ${result === null ? 'null' : typeof result}.`);
|
|
447
|
+
}
|
|
343
448
|
if (result !== null && typeof result === 'object' && 'status' in result && typeof result['status'] === 'number') {
|
|
344
449
|
const response = result;
|
|
345
450
|
if (response.body !== undefined) {
|