@milaboratories/pframes-rs-serv 1.0.68 → 1.0.69
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/dist/fs-store.cjs +27 -13
- package/dist/fs-store.cjs.map +1 -1
- package/dist/fs-store.d.ts.map +1 -1
- package/dist/fs-store.js +28 -14
- package/dist/fs-store.js.map +1 -1
- package/dist/handler.cjs +15 -9
- package/dist/handler.cjs.map +1 -1
- package/dist/handler.d.ts.map +1 -1
- package/dist/handler.js +15 -9
- package/dist/handler.js.map +1 -1
- package/dist/parquet-server.cjs +33 -30
- package/dist/parquet-server.cjs.map +1 -1
- package/dist/parquet-server.d.ts +4 -3
- package/dist/parquet-server.d.ts.map +1 -1
- package/dist/parquet-server.js +33 -30
- package/dist/parquet-server.js.map +1 -1
- package/dist/serve.cjs +12 -20
- package/dist/serve.cjs.map +1 -1
- package/dist/serve.d.ts +1 -1
- package/dist/serve.d.ts.map +1 -1
- package/dist/serve.js +13 -21
- package/dist/serve.js.map +1 -1
- package/dist/utils/headers.cjs +2 -0
- package/dist/utils/headers.cjs.map +1 -1
- package/dist/utils/headers.d.ts +2 -0
- package/dist/utils/headers.d.ts.map +1 -1
- package/dist/utils/headers.js +2 -0
- package/dist/utils/headers.js.map +1 -1
- package/dist/utils/status.cjs +1 -0
- package/dist/utils/status.cjs.map +1 -1
- package/dist/utils/status.d.ts +1 -0
- package/dist/utils/status.d.ts.map +1 -1
- package/dist/utils/status.js +1 -0
- package/dist/utils/status.js.map +1 -1
- package/package.json +4 -3
- package/src/fs-store.ts +35 -17
- package/src/handler.ts +14 -9
- package/src/parquet-server.ts +52 -36
- package/src/serve.ts +14 -29
- package/src/utils/headers.ts +2 -0
- package/src/utils/status.ts +1 -0
package/dist/utils/status.d.ts
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"status.d.ts","sourceRoot":"","sources":["../../src/utils/status.ts"],"names":[],"mappings":"AAAA,2DAA2D;AAC3D,eAAO,MAAM,UAAU
|
|
1
|
+
{"version":3,"file":"status.d.ts","sourceRoot":"","sources":["../../src/utils/status.ts"],"names":[],"mappings":"AAAA,2DAA2D;AAC3D,eAAO,MAAM,UAAU;;;;;;;;;;;;;;CAcb,CAAC"}
|
package/dist/utils/status.js
CHANGED
|
@@ -7,6 +7,7 @@ const StatusCode = {
|
|
|
7
7
|
Unauthorized: 401, // <https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/401>
|
|
8
8
|
NotFound: 404, // <https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/404>
|
|
9
9
|
MethodNotAllowed: 405, // <https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/405>
|
|
10
|
+
RequestTimeout: 408, // <https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/408>
|
|
10
11
|
Gone: 410, // <https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/410>
|
|
11
12
|
PreconditionFailed: 412, // <https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/412>
|
|
12
13
|
RangeNotSatisfiable: 416, // <https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/416>
|
package/dist/utils/status.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"status.js","sources":["../../src/utils/status.ts"],"sourcesContent":["/** HTTP status codes used in the parquet server handler */\nexport const StatusCode = {\n Ok: 200, // <https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/200>\n PartialContent: 206, // <https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/206>\n NotModified: 304, // <https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/304>\n BadRequest: 400, // <https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/400>\n Unauthorized: 401, // <https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/401>\n NotFound: 404, // <https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/404>\n MethodNotAllowed: 405, // <https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/405>\n Gone: 410, // <https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/410>\n PreconditionFailed: 412, // <https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/412>\n RangeNotSatisfiable: 416, // <https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/416>\n InternalServerError: 500, // <https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/500>\n GatewayTimeout: 504 // <https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/504>\n} as const;\n"],"names":[],"mappings":"AAAA;AACO,MAAM,UAAU,GAAG;IACxB,EAAE,EAAE,GAAG;IACP,cAAc,EAAE,GAAG;IACnB,WAAW,EAAE,GAAG;IAChB,UAAU,EAAE,GAAG;IACf,YAAY,EAAE,GAAG;IACjB,QAAQ,EAAE,GAAG;IACb,gBAAgB,EAAE,GAAG;IACrB,IAAI,EAAE,GAAG;IACT,kBAAkB,EAAE,GAAG;IACvB,mBAAmB,EAAE,GAAG;IACxB,mBAAmB,EAAE,GAAG;IACxB,cAAc,EAAE,GAAG;;;;;"}
|
|
1
|
+
{"version":3,"file":"status.js","sources":["../../src/utils/status.ts"],"sourcesContent":["/** HTTP status codes used in the parquet server handler */\nexport const StatusCode = {\n Ok: 200, // <https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/200>\n PartialContent: 206, // <https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/206>\n NotModified: 304, // <https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/304>\n BadRequest: 400, // <https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/400>\n Unauthorized: 401, // <https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/401>\n NotFound: 404, // <https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/404>\n MethodNotAllowed: 405, // <https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/405>\n RequestTimeout: 408, // <https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/408>\n Gone: 410, // <https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/410>\n PreconditionFailed: 412, // <https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/412>\n RangeNotSatisfiable: 416, // <https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/416>\n InternalServerError: 500, // <https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/500>\n GatewayTimeout: 504 // <https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/504>\n} as const;\n"],"names":[],"mappings":"AAAA;AACO,MAAM,UAAU,GAAG;IACxB,EAAE,EAAE,GAAG;IACP,cAAc,EAAE,GAAG;IACnB,WAAW,EAAE,GAAG;IAChB,UAAU,EAAE,GAAG;IACf,YAAY,EAAE,GAAG;IACjB,QAAQ,EAAE,GAAG;IACb,gBAAgB,EAAE,GAAG;IACrB,cAAc,EAAE,GAAG;IACnB,IAAI,EAAE,GAAG;IACT,kBAAkB,EAAE,GAAG;IACvB,mBAAmB,EAAE,GAAG;IACxB,mBAAmB,EAAE,GAAG;IACxB,cAAc,EAAE,GAAG;;;;;"}
|
package/package.json
CHANGED
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
"proxy",
|
|
9
9
|
"server"
|
|
10
10
|
],
|
|
11
|
-
"version": "1.0.
|
|
11
|
+
"version": "1.0.69",
|
|
12
12
|
"type": "module",
|
|
13
13
|
"types": "./dist/index.d.ts",
|
|
14
14
|
"main": "./dist/index.js",
|
|
@@ -30,11 +30,12 @@
|
|
|
30
30
|
"dependencies": {
|
|
31
31
|
"@milaboratories/helpers": "1.6.22",
|
|
32
32
|
"@milaboratories/pl-model-common": "1.19.14",
|
|
33
|
-
"@milaboratories/pl-model-middle-layer": "1.8.
|
|
33
|
+
"@milaboratories/pl-model-middle-layer": "1.8.21",
|
|
34
34
|
"commander": "^14.0.0",
|
|
35
35
|
"selfsigned": "^3.0.1"
|
|
36
36
|
},
|
|
37
37
|
"devDependencies": {
|
|
38
|
+
"@datadog/pprof": "^5.9.0",
|
|
38
39
|
"@milaboratories/ts-builder": "1.0.5",
|
|
39
40
|
"@milaboratories/ts-configs": "1.0.6",
|
|
40
41
|
"@types/autocannon": "^7.12.7",
|
|
@@ -43,7 +44,7 @@
|
|
|
43
44
|
"autocannon": "^8.0.0",
|
|
44
45
|
"tslib": "^2.8.1",
|
|
45
46
|
"typescript": "^5.9.2",
|
|
46
|
-
"undici": "^7.
|
|
47
|
+
"undici": "^7.15.0",
|
|
47
48
|
"vite": "^7.1.3",
|
|
48
49
|
"vitest": "^3.2.4"
|
|
49
50
|
},
|
package/src/fs-store.ts
CHANGED
|
@@ -3,7 +3,7 @@ import { createReadStream } from 'node:fs';
|
|
|
3
3
|
import { stat, open, type FileHandle } from 'node:fs/promises';
|
|
4
4
|
import { join, resolve } from 'node:path';
|
|
5
5
|
import { PFrameInternal } from '@milaboratories/pl-model-middle-layer';
|
|
6
|
-
import { ensureError } from '@milaboratories/pl-model-common';
|
|
6
|
+
import { ensureError, isAbortError } from '@milaboratories/pl-model-common';
|
|
7
7
|
|
|
8
8
|
/** Object store for serving files from a local directory */
|
|
9
9
|
export class FileSystemStore extends PFrameInternal.ObjectStore {
|
|
@@ -46,19 +46,20 @@ export class FileSystemStore extends PFrameInternal.ObjectStore {
|
|
|
46
46
|
callback: (response: PFrameInternal.ObjectStoreResponse) => Promise<void>;
|
|
47
47
|
}
|
|
48
48
|
): Promise<void> {
|
|
49
|
-
let file: FileHandle;
|
|
49
|
+
let file: FileHandle | undefined;
|
|
50
50
|
try {
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
51
|
+
try {
|
|
52
|
+
const path = join(this.rootDir, filename);
|
|
53
|
+
file = await open(path, 'r');
|
|
54
|
+
} catch (error: unknown) {
|
|
55
|
+
this.logger(
|
|
56
|
+
'error',
|
|
57
|
+
`File system store failed to open file ${filename}: ${ensureError(error)}`
|
|
58
|
+
);
|
|
59
|
+
return await params.callback({ type: 'NotFound' });
|
|
60
|
+
}
|
|
61
|
+
params.signal.throwIfAborted();
|
|
60
62
|
|
|
61
|
-
try {
|
|
62
63
|
let size: number;
|
|
63
64
|
try {
|
|
64
65
|
({ size } = await file.stat());
|
|
@@ -69,6 +70,7 @@ export class FileSystemStore extends PFrameInternal.ObjectStore {
|
|
|
69
70
|
);
|
|
70
71
|
return await params.callback({ type: 'InternalError' });
|
|
71
72
|
}
|
|
73
|
+
params.signal.throwIfAborted();
|
|
72
74
|
|
|
73
75
|
const range = this.translate(size, params.range);
|
|
74
76
|
if (!range) {
|
|
@@ -83,7 +85,6 @@ export class FileSystemStore extends PFrameInternal.ObjectStore {
|
|
|
83
85
|
try {
|
|
84
86
|
data = createReadStream('ignored', {
|
|
85
87
|
fd: file.fd,
|
|
86
|
-
autoClose: false,
|
|
87
88
|
start: range.start,
|
|
88
89
|
end: range.end,
|
|
89
90
|
signal: params.signal
|
|
@@ -100,11 +101,28 @@ export class FileSystemStore extends PFrameInternal.ObjectStore {
|
|
|
100
101
|
return await params.callback({ type: 'InternalError' });
|
|
101
102
|
}
|
|
102
103
|
|
|
103
|
-
|
|
104
|
-
.callback({ type: 'Ok', size, range, data })
|
|
105
|
-
|
|
104
|
+
try {
|
|
105
|
+
return await params.callback({ type: 'Ok', size, range, data });
|
|
106
|
+
} catch (error: unknown) {
|
|
107
|
+
if (!isAbortError(error)) {
|
|
108
|
+
this.logger(
|
|
109
|
+
'error',
|
|
110
|
+
`File system store received unexpected rejection from callback: ${ensureError(error)}`
|
|
111
|
+
);
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
} catch (error: unknown) {
|
|
115
|
+
if (!isAbortError(error)) {
|
|
116
|
+
this.logger(
|
|
117
|
+
'error',
|
|
118
|
+
`File system store unhandled error: ${ensureError(error)}`
|
|
119
|
+
);
|
|
120
|
+
}
|
|
121
|
+
return await params.callback({ type: 'InternalError' });
|
|
106
122
|
} finally {
|
|
107
|
-
await file
|
|
123
|
+
await file?.close().catch(() => {
|
|
124
|
+
// already closed
|
|
125
|
+
});
|
|
108
126
|
}
|
|
109
127
|
}
|
|
110
128
|
}
|
package/src/handler.ts
CHANGED
|
@@ -18,13 +18,14 @@ import {
|
|
|
18
18
|
HeaderName,
|
|
19
19
|
HeaderValue
|
|
20
20
|
} from './utils';
|
|
21
|
+
import { isAbortError } from '@milaboratories/pl-model-common';
|
|
21
22
|
|
|
22
23
|
/** Main request handler for parquet files */
|
|
23
|
-
|
|
24
|
+
function handleRequest(
|
|
24
25
|
request: IncomingMessage,
|
|
25
26
|
response: ServerResponse,
|
|
26
27
|
store: PFrameInternal.ObjectStore
|
|
27
|
-
):
|
|
28
|
+
): void {
|
|
28
29
|
// RFC 9110 section 6.6.1: Date header should be present in all responses
|
|
29
30
|
response.sendDate = true;
|
|
30
31
|
// RFC 9110 section 8.6: Content-Length 0 as default for error responses
|
|
@@ -87,7 +88,11 @@ async function handleRequest(
|
|
|
87
88
|
signal,
|
|
88
89
|
// pipeline automatically destroys the streams if they were not gracefully closed
|
|
89
90
|
callback: async (result) => {
|
|
90
|
-
if (
|
|
91
|
+
if (request.destroyed) {
|
|
92
|
+
// request has timed out, close the connection
|
|
93
|
+
response.setHeader(HeaderName.Connection, HeaderValue.Connection);
|
|
94
|
+
return void response.writeHead(StatusCode.RequestTimeout).end();
|
|
95
|
+
}
|
|
91
96
|
|
|
92
97
|
switch (result.type) {
|
|
93
98
|
case 'InternalError':
|
|
@@ -131,11 +136,11 @@ async function handleRequest(
|
|
|
131
136
|
return void response.end();
|
|
132
137
|
}
|
|
133
138
|
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
}
|
|
139
|
+
try {
|
|
140
|
+
return await pipeline(result.data!, response, { signal });
|
|
141
|
+
} catch (error: unknown) {
|
|
142
|
+
if (!isAbortError(error)) throw error;
|
|
143
|
+
}
|
|
139
144
|
}
|
|
140
145
|
});
|
|
141
146
|
}
|
|
@@ -153,7 +158,7 @@ export function createRequestHandler(
|
|
|
153
158
|
options: PFrameInternal.RequestHandlerOptions
|
|
154
159
|
): RequestListener {
|
|
155
160
|
const { store } = options;
|
|
156
|
-
return (request, response) =>
|
|
161
|
+
return (request, response) => handleRequest(request, response, store);
|
|
157
162
|
}
|
|
158
163
|
|
|
159
164
|
/** Request authorization middleware */
|
package/src/parquet-server.ts
CHANGED
|
@@ -2,23 +2,22 @@ import { type ChildProcess, spawn } from 'node:child_process';
|
|
|
2
2
|
import { createInterface, type Interface } from 'node:readline/promises';
|
|
3
3
|
import { join, dirname } from 'node:path';
|
|
4
4
|
import { fileURLToPath } from 'node:url';
|
|
5
|
-
import { Command } from 'commander';
|
|
6
|
-
import {
|
|
7
|
-
import { createRequestHandler } from './handler';
|
|
8
|
-
import { serve } from './serve';
|
|
5
|
+
import { Command, InvalidArgumentError } from 'commander';
|
|
6
|
+
import { HttpHelpers } from './export';
|
|
9
7
|
import {
|
|
10
8
|
parseJson,
|
|
11
|
-
|
|
12
|
-
|
|
9
|
+
stringifyJson,
|
|
10
|
+
type StringifiedJson
|
|
13
11
|
} from '@milaboratories/pl-model-common';
|
|
14
12
|
import { type PFrameInternal } from '@milaboratories/pl-model-middle-layer';
|
|
15
13
|
|
|
16
14
|
const Options = {
|
|
17
|
-
|
|
18
|
-
NoAuth: '--no-auth'
|
|
15
|
+
NoHttps: '--no-https',
|
|
16
|
+
NoAuth: '--no-auth',
|
|
17
|
+
Port: '--port'
|
|
19
18
|
} as const;
|
|
20
19
|
|
|
21
|
-
type
|
|
20
|
+
type Info = StringifiedJson<PFrameInternal.HttpServerInfo>;
|
|
22
21
|
|
|
23
22
|
/**
|
|
24
23
|
* Serves parquet files from the given root directory.
|
|
@@ -31,17 +30,36 @@ export async function runParquetServer(): Promise<void> {
|
|
|
31
30
|
.name('parquet-server')
|
|
32
31
|
.description('Serve parquet files from a directory over HTTP(S)')
|
|
33
32
|
.argument('<root-directory>', 'Root directory containing parquet files')
|
|
34
|
-
.option(Options.
|
|
35
|
-
.option(Options.NoAuth, 'Disable
|
|
33
|
+
.option(Options.NoHttps, 'Downgrade HTTPS to HTTP')
|
|
34
|
+
.option(Options.NoAuth, 'Disable authorization')
|
|
35
|
+
.option(
|
|
36
|
+
`${Options.Port} <number>`,
|
|
37
|
+
'Port to listen on',
|
|
38
|
+
(value) => {
|
|
39
|
+
const port = parseInt(value, 10);
|
|
40
|
+
if (isNaN(port) || port < 0 || port > 65535) {
|
|
41
|
+
throw new InvalidArgumentError('valid port numbers are 0-65535');
|
|
42
|
+
}
|
|
43
|
+
return port;
|
|
44
|
+
},
|
|
45
|
+
0
|
|
46
|
+
)
|
|
36
47
|
.action(
|
|
37
|
-
async (
|
|
48
|
+
async (
|
|
49
|
+
rootDir: string,
|
|
50
|
+
options: {
|
|
51
|
+
https: boolean;
|
|
52
|
+
auth: boolean;
|
|
53
|
+
port: number;
|
|
54
|
+
}
|
|
55
|
+
) => {
|
|
38
56
|
const abortController = new AbortController();
|
|
39
57
|
process
|
|
40
58
|
.on('SIGINT', () => abortController.abort())
|
|
41
59
|
.on('SIGTERM', () => abortController.abort());
|
|
42
60
|
abortController.signal.throwIfAborted();
|
|
43
61
|
|
|
44
|
-
const store = await
|
|
62
|
+
const store = await HttpHelpers.createFsStore({
|
|
45
63
|
rootDir,
|
|
46
64
|
logger: (level, message) => {
|
|
47
65
|
const timestamp = new Date(Date.now()).toISOString();
|
|
@@ -49,22 +67,19 @@ export async function runParquetServer(): Promise<void> {
|
|
|
49
67
|
}
|
|
50
68
|
});
|
|
51
69
|
abortController.signal.throwIfAborted();
|
|
52
|
-
const handler = createRequestHandler({ store });
|
|
70
|
+
const handler = HttpHelpers.createRequestHandler({ store });
|
|
53
71
|
|
|
54
|
-
const server = await
|
|
72
|
+
const server = await HttpHelpers.createHttpServer({
|
|
55
73
|
handler,
|
|
56
|
-
...(options.
|
|
57
|
-
...(!options.auth && { noAuth: true })
|
|
74
|
+
...(!options.https && { noHttps: true }),
|
|
75
|
+
...(!options.auth && { noAuth: true }),
|
|
76
|
+
port: options.port
|
|
58
77
|
});
|
|
59
78
|
abortController.signal.onabort = () => server.stop();
|
|
60
79
|
abortController.signal.throwIfAborted();
|
|
61
80
|
|
|
62
|
-
const
|
|
63
|
-
|
|
64
|
-
...(server.authToken && { authToken: server.authToken }),
|
|
65
|
-
...(server.encodedCaCert && { caCert: server.encodedCaCert })
|
|
66
|
-
});
|
|
67
|
-
console.log(serverConfig);
|
|
81
|
+
const serverInfo: Info = stringifyJson(server.info);
|
|
82
|
+
console.log(serverInfo);
|
|
68
83
|
|
|
69
84
|
await server.stopped;
|
|
70
85
|
}
|
|
@@ -81,32 +96,32 @@ export async function runParquetServer(): Promise<void> {
|
|
|
81
96
|
*/
|
|
82
97
|
export class ParquetServer implements Disposable {
|
|
83
98
|
readonly #process: ChildProcess;
|
|
84
|
-
readonly #
|
|
99
|
+
readonly #info: PFrameInternal.HttpServerInfo;
|
|
85
100
|
readonly #lineReader: Interface;
|
|
86
101
|
|
|
87
102
|
private constructor(
|
|
88
103
|
process: ChildProcess,
|
|
89
|
-
|
|
104
|
+
info: PFrameInternal.HttpServerInfo,
|
|
90
105
|
lineReader: Interface
|
|
91
106
|
) {
|
|
92
107
|
this.#process = process;
|
|
93
|
-
this.#
|
|
108
|
+
this.#info = info;
|
|
94
109
|
this.#lineReader = lineReader;
|
|
95
110
|
}
|
|
96
111
|
|
|
97
|
-
get
|
|
98
|
-
return this.#
|
|
112
|
+
get info(): PFrameInternal.HttpServerInfo {
|
|
113
|
+
return this.#info;
|
|
99
114
|
}
|
|
100
115
|
|
|
101
116
|
static async serve(
|
|
102
117
|
rootDir: string,
|
|
103
118
|
options?: {
|
|
104
|
-
|
|
105
|
-
noAuth?:
|
|
119
|
+
noHttps?: true;
|
|
120
|
+
noAuth?: true;
|
|
121
|
+
port?: number;
|
|
106
122
|
}
|
|
107
123
|
): Promise<ParquetServer> {
|
|
108
|
-
const
|
|
109
|
-
const nodeDirname = dirname(fileURLToPath(nodeFileUrl));
|
|
124
|
+
const nodeDirname = dirname(fileURLToPath(import.meta.url));
|
|
110
125
|
const binPath = join(nodeDirname, '..', 'bin', 'parquet-server.mjs');
|
|
111
126
|
|
|
112
127
|
const serverProcess = spawn(
|
|
@@ -114,8 +129,9 @@ export class ParquetServer implements Disposable {
|
|
|
114
129
|
[
|
|
115
130
|
binPath,
|
|
116
131
|
rootDir,
|
|
117
|
-
...(options?.
|
|
118
|
-
...(options?.noAuth ? [Options.NoAuth] : [])
|
|
132
|
+
...(options?.noHttps ? [Options.NoHttps] : []),
|
|
133
|
+
...(options?.noAuth ? [Options.NoAuth] : []),
|
|
134
|
+
...(options?.port ? [Options.Port, options.port.toString()] : [])
|
|
119
135
|
],
|
|
120
136
|
{
|
|
121
137
|
stdio: ['ignore', 'pipe', 'ignore']
|
|
@@ -125,11 +141,11 @@ export class ParquetServer implements Disposable {
|
|
|
125
141
|
const lineReader = createInterface({ input: serverProcess.stdout! });
|
|
126
142
|
|
|
127
143
|
const firstLine = await lineReader[Symbol.asyncIterator]().next();
|
|
128
|
-
const
|
|
144
|
+
const serverInfo = parseJson(firstLine.value as Info);
|
|
129
145
|
|
|
130
146
|
lineReader.on('line', console.log);
|
|
131
147
|
|
|
132
|
-
return new ParquetServer(serverProcess,
|
|
148
|
+
return new ParquetServer(serverProcess, serverInfo, lineReader);
|
|
133
149
|
}
|
|
134
150
|
|
|
135
151
|
[Symbol.dispose](): void {
|
package/src/serve.ts
CHANGED
|
@@ -14,8 +14,7 @@ import type { PFrameInternal } from '@milaboratories/pl-model-middle-layer';
|
|
|
14
14
|
import {
|
|
15
15
|
base64Encode,
|
|
16
16
|
Base64Encoded,
|
|
17
|
-
ensureError
|
|
18
|
-
PFrameError
|
|
17
|
+
ensureError
|
|
19
18
|
} from '@milaboratories/pl-model-common';
|
|
20
19
|
import { generate, type GenerateResult } from 'selfsigned';
|
|
21
20
|
import { randomUUID } from 'node:crypto';
|
|
@@ -54,18 +53,16 @@ async function generateCertificate(): Promise<GenerateResult> {
|
|
|
54
53
|
/** Create an object store URL from the server address info. */
|
|
55
54
|
function createObjectStoreUrl(
|
|
56
55
|
info: AddressInfo,
|
|
57
|
-
|
|
56
|
+
noHttps?: true
|
|
58
57
|
): PFrameInternal.ObjectStoreUrl {
|
|
59
|
-
const protocol =
|
|
58
|
+
const protocol = noHttps ? 'http' : 'https';
|
|
60
59
|
switch (info.family) {
|
|
61
60
|
case 'IPv4':
|
|
62
61
|
return `${protocol}://${info.address}:${info.port}/` as PFrameInternal.ObjectStoreUrl;
|
|
63
62
|
case 'IPv6':
|
|
64
63
|
return `${protocol}://[${info.address}]:${info.port}/` as PFrameInternal.ObjectStoreUrl;
|
|
65
64
|
default:
|
|
66
|
-
|
|
67
|
-
`PFrame helper HTTP(S) server bound to 'localhost' has unknown address family: ${info.family}`
|
|
68
|
-
);
|
|
65
|
+
return `${protocol}://localhost:${info.port}/` as PFrameInternal.ObjectStoreUrl;
|
|
69
66
|
}
|
|
70
67
|
}
|
|
71
68
|
|
|
@@ -76,7 +73,7 @@ function createObjectStoreUrl(
|
|
|
76
73
|
export async function serve({
|
|
77
74
|
handler,
|
|
78
75
|
port = 0,
|
|
79
|
-
|
|
76
|
+
noHttps,
|
|
80
77
|
noAuth
|
|
81
78
|
}: PFrameInternal.HttpServerOptions): Promise<PFrameInternal.HttpServer> {
|
|
82
79
|
const started = new Deferred<PFrameInternal.HttpServer>();
|
|
@@ -91,52 +88,41 @@ export async function serve({
|
|
|
91
88
|
}
|
|
92
89
|
|
|
93
90
|
// Create HTTP server
|
|
94
|
-
let
|
|
95
|
-
| Base64Encoded<PFrameInternal.PemCertificate>
|
|
96
|
-
| undefined;
|
|
91
|
+
let encodedCaCert: Base64Encoded<PFrameInternal.PemCertificate> | undefined;
|
|
97
92
|
const defaultOptions: ServerOptions = {
|
|
98
93
|
keepAlive: true
|
|
99
94
|
};
|
|
100
95
|
let server: HttpServer | HttpsServer;
|
|
101
96
|
|
|
102
|
-
if (
|
|
97
|
+
if (noHttps) {
|
|
103
98
|
server = createHttpServer(defaultOptions, effectiveHandler);
|
|
104
99
|
} else {
|
|
105
100
|
const { cert, private: key, public: ca } = await generateCertificate();
|
|
106
|
-
|
|
101
|
+
encodedCaCert = base64Encode(cert as PFrameInternal.PemCertificate);
|
|
107
102
|
server = createHttpsServer(
|
|
108
103
|
{ ...defaultOptions, cert, key, ca },
|
|
109
104
|
effectiveHandler
|
|
110
105
|
);
|
|
111
106
|
}
|
|
112
107
|
|
|
113
|
-
const abortController = new AbortController();
|
|
114
108
|
server
|
|
115
109
|
.on('listening', () => {
|
|
116
110
|
// Cast is safe by specification <https://nodejs.org/api/net.html#serveraddress>
|
|
117
|
-
const
|
|
111
|
+
const url = createObjectStoreUrl(
|
|
118
112
|
server.address() as AddressInfo,
|
|
119
|
-
|
|
113
|
+
noHttps
|
|
120
114
|
);
|
|
121
115
|
stopped = new Deferred<void>();
|
|
122
116
|
|
|
123
117
|
started.resolve({
|
|
124
|
-
get
|
|
125
|
-
return
|
|
126
|
-
},
|
|
127
|
-
get authToken(): PFrameInternal.HttpAuthorizationToken | undefined {
|
|
128
|
-
return authToken;
|
|
129
|
-
},
|
|
130
|
-
get encodedCaCert():
|
|
131
|
-
| Base64Encoded<PFrameInternal.PemCertificate>
|
|
132
|
-
| undefined {
|
|
133
|
-
return certificateBase64;
|
|
118
|
+
get info(): PFrameInternal.HttpServerInfo {
|
|
119
|
+
return { url, authToken, encodedCaCert };
|
|
134
120
|
},
|
|
135
121
|
get stopped(): Promise<void> {
|
|
136
122
|
return stopped!.promise;
|
|
137
123
|
},
|
|
138
124
|
stop(): Promise<void> {
|
|
139
|
-
|
|
125
|
+
server.close();
|
|
140
126
|
return stopped!.promise;
|
|
141
127
|
}
|
|
142
128
|
});
|
|
@@ -148,8 +134,7 @@ export async function serve({
|
|
|
148
134
|
.on('close', () => stopped?.resolve())
|
|
149
135
|
.listen({
|
|
150
136
|
host: 'localhost',
|
|
151
|
-
port
|
|
152
|
-
signal: abortController.signal
|
|
137
|
+
port
|
|
153
138
|
});
|
|
154
139
|
} catch (error: unknown) {
|
|
155
140
|
started.reject(ensureError(error));
|
package/src/utils/headers.ts
CHANGED
|
@@ -5,6 +5,7 @@ export const HeaderName = {
|
|
|
5
5
|
Allow: 'allow',
|
|
6
6
|
Authorization: 'authorization',
|
|
7
7
|
CacheControl: 'cache-control',
|
|
8
|
+
Connection: 'connection',
|
|
8
9
|
ContentLength: 'content-length',
|
|
9
10
|
ContentRange: 'content-range',
|
|
10
11
|
ContentType: 'content-type',
|
|
@@ -24,6 +25,7 @@ export const HeaderValue = {
|
|
|
24
25
|
AcceptRanges: 'bytes',
|
|
25
26
|
Allow: 'GET, HEAD',
|
|
26
27
|
CacheControl: 'public, immutable, max-age=31536000',
|
|
28
|
+
Connection: 'close',
|
|
27
29
|
ContentType: 'application/octet-stream',
|
|
28
30
|
WWWAuthenticate: 'Bearer realm="parquet-server"'
|
|
29
31
|
} as const;
|
package/src/utils/status.ts
CHANGED
|
@@ -7,6 +7,7 @@ export const StatusCode = {
|
|
|
7
7
|
Unauthorized: 401, // <https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/401>
|
|
8
8
|
NotFound: 404, // <https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/404>
|
|
9
9
|
MethodNotAllowed: 405, // <https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/405>
|
|
10
|
+
RequestTimeout: 408, // <https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/408>
|
|
10
11
|
Gone: 410, // <https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/410>
|
|
11
12
|
PreconditionFailed: 412, // <https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/412>
|
|
12
13
|
RangeNotSatisfiable: 416, // <https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/416>
|