h3 1.6.5 → 1.7.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/README.md +1 -1
- package/dist/index.cjs +93 -44
- package/dist/index.d.ts +20 -7
- package/dist/index.mjs +93 -45
- package/package.json +22 -22
package/README.md
CHANGED
|
@@ -111,7 +111,7 @@ app.use('/1', eventHandler(() => '<h1>Hello world!</h1>'))
|
|
|
111
111
|
.use('/2', eventHandler(() => '<h1>Goodbye!</h1>'))
|
|
112
112
|
|
|
113
113
|
// We can proxy requests and rewrite cookie's domain and path
|
|
114
|
-
app.use('/api', eventHandler((event) => proxyRequest('https://example.com', {
|
|
114
|
+
app.use('/api', eventHandler((event) => proxyRequest(event, 'https://example.com', {
|
|
115
115
|
// f.e. keep one domain unchanged, rewrite one domain and remove other domains
|
|
116
116
|
cookieDomainRewrite: {
|
|
117
117
|
"example.com": "example.com",
|
package/dist/index.cjs
CHANGED
|
@@ -182,7 +182,7 @@ function createError(input) {
|
|
|
182
182
|
return err;
|
|
183
183
|
}
|
|
184
184
|
function sendError(event, error, debug) {
|
|
185
|
-
if (event.
|
|
185
|
+
if (event.handled) {
|
|
186
186
|
return;
|
|
187
187
|
}
|
|
188
188
|
const h3Error = isError(error) ? error : createError(error);
|
|
@@ -195,7 +195,7 @@ function sendError(event, error, debug) {
|
|
|
195
195
|
if (debug) {
|
|
196
196
|
responseBody.stack = (h3Error.stack || "").split("\n").map((l) => l.trim());
|
|
197
197
|
}
|
|
198
|
-
if (event.
|
|
198
|
+
if (event.handled) {
|
|
199
199
|
return;
|
|
200
200
|
}
|
|
201
201
|
const _code = Number.parseInt(h3Error.statusCode);
|
|
@@ -257,23 +257,31 @@ function getRequestHeader(event, name) {
|
|
|
257
257
|
return value;
|
|
258
258
|
}
|
|
259
259
|
const getHeader = getRequestHeader;
|
|
260
|
-
function getRequestHost(event) {
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
260
|
+
function getRequestHost(event, opts = {}) {
|
|
261
|
+
if (opts.xForwardedHost) {
|
|
262
|
+
const xForwardedHost = event.node.req.headers["x-forwarded-host"];
|
|
263
|
+
if (xForwardedHost) {
|
|
264
|
+
return xForwardedHost;
|
|
265
|
+
}
|
|
264
266
|
}
|
|
265
267
|
return event.node.req.headers.host || "localhost";
|
|
266
268
|
}
|
|
267
|
-
function getRequestProtocol(event) {
|
|
268
|
-
if (event.node.req.headers["x-forwarded-proto"] === "https") {
|
|
269
|
+
function getRequestProtocol(event, opts = {}) {
|
|
270
|
+
if (opts.xForwardedProto !== false && event.node.req.headers["x-forwarded-proto"] === "https") {
|
|
269
271
|
return "https";
|
|
270
272
|
}
|
|
271
273
|
return event.node.req.connection.encrypted ? "https" : "http";
|
|
272
274
|
}
|
|
273
|
-
|
|
274
|
-
|
|
275
|
+
const DOUBLE_SLASH_RE = /[/\\]{2,}/g;
|
|
276
|
+
function getRequestPath(event) {
|
|
277
|
+
const path = (event.node.req.url || "/").replace(DOUBLE_SLASH_RE, "/");
|
|
278
|
+
return path;
|
|
279
|
+
}
|
|
280
|
+
function getRequestURL(event, opts = {}) {
|
|
281
|
+
const host = getRequestHost(event, opts);
|
|
275
282
|
const protocol = getRequestProtocol(event);
|
|
276
|
-
|
|
283
|
+
const path = getRequestPath(event);
|
|
284
|
+
return new URL(path, `${protocol}://${host}`);
|
|
277
285
|
}
|
|
278
286
|
|
|
279
287
|
const RawBodySymbol = Symbol.for("h3RawBody");
|
|
@@ -283,9 +291,15 @@ function readRawBody(event, encoding = "utf8") {
|
|
|
283
291
|
assertMethod(event, PayloadMethods$1);
|
|
284
292
|
const _rawBody = event.node.req[RawBodySymbol] || event.node.req.body;
|
|
285
293
|
if (_rawBody) {
|
|
286
|
-
const promise2 = Promise.resolve(_rawBody).then(
|
|
287
|
-
(
|
|
288
|
-
|
|
294
|
+
const promise2 = Promise.resolve(_rawBody).then((_resolved) => {
|
|
295
|
+
if (Buffer.isBuffer(_resolved)) {
|
|
296
|
+
return _resolved;
|
|
297
|
+
}
|
|
298
|
+
if (_resolved.constructor === Object) {
|
|
299
|
+
return Buffer.from(JSON.stringify(_resolved));
|
|
300
|
+
}
|
|
301
|
+
return Buffer.from(_resolved);
|
|
302
|
+
});
|
|
289
303
|
return encoding ? promise2.then((buff) => buff.toString(encoding)) : promise2;
|
|
290
304
|
}
|
|
291
305
|
if (!Number.parseInt(event.node.req.headers["content-length"] || "")) {
|
|
@@ -370,7 +384,9 @@ function handleCacheHeaders(event, opts) {
|
|
|
370
384
|
event.node.res.setHeader("cache-control", cacheControls.join(", "));
|
|
371
385
|
if (cacheMatched) {
|
|
372
386
|
event.node.res.statusCode = 304;
|
|
373
|
-
event.
|
|
387
|
+
if (!event.handled) {
|
|
388
|
+
event.node.res.end();
|
|
389
|
+
}
|
|
374
390
|
return true;
|
|
375
391
|
}
|
|
376
392
|
return false;
|
|
@@ -519,6 +535,7 @@ async function sendProxy(event, target, opts = {}) {
|
|
|
519
535
|
event.node.res.statusCode
|
|
520
536
|
);
|
|
521
537
|
event.node.res.statusMessage = sanitizeStatusMessage(response.statusText);
|
|
538
|
+
const cookies = [];
|
|
522
539
|
for (const [key, value] of response.headers.entries()) {
|
|
523
540
|
if (key === "content-encoding") {
|
|
524
541
|
continue;
|
|
@@ -527,7 +544,15 @@ async function sendProxy(event, target, opts = {}) {
|
|
|
527
544
|
continue;
|
|
528
545
|
}
|
|
529
546
|
if (key === "set-cookie") {
|
|
530
|
-
|
|
547
|
+
cookies.push(...splitCookiesString(value));
|
|
548
|
+
continue;
|
|
549
|
+
}
|
|
550
|
+
event.node.res.setHeader(key, value);
|
|
551
|
+
}
|
|
552
|
+
if (cookies.length > 0) {
|
|
553
|
+
event.node.res.setHeader(
|
|
554
|
+
"set-cookie",
|
|
555
|
+
cookies.map((cookie) => {
|
|
531
556
|
if (opts.cookieDomainRewrite) {
|
|
532
557
|
cookie = rewriteCookieProperty(
|
|
533
558
|
cookie,
|
|
@@ -543,15 +568,18 @@ async function sendProxy(event, target, opts = {}) {
|
|
|
543
568
|
);
|
|
544
569
|
}
|
|
545
570
|
return cookie;
|
|
546
|
-
})
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
571
|
+
})
|
|
572
|
+
);
|
|
573
|
+
}
|
|
574
|
+
if (opts.onResponse) {
|
|
575
|
+
await opts.onResponse(event, response);
|
|
551
576
|
}
|
|
552
577
|
if (response._data !== void 0) {
|
|
553
578
|
return response._data;
|
|
554
579
|
}
|
|
580
|
+
if (event.handled) {
|
|
581
|
+
return;
|
|
582
|
+
}
|
|
555
583
|
if (opts.sendStream === false) {
|
|
556
584
|
const data = new Uint8Array(await response.arrayBuffer());
|
|
557
585
|
return event.node.res.end(data);
|
|
@@ -610,14 +638,16 @@ function rewriteCookieProperty(header, map, property) {
|
|
|
610
638
|
);
|
|
611
639
|
}
|
|
612
640
|
|
|
613
|
-
const defer = typeof setImmediate
|
|
641
|
+
const defer = typeof setImmediate === "undefined" ? (fn) => fn() : setImmediate;
|
|
614
642
|
function send(event, data, type) {
|
|
615
643
|
if (type) {
|
|
616
644
|
defaultContentType(event, type);
|
|
617
645
|
}
|
|
618
646
|
return new Promise((resolve) => {
|
|
619
647
|
defer(() => {
|
|
620
|
-
event.
|
|
648
|
+
if (!event.handled) {
|
|
649
|
+
event.node.res.end(data);
|
|
650
|
+
}
|
|
621
651
|
resolve();
|
|
622
652
|
});
|
|
623
653
|
});
|
|
@@ -627,7 +657,9 @@ function sendNoContent(event, code = 204) {
|
|
|
627
657
|
if (event.node.res.statusCode === 204) {
|
|
628
658
|
event.node.res.removeHeader("content-length");
|
|
629
659
|
}
|
|
630
|
-
event.
|
|
660
|
+
if (!event.handled) {
|
|
661
|
+
event.node.res.end();
|
|
662
|
+
}
|
|
631
663
|
}
|
|
632
664
|
function setResponseStatus(event, code, text) {
|
|
633
665
|
if (code) {
|
|
@@ -810,7 +842,7 @@ async function getSession(event, config) {
|
|
|
810
842
|
Object.assign(session, unsealed);
|
|
811
843
|
}
|
|
812
844
|
if (!session.id) {
|
|
813
|
-
session.id = (config.crypto || crypto__default).randomUUID();
|
|
845
|
+
session.id = config.generateId?.() ?? (config.crypto || crypto__default).randomUUID();
|
|
814
846
|
session.createdAt = Date.now();
|
|
815
847
|
await updateSession(event, config);
|
|
816
848
|
}
|
|
@@ -1081,11 +1113,15 @@ class H3Response {
|
|
|
1081
1113
|
class H3Event {
|
|
1082
1114
|
constructor(req, res) {
|
|
1083
1115
|
this["__is_event__"] = true;
|
|
1116
|
+
this._handled = false;
|
|
1084
1117
|
this.context = {};
|
|
1085
1118
|
this.node = { req, res };
|
|
1086
1119
|
}
|
|
1087
1120
|
get path() {
|
|
1088
|
-
return this
|
|
1121
|
+
return getRequestPath(this);
|
|
1122
|
+
}
|
|
1123
|
+
get handled() {
|
|
1124
|
+
return this._handled || this.node.res.writableEnded || this.node.res.headersSent;
|
|
1089
1125
|
}
|
|
1090
1126
|
/** @deprecated Please use `event.node.req` instead. **/
|
|
1091
1127
|
get req() {
|
|
@@ -1098,35 +1134,37 @@ class H3Event {
|
|
|
1098
1134
|
// Implementation of FetchEvent
|
|
1099
1135
|
respondWith(r) {
|
|
1100
1136
|
Promise.resolve(r).then((_response) => {
|
|
1101
|
-
if (this.
|
|
1137
|
+
if (this.handled) {
|
|
1102
1138
|
return;
|
|
1103
1139
|
}
|
|
1104
1140
|
const response = _response instanceof H3Response ? _response : new H3Response(_response);
|
|
1105
1141
|
for (const [key, value] of response.headers.entries()) {
|
|
1106
|
-
this.res.setHeader(key, value);
|
|
1142
|
+
this.node.res.setHeader(key, value);
|
|
1107
1143
|
}
|
|
1108
1144
|
if (response.status) {
|
|
1109
|
-
this.res.statusCode = sanitizeStatusCode(
|
|
1145
|
+
this.node.res.statusCode = sanitizeStatusCode(
|
|
1110
1146
|
response.status,
|
|
1111
|
-
this.res.statusCode
|
|
1147
|
+
this.node.res.statusCode
|
|
1112
1148
|
);
|
|
1113
1149
|
}
|
|
1114
1150
|
if (response.statusText) {
|
|
1115
|
-
this.res.statusMessage = sanitizeStatusMessage(
|
|
1151
|
+
this.node.res.statusMessage = sanitizeStatusMessage(
|
|
1152
|
+
response.statusText
|
|
1153
|
+
);
|
|
1116
1154
|
}
|
|
1117
1155
|
if (response.redirected) {
|
|
1118
|
-
this.res.setHeader("location", response.url);
|
|
1156
|
+
this.node.res.setHeader("location", response.url);
|
|
1119
1157
|
}
|
|
1120
1158
|
if (!response._body) {
|
|
1121
|
-
return this.res.end();
|
|
1159
|
+
return this.node.res.end();
|
|
1122
1160
|
}
|
|
1123
1161
|
if (typeof response._body === "string" || "buffer" in response._body || "byteLength" in response._body) {
|
|
1124
|
-
return this.res.end(response._body);
|
|
1162
|
+
return this.node.res.end(response._body);
|
|
1125
1163
|
}
|
|
1126
1164
|
if (!response.headers.has("content-type")) {
|
|
1127
1165
|
response.headers.set("content-type", MIMES.json);
|
|
1128
1166
|
}
|
|
1129
|
-
this.res.end(JSON.stringify(response._body));
|
|
1167
|
+
this.node.res.end(JSON.stringify(response._body));
|
|
1130
1168
|
});
|
|
1131
1169
|
}
|
|
1132
1170
|
}
|
|
@@ -1252,7 +1290,7 @@ function createAppEventHandler(stack, options) {
|
|
|
1252
1290
|
continue;
|
|
1253
1291
|
}
|
|
1254
1292
|
const val = await layer.handler(event);
|
|
1255
|
-
if (event.
|
|
1293
|
+
if (event.handled) {
|
|
1256
1294
|
return;
|
|
1257
1295
|
}
|
|
1258
1296
|
const type = typeof val;
|
|
@@ -1277,10 +1315,10 @@ function createAppEventHandler(stack, options) {
|
|
|
1277
1315
|
}
|
|
1278
1316
|
}
|
|
1279
1317
|
}
|
|
1280
|
-
if (!event.
|
|
1318
|
+
if (!event.handled) {
|
|
1281
1319
|
throw createError({
|
|
1282
1320
|
statusCode: 404,
|
|
1283
|
-
statusMessage: `Cannot find any
|
|
1321
|
+
statusMessage: `Cannot find any path matching ${event.node.req.url || "/"}.`
|
|
1284
1322
|
});
|
|
1285
1323
|
}
|
|
1286
1324
|
});
|
|
@@ -1428,15 +1466,25 @@ function createRouter(opts = {}) {
|
|
|
1428
1466
|
const method = (event.node.req.method || "get").toLowerCase();
|
|
1429
1467
|
const handler = matched.handlers[method] || matched.handlers.all;
|
|
1430
1468
|
if (!handler) {
|
|
1431
|
-
|
|
1432
|
-
|
|
1433
|
-
|
|
1434
|
-
|
|
1435
|
-
|
|
1469
|
+
if (opts.preemptive || opts.preemtive) {
|
|
1470
|
+
throw createError({
|
|
1471
|
+
statusCode: 405,
|
|
1472
|
+
name: "Method Not Allowed",
|
|
1473
|
+
statusMessage: `Method ${method} is not allowed on this route.`
|
|
1474
|
+
});
|
|
1475
|
+
} else {
|
|
1476
|
+
return;
|
|
1477
|
+
}
|
|
1436
1478
|
}
|
|
1437
1479
|
const params = matched.params || {};
|
|
1438
1480
|
event.context.params = params;
|
|
1439
|
-
return handler(event)
|
|
1481
|
+
return Promise.resolve(handler(event)).then((res) => {
|
|
1482
|
+
if (res === void 0 && (opts.preemptive || opts.preemtive)) {
|
|
1483
|
+
setResponseStatus(event, 204);
|
|
1484
|
+
return "";
|
|
1485
|
+
}
|
|
1486
|
+
return res;
|
|
1487
|
+
});
|
|
1440
1488
|
});
|
|
1441
1489
|
return router;
|
|
1442
1490
|
}
|
|
@@ -1479,6 +1527,7 @@ exports.getQuery = getQuery;
|
|
|
1479
1527
|
exports.getRequestHeader = getRequestHeader;
|
|
1480
1528
|
exports.getRequestHeaders = getRequestHeaders;
|
|
1481
1529
|
exports.getRequestHost = getRequestHost;
|
|
1530
|
+
exports.getRequestPath = getRequestPath;
|
|
1482
1531
|
exports.getRequestProtocol = getRequestProtocol;
|
|
1483
1532
|
exports.getRequestURL = getRequestURL;
|
|
1484
1533
|
exports.getResponseHeader = getResponseHeader;
|
package/dist/index.d.ts
CHANGED
|
@@ -24,6 +24,8 @@ interface SessionConfig {
|
|
|
24
24
|
sessionHeader?: false | string;
|
|
25
25
|
seal?: SealOptions;
|
|
26
26
|
crypto?: Crypto;
|
|
27
|
+
/** Default is Crypto.randomUUID */
|
|
28
|
+
generateId?: () => string;
|
|
27
29
|
}
|
|
28
30
|
declare function useSession<T extends SessionDataT = SessionDataT>(event: H3Event, config: SessionConfig): Promise<{
|
|
29
31
|
readonly id: string | undefined;
|
|
@@ -105,10 +107,12 @@ interface NodeEventContext {
|
|
|
105
107
|
}
|
|
106
108
|
declare class H3Event implements Pick<FetchEvent, "respondWith"> {
|
|
107
109
|
"__is_event__": boolean;
|
|
110
|
+
_handled: boolean;
|
|
108
111
|
node: NodeEventContext;
|
|
109
112
|
context: H3EventContext;
|
|
110
113
|
constructor(req: IncomingMessage, res: ServerResponse);
|
|
111
|
-
get path(): string
|
|
114
|
+
get path(): string;
|
|
115
|
+
get handled(): boolean;
|
|
112
116
|
/** @deprecated Please use `event.node.req` instead. **/
|
|
113
117
|
get req(): IncomingMessage;
|
|
114
118
|
/** @deprecated Please use `event.node.res` instead. **/
|
|
@@ -313,6 +317,7 @@ interface ProxyOptions {
|
|
|
313
317
|
sendStream?: boolean;
|
|
314
318
|
cookieDomainRewrite?: string | Record<string, string>;
|
|
315
319
|
cookiePathRewrite?: string | Record<string, string>;
|
|
320
|
+
onResponse?: (event: H3Event, response: Response) => void;
|
|
316
321
|
}
|
|
317
322
|
declare function proxyRequest(event: H3Event, target: string, opts?: ProxyOptions): Promise<any>;
|
|
318
323
|
declare function sendProxy(event: H3Event, target: string, opts?: ProxyOptions): Promise<any>;
|
|
@@ -324,8 +329,8 @@ declare function fetchWithEvent(event: H3Event, req: RequestInfo | URL, init?: R
|
|
|
324
329
|
}): Promise<Response>;
|
|
325
330
|
|
|
326
331
|
declare function getQuery(event: H3Event): ufo.QueryObject;
|
|
327
|
-
declare function getRouterParams(event: H3Event): H3Event["context"]
|
|
328
|
-
declare function getRouterParam(event: H3Event, name: string):
|
|
332
|
+
declare function getRouterParams(event: H3Event): NonNullable<H3Event["context"]["params"]>;
|
|
333
|
+
declare function getRouterParam(event: H3Event, name: string): string | undefined;
|
|
329
334
|
declare function getMethod(event: H3Event, defaultMethod?: HTTPMethod): HTTPMethod;
|
|
330
335
|
declare function isMethod(event: H3Event, expected: HTTPMethod | HTTPMethod[], allowHead?: boolean): boolean;
|
|
331
336
|
declare function assertMethod(event: H3Event, expected: HTTPMethod | HTTPMethod[], allowHead?: boolean): void;
|
|
@@ -333,9 +338,17 @@ declare function getRequestHeaders(event: H3Event): RequestHeaders;
|
|
|
333
338
|
declare const getHeaders: typeof getRequestHeaders;
|
|
334
339
|
declare function getRequestHeader(event: H3Event, name: string): RequestHeaders[string];
|
|
335
340
|
declare const getHeader: typeof getRequestHeader;
|
|
336
|
-
declare function getRequestHost(event: H3Event
|
|
337
|
-
|
|
338
|
-
|
|
341
|
+
declare function getRequestHost(event: H3Event, opts?: {
|
|
342
|
+
xForwardedHost?: boolean;
|
|
343
|
+
}): string;
|
|
344
|
+
declare function getRequestProtocol(event: H3Event, opts?: {
|
|
345
|
+
xForwardedProto?: boolean;
|
|
346
|
+
}): "https" | "http";
|
|
347
|
+
declare function getRequestPath(event: H3Event): string;
|
|
348
|
+
declare function getRequestURL(event: H3Event, opts?: {
|
|
349
|
+
xForwardedHost?: boolean;
|
|
350
|
+
xForwardedProto?: boolean;
|
|
351
|
+
}): URL;
|
|
339
352
|
|
|
340
353
|
declare function send(event: H3Event, data?: any, type?: string): Promise<void>;
|
|
341
354
|
/**
|
|
@@ -402,4 +415,4 @@ interface CreateRouterOptions {
|
|
|
402
415
|
}
|
|
403
416
|
declare function createRouter(opts?: CreateRouterOptions): Router;
|
|
404
417
|
|
|
405
|
-
export { AddRouteShortcuts, App, AppOptions, AppUse, CacheConditions, CreateRouterOptions, DynamicEventHandler, Encoding, EventHandler, EventHandlerResponse, H3CorsOptions, H3Error, H3Event, H3EventContext, H3Headers, H3Response, HTTPMethod, InputLayer, InputStack, Layer, LazyEventHandler, MIMES, Matcher, MultiPartData, NodeEventContext, NodeListener, NodeMiddleware, NodePromisifiedHandler, ProxyOptions, RequestHeaders, Router, RouterMethod, RouterUse, Session, SessionConfig, SessionData, Stack, appendCorsHeaders, appendCorsPreflightHeaders, appendHeader, appendHeaders, appendResponseHeader, appendResponseHeaders, assertMethod, callNodeListener, clearSession, createApp, createAppEventHandler, createError, createEvent, createRouter, defaultContentType, defineEventHandler, defineLazyEventHandler, defineNodeListener, defineNodeMiddleware, deleteCookie, dynamicEventHandler, eventHandler, fetchWithEvent, fromNodeMiddleware, getCookie, getHeader, getHeaders, getMethod, getProxyRequestHeaders, getQuery, getRequestHeader, getRequestHeaders, getRequestHost, getRequestProtocol, getRequestURL, getResponseHeader, getResponseHeaders, getResponseStatus, getResponseStatusText, getRouterParam, getRouterParams, getSession, handleCacheHeaders, handleCors, isCorsOriginAllowed, isError, isEvent, isEventHandler, isMethod, isPreflightRequest, isStream, lazyEventHandler, parseCookies, promisifyNodeListener, proxyRequest, readBody, readMultipartFormData, readRawBody, sanitizeStatusCode, sanitizeStatusMessage, sealSession, send, sendError, sendNoContent, sendProxy, sendRedirect, sendStream, setCookie, setHeader, setHeaders, setResponseHeader, setResponseHeaders, setResponseStatus, splitCookiesString, toEventHandler, toNodeListener, unsealSession, updateSession, use, useBase, useSession, writeEarlyHints };
|
|
418
|
+
export { AddRouteShortcuts, App, AppOptions, AppUse, CacheConditions, CreateRouterOptions, DynamicEventHandler, Encoding, EventHandler, EventHandlerResponse, H3CorsOptions, H3Error, H3Event, H3EventContext, H3Headers, H3Response, HTTPMethod, InputLayer, InputStack, Layer, LazyEventHandler, MIMES, Matcher, MultiPartData, NodeEventContext, NodeListener, NodeMiddleware, NodePromisifiedHandler, ProxyOptions, RequestHeaders, Router, RouterMethod, RouterUse, Session, SessionConfig, SessionData, Stack, appendCorsHeaders, appendCorsPreflightHeaders, appendHeader, appendHeaders, appendResponseHeader, appendResponseHeaders, assertMethod, callNodeListener, clearSession, createApp, createAppEventHandler, createError, createEvent, createRouter, defaultContentType, defineEventHandler, defineLazyEventHandler, defineNodeListener, defineNodeMiddleware, deleteCookie, dynamicEventHandler, eventHandler, fetchWithEvent, fromNodeMiddleware, getCookie, getHeader, getHeaders, getMethod, getProxyRequestHeaders, getQuery, getRequestHeader, getRequestHeaders, getRequestHost, getRequestPath, getRequestProtocol, getRequestURL, getResponseHeader, getResponseHeaders, getResponseStatus, getResponseStatusText, getRouterParam, getRouterParams, getSession, handleCacheHeaders, handleCors, isCorsOriginAllowed, isError, isEvent, isEventHandler, isMethod, isPreflightRequest, isStream, lazyEventHandler, parseCookies, promisifyNodeListener, proxyRequest, readBody, readMultipartFormData, readRawBody, sanitizeStatusCode, sanitizeStatusMessage, sealSession, send, sendError, sendNoContent, sendProxy, sendRedirect, sendStream, setCookie, setHeader, setHeaders, setResponseHeader, setResponseHeaders, setResponseStatus, splitCookiesString, toEventHandler, toNodeListener, unsealSession, updateSession, use, useBase, useSession, writeEarlyHints };
|
package/dist/index.mjs
CHANGED
|
@@ -175,7 +175,7 @@ function createError(input) {
|
|
|
175
175
|
return err;
|
|
176
176
|
}
|
|
177
177
|
function sendError(event, error, debug) {
|
|
178
|
-
if (event.
|
|
178
|
+
if (event.handled) {
|
|
179
179
|
return;
|
|
180
180
|
}
|
|
181
181
|
const h3Error = isError(error) ? error : createError(error);
|
|
@@ -188,7 +188,7 @@ function sendError(event, error, debug) {
|
|
|
188
188
|
if (debug) {
|
|
189
189
|
responseBody.stack = (h3Error.stack || "").split("\n").map((l) => l.trim());
|
|
190
190
|
}
|
|
191
|
-
if (event.
|
|
191
|
+
if (event.handled) {
|
|
192
192
|
return;
|
|
193
193
|
}
|
|
194
194
|
const _code = Number.parseInt(h3Error.statusCode);
|
|
@@ -250,23 +250,31 @@ function getRequestHeader(event, name) {
|
|
|
250
250
|
return value;
|
|
251
251
|
}
|
|
252
252
|
const getHeader = getRequestHeader;
|
|
253
|
-
function getRequestHost(event) {
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
253
|
+
function getRequestHost(event, opts = {}) {
|
|
254
|
+
if (opts.xForwardedHost) {
|
|
255
|
+
const xForwardedHost = event.node.req.headers["x-forwarded-host"];
|
|
256
|
+
if (xForwardedHost) {
|
|
257
|
+
return xForwardedHost;
|
|
258
|
+
}
|
|
257
259
|
}
|
|
258
260
|
return event.node.req.headers.host || "localhost";
|
|
259
261
|
}
|
|
260
|
-
function getRequestProtocol(event) {
|
|
261
|
-
if (event.node.req.headers["x-forwarded-proto"] === "https") {
|
|
262
|
+
function getRequestProtocol(event, opts = {}) {
|
|
263
|
+
if (opts.xForwardedProto !== false && event.node.req.headers["x-forwarded-proto"] === "https") {
|
|
262
264
|
return "https";
|
|
263
265
|
}
|
|
264
266
|
return event.node.req.connection.encrypted ? "https" : "http";
|
|
265
267
|
}
|
|
266
|
-
|
|
267
|
-
|
|
268
|
+
const DOUBLE_SLASH_RE = /[/\\]{2,}/g;
|
|
269
|
+
function getRequestPath(event) {
|
|
270
|
+
const path = (event.node.req.url || "/").replace(DOUBLE_SLASH_RE, "/");
|
|
271
|
+
return path;
|
|
272
|
+
}
|
|
273
|
+
function getRequestURL(event, opts = {}) {
|
|
274
|
+
const host = getRequestHost(event, opts);
|
|
268
275
|
const protocol = getRequestProtocol(event);
|
|
269
|
-
|
|
276
|
+
const path = getRequestPath(event);
|
|
277
|
+
return new URL(path, `${protocol}://${host}`);
|
|
270
278
|
}
|
|
271
279
|
|
|
272
280
|
const RawBodySymbol = Symbol.for("h3RawBody");
|
|
@@ -276,9 +284,15 @@ function readRawBody(event, encoding = "utf8") {
|
|
|
276
284
|
assertMethod(event, PayloadMethods$1);
|
|
277
285
|
const _rawBody = event.node.req[RawBodySymbol] || event.node.req.body;
|
|
278
286
|
if (_rawBody) {
|
|
279
|
-
const promise2 = Promise.resolve(_rawBody).then(
|
|
280
|
-
(
|
|
281
|
-
|
|
287
|
+
const promise2 = Promise.resolve(_rawBody).then((_resolved) => {
|
|
288
|
+
if (Buffer.isBuffer(_resolved)) {
|
|
289
|
+
return _resolved;
|
|
290
|
+
}
|
|
291
|
+
if (_resolved.constructor === Object) {
|
|
292
|
+
return Buffer.from(JSON.stringify(_resolved));
|
|
293
|
+
}
|
|
294
|
+
return Buffer.from(_resolved);
|
|
295
|
+
});
|
|
282
296
|
return encoding ? promise2.then((buff) => buff.toString(encoding)) : promise2;
|
|
283
297
|
}
|
|
284
298
|
if (!Number.parseInt(event.node.req.headers["content-length"] || "")) {
|
|
@@ -363,7 +377,9 @@ function handleCacheHeaders(event, opts) {
|
|
|
363
377
|
event.node.res.setHeader("cache-control", cacheControls.join(", "));
|
|
364
378
|
if (cacheMatched) {
|
|
365
379
|
event.node.res.statusCode = 304;
|
|
366
|
-
event.
|
|
380
|
+
if (!event.handled) {
|
|
381
|
+
event.node.res.end();
|
|
382
|
+
}
|
|
367
383
|
return true;
|
|
368
384
|
}
|
|
369
385
|
return false;
|
|
@@ -512,6 +528,7 @@ async function sendProxy(event, target, opts = {}) {
|
|
|
512
528
|
event.node.res.statusCode
|
|
513
529
|
);
|
|
514
530
|
event.node.res.statusMessage = sanitizeStatusMessage(response.statusText);
|
|
531
|
+
const cookies = [];
|
|
515
532
|
for (const [key, value] of response.headers.entries()) {
|
|
516
533
|
if (key === "content-encoding") {
|
|
517
534
|
continue;
|
|
@@ -520,7 +537,15 @@ async function sendProxy(event, target, opts = {}) {
|
|
|
520
537
|
continue;
|
|
521
538
|
}
|
|
522
539
|
if (key === "set-cookie") {
|
|
523
|
-
|
|
540
|
+
cookies.push(...splitCookiesString(value));
|
|
541
|
+
continue;
|
|
542
|
+
}
|
|
543
|
+
event.node.res.setHeader(key, value);
|
|
544
|
+
}
|
|
545
|
+
if (cookies.length > 0) {
|
|
546
|
+
event.node.res.setHeader(
|
|
547
|
+
"set-cookie",
|
|
548
|
+
cookies.map((cookie) => {
|
|
524
549
|
if (opts.cookieDomainRewrite) {
|
|
525
550
|
cookie = rewriteCookieProperty(
|
|
526
551
|
cookie,
|
|
@@ -536,15 +561,18 @@ async function sendProxy(event, target, opts = {}) {
|
|
|
536
561
|
);
|
|
537
562
|
}
|
|
538
563
|
return cookie;
|
|
539
|
-
})
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
564
|
+
})
|
|
565
|
+
);
|
|
566
|
+
}
|
|
567
|
+
if (opts.onResponse) {
|
|
568
|
+
await opts.onResponse(event, response);
|
|
544
569
|
}
|
|
545
570
|
if (response._data !== void 0) {
|
|
546
571
|
return response._data;
|
|
547
572
|
}
|
|
573
|
+
if (event.handled) {
|
|
574
|
+
return;
|
|
575
|
+
}
|
|
548
576
|
if (opts.sendStream === false) {
|
|
549
577
|
const data = new Uint8Array(await response.arrayBuffer());
|
|
550
578
|
return event.node.res.end(data);
|
|
@@ -603,14 +631,16 @@ function rewriteCookieProperty(header, map, property) {
|
|
|
603
631
|
);
|
|
604
632
|
}
|
|
605
633
|
|
|
606
|
-
const defer = typeof setImmediate
|
|
634
|
+
const defer = typeof setImmediate === "undefined" ? (fn) => fn() : setImmediate;
|
|
607
635
|
function send(event, data, type) {
|
|
608
636
|
if (type) {
|
|
609
637
|
defaultContentType(event, type);
|
|
610
638
|
}
|
|
611
639
|
return new Promise((resolve) => {
|
|
612
640
|
defer(() => {
|
|
613
|
-
event.
|
|
641
|
+
if (!event.handled) {
|
|
642
|
+
event.node.res.end(data);
|
|
643
|
+
}
|
|
614
644
|
resolve();
|
|
615
645
|
});
|
|
616
646
|
});
|
|
@@ -620,7 +650,9 @@ function sendNoContent(event, code = 204) {
|
|
|
620
650
|
if (event.node.res.statusCode === 204) {
|
|
621
651
|
event.node.res.removeHeader("content-length");
|
|
622
652
|
}
|
|
623
|
-
event.
|
|
653
|
+
if (!event.handled) {
|
|
654
|
+
event.node.res.end();
|
|
655
|
+
}
|
|
624
656
|
}
|
|
625
657
|
function setResponseStatus(event, code, text) {
|
|
626
658
|
if (code) {
|
|
@@ -803,7 +835,7 @@ async function getSession(event, config) {
|
|
|
803
835
|
Object.assign(session, unsealed);
|
|
804
836
|
}
|
|
805
837
|
if (!session.id) {
|
|
806
|
-
session.id = (config.crypto || crypto).randomUUID();
|
|
838
|
+
session.id = config.generateId?.() ?? (config.crypto || crypto).randomUUID();
|
|
807
839
|
session.createdAt = Date.now();
|
|
808
840
|
await updateSession(event, config);
|
|
809
841
|
}
|
|
@@ -1074,11 +1106,15 @@ class H3Response {
|
|
|
1074
1106
|
class H3Event {
|
|
1075
1107
|
constructor(req, res) {
|
|
1076
1108
|
this["__is_event__"] = true;
|
|
1109
|
+
this._handled = false;
|
|
1077
1110
|
this.context = {};
|
|
1078
1111
|
this.node = { req, res };
|
|
1079
1112
|
}
|
|
1080
1113
|
get path() {
|
|
1081
|
-
return this
|
|
1114
|
+
return getRequestPath(this);
|
|
1115
|
+
}
|
|
1116
|
+
get handled() {
|
|
1117
|
+
return this._handled || this.node.res.writableEnded || this.node.res.headersSent;
|
|
1082
1118
|
}
|
|
1083
1119
|
/** @deprecated Please use `event.node.req` instead. **/
|
|
1084
1120
|
get req() {
|
|
@@ -1091,35 +1127,37 @@ class H3Event {
|
|
|
1091
1127
|
// Implementation of FetchEvent
|
|
1092
1128
|
respondWith(r) {
|
|
1093
1129
|
Promise.resolve(r).then((_response) => {
|
|
1094
|
-
if (this.
|
|
1130
|
+
if (this.handled) {
|
|
1095
1131
|
return;
|
|
1096
1132
|
}
|
|
1097
1133
|
const response = _response instanceof H3Response ? _response : new H3Response(_response);
|
|
1098
1134
|
for (const [key, value] of response.headers.entries()) {
|
|
1099
|
-
this.res.setHeader(key, value);
|
|
1135
|
+
this.node.res.setHeader(key, value);
|
|
1100
1136
|
}
|
|
1101
1137
|
if (response.status) {
|
|
1102
|
-
this.res.statusCode = sanitizeStatusCode(
|
|
1138
|
+
this.node.res.statusCode = sanitizeStatusCode(
|
|
1103
1139
|
response.status,
|
|
1104
|
-
this.res.statusCode
|
|
1140
|
+
this.node.res.statusCode
|
|
1105
1141
|
);
|
|
1106
1142
|
}
|
|
1107
1143
|
if (response.statusText) {
|
|
1108
|
-
this.res.statusMessage = sanitizeStatusMessage(
|
|
1144
|
+
this.node.res.statusMessage = sanitizeStatusMessage(
|
|
1145
|
+
response.statusText
|
|
1146
|
+
);
|
|
1109
1147
|
}
|
|
1110
1148
|
if (response.redirected) {
|
|
1111
|
-
this.res.setHeader("location", response.url);
|
|
1149
|
+
this.node.res.setHeader("location", response.url);
|
|
1112
1150
|
}
|
|
1113
1151
|
if (!response._body) {
|
|
1114
|
-
return this.res.end();
|
|
1152
|
+
return this.node.res.end();
|
|
1115
1153
|
}
|
|
1116
1154
|
if (typeof response._body === "string" || "buffer" in response._body || "byteLength" in response._body) {
|
|
1117
|
-
return this.res.end(response._body);
|
|
1155
|
+
return this.node.res.end(response._body);
|
|
1118
1156
|
}
|
|
1119
1157
|
if (!response.headers.has("content-type")) {
|
|
1120
1158
|
response.headers.set("content-type", MIMES.json);
|
|
1121
1159
|
}
|
|
1122
|
-
this.res.end(JSON.stringify(response._body));
|
|
1160
|
+
this.node.res.end(JSON.stringify(response._body));
|
|
1123
1161
|
});
|
|
1124
1162
|
}
|
|
1125
1163
|
}
|
|
@@ -1245,7 +1283,7 @@ function createAppEventHandler(stack, options) {
|
|
|
1245
1283
|
continue;
|
|
1246
1284
|
}
|
|
1247
1285
|
const val = await layer.handler(event);
|
|
1248
|
-
if (event.
|
|
1286
|
+
if (event.handled) {
|
|
1249
1287
|
return;
|
|
1250
1288
|
}
|
|
1251
1289
|
const type = typeof val;
|
|
@@ -1270,10 +1308,10 @@ function createAppEventHandler(stack, options) {
|
|
|
1270
1308
|
}
|
|
1271
1309
|
}
|
|
1272
1310
|
}
|
|
1273
|
-
if (!event.
|
|
1311
|
+
if (!event.handled) {
|
|
1274
1312
|
throw createError({
|
|
1275
1313
|
statusCode: 404,
|
|
1276
|
-
statusMessage: `Cannot find any
|
|
1314
|
+
statusMessage: `Cannot find any path matching ${event.node.req.url || "/"}.`
|
|
1277
1315
|
});
|
|
1278
1316
|
}
|
|
1279
1317
|
});
|
|
@@ -1421,17 +1459,27 @@ function createRouter(opts = {}) {
|
|
|
1421
1459
|
const method = (event.node.req.method || "get").toLowerCase();
|
|
1422
1460
|
const handler = matched.handlers[method] || matched.handlers.all;
|
|
1423
1461
|
if (!handler) {
|
|
1424
|
-
|
|
1425
|
-
|
|
1426
|
-
|
|
1427
|
-
|
|
1428
|
-
|
|
1462
|
+
if (opts.preemptive || opts.preemtive) {
|
|
1463
|
+
throw createError({
|
|
1464
|
+
statusCode: 405,
|
|
1465
|
+
name: "Method Not Allowed",
|
|
1466
|
+
statusMessage: `Method ${method} is not allowed on this route.`
|
|
1467
|
+
});
|
|
1468
|
+
} else {
|
|
1469
|
+
return;
|
|
1470
|
+
}
|
|
1429
1471
|
}
|
|
1430
1472
|
const params = matched.params || {};
|
|
1431
1473
|
event.context.params = params;
|
|
1432
|
-
return handler(event)
|
|
1474
|
+
return Promise.resolve(handler(event)).then((res) => {
|
|
1475
|
+
if (res === void 0 && (opts.preemptive || opts.preemtive)) {
|
|
1476
|
+
setResponseStatus(event, 204);
|
|
1477
|
+
return "";
|
|
1478
|
+
}
|
|
1479
|
+
return res;
|
|
1480
|
+
});
|
|
1433
1481
|
});
|
|
1434
1482
|
return router;
|
|
1435
1483
|
}
|
|
1436
1484
|
|
|
1437
|
-
export { H3Error, H3Event, H3Headers, H3Response, MIMES, appendCorsHeaders, appendCorsPreflightHeaders, appendHeader, appendHeaders, appendResponseHeader, appendResponseHeaders, assertMethod, callNodeListener, clearSession, createApp, createAppEventHandler, createError, createEvent, createRouter, defaultContentType, defineEventHandler, defineLazyEventHandler, defineNodeListener, defineNodeMiddleware, deleteCookie, dynamicEventHandler, eventHandler, fetchWithEvent, fromNodeMiddleware, getCookie, getHeader, getHeaders, getMethod, getProxyRequestHeaders, getQuery, getRequestHeader, getRequestHeaders, getRequestHost, getRequestProtocol, getRequestURL, getResponseHeader, getResponseHeaders, getResponseStatus, getResponseStatusText, getRouterParam, getRouterParams, getSession, handleCacheHeaders, handleCors, isCorsOriginAllowed, isError, isEvent, isEventHandler, isMethod, isPreflightRequest, isStream, lazyEventHandler, parseCookies, promisifyNodeListener, proxyRequest, readBody, readMultipartFormData, readRawBody, sanitizeStatusCode, sanitizeStatusMessage, sealSession, send, sendError, sendNoContent, sendProxy, sendRedirect, sendStream, setCookie, setHeader, setHeaders, setResponseHeader, setResponseHeaders, setResponseStatus, splitCookiesString, toEventHandler, toNodeListener, unsealSession, updateSession, use, useBase, useSession, writeEarlyHints };
|
|
1485
|
+
export { H3Error, H3Event, H3Headers, H3Response, MIMES, appendCorsHeaders, appendCorsPreflightHeaders, appendHeader, appendHeaders, appendResponseHeader, appendResponseHeaders, assertMethod, callNodeListener, clearSession, createApp, createAppEventHandler, createError, createEvent, createRouter, defaultContentType, defineEventHandler, defineLazyEventHandler, defineNodeListener, defineNodeMiddleware, deleteCookie, dynamicEventHandler, eventHandler, fetchWithEvent, fromNodeMiddleware, getCookie, getHeader, getHeaders, getMethod, getProxyRequestHeaders, getQuery, getRequestHeader, getRequestHeaders, getRequestHost, getRequestPath, getRequestProtocol, getRequestURL, getResponseHeader, getResponseHeaders, getResponseStatus, getResponseStatusText, getRouterParam, getRouterParams, getSession, handleCacheHeaders, handleCors, isCorsOriginAllowed, isError, isEvent, isEventHandler, isMethod, isPreflightRequest, isStream, lazyEventHandler, parseCookies, promisifyNodeListener, proxyRequest, readBody, readMultipartFormData, readRawBody, sanitizeStatusCode, sanitizeStatusMessage, sealSession, send, sendError, sendNoContent, sendProxy, sendRedirect, sendStream, setCookie, setHeader, setHeaders, setResponseHeader, setResponseHeaders, setResponseStatus, splitCookiesString, toEventHandler, toNodeListener, unsealSession, updateSession, use, useBase, useSession, writeEarlyHints };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "h3",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.7.0",
|
|
4
4
|
"description": "Tiny JavaScript Server",
|
|
5
5
|
"repository": "unjs/h3",
|
|
6
6
|
"license": "MIT",
|
|
@@ -19,46 +19,46 @@
|
|
|
19
19
|
"files": [
|
|
20
20
|
"dist"
|
|
21
21
|
],
|
|
22
|
+
"scripts": {
|
|
23
|
+
"build": "unbuild",
|
|
24
|
+
"dev": "vitest",
|
|
25
|
+
"lint": "eslint --cache --ext .ts,.js,.mjs,.cjs . && prettier -c src test playground",
|
|
26
|
+
"lint:fix": "eslint --cache --ext .ts,.js,.mjs,.cjs . --fix && prettier -c src test playground -w",
|
|
27
|
+
"play": "jiti ./playground/index.ts",
|
|
28
|
+
"profile": "0x -o -D .profile -P 'autocannon -c 100 -p 10 -d 40 http://localhost:$PORT' ./playground/server.cjs",
|
|
29
|
+
"release": "pnpm test && pnpm build && changelogen --release && pnpm publish && git push --follow-tags",
|
|
30
|
+
"test": "pnpm lint && vitest run --coverage"
|
|
31
|
+
},
|
|
22
32
|
"dependencies": {
|
|
23
33
|
"cookie-es": "^1.0.0",
|
|
24
34
|
"defu": "^6.1.2",
|
|
25
|
-
"destr": "^
|
|
35
|
+
"destr": "^2.0.0",
|
|
26
36
|
"iron-webcrypto": "^0.7.0",
|
|
27
37
|
"radix3": "^1.0.1",
|
|
28
38
|
"ufo": "^1.1.2",
|
|
29
|
-
"uncrypto": "^0.1.
|
|
39
|
+
"uncrypto": "^0.1.3"
|
|
30
40
|
},
|
|
31
41
|
"devDependencies": {
|
|
32
42
|
"0x": "^5.5.0",
|
|
33
43
|
"@types/express": "^4.17.17",
|
|
34
|
-
"@types/node": "^20.
|
|
44
|
+
"@types/node": "^20.3.1",
|
|
35
45
|
"@types/supertest": "^2.0.12",
|
|
36
|
-
"@vitest/coverage-
|
|
46
|
+
"@vitest/coverage-v8": "^0.32.2",
|
|
37
47
|
"autocannon": "^7.11.0",
|
|
38
48
|
"changelogen": "^0.5.3",
|
|
39
49
|
"connect": "^3.7.0",
|
|
40
|
-
"eslint": "^8.
|
|
41
|
-
"eslint-config-unjs": "^0.1
|
|
50
|
+
"eslint": "^8.43.0",
|
|
51
|
+
"eslint-config-unjs": "^0.2.1",
|
|
42
52
|
"express": "^4.18.2",
|
|
43
|
-
"get-port": "^
|
|
53
|
+
"get-port": "^7.0.0",
|
|
44
54
|
"jiti": "^1.18.2",
|
|
45
55
|
"listhen": "^1.0.4",
|
|
46
|
-
"node-fetch-native": "^1.
|
|
56
|
+
"node-fetch-native": "^1.2.0",
|
|
47
57
|
"prettier": "^2.8.8",
|
|
48
58
|
"supertest": "^6.3.3",
|
|
49
|
-
"typescript": "^5.
|
|
59
|
+
"typescript": "^5.1.3",
|
|
50
60
|
"unbuild": "^1.2.1",
|
|
51
|
-
"vitest": "^0.
|
|
61
|
+
"vitest": "^0.32.2"
|
|
52
62
|
},
|
|
53
|
-
"packageManager": "pnpm@8.
|
|
54
|
-
"scripts": {
|
|
55
|
-
"build": "unbuild",
|
|
56
|
-
"dev": "vitest",
|
|
57
|
-
"lint": "eslint --cache --ext .ts,.js,.mjs,.cjs . && prettier -c src test playground",
|
|
58
|
-
"lint:fix": "eslint --cache --ext .ts,.js,.mjs,.cjs . --fix && prettier -c src test playground -w",
|
|
59
|
-
"play": "jiti ./playground/index.ts",
|
|
60
|
-
"profile": "0x -o -D .profile -P 'autocannon -c 100 -p 10 -d 40 http://localhost:$PORT' ./playground/server.cjs",
|
|
61
|
-
"release": "pnpm test && pnpm build && changelogen --release && pnpm publish && git push --follow-tags",
|
|
62
|
-
"test": "pnpm lint && vitest run --coverage"
|
|
63
|
-
}
|
|
63
|
+
"packageManager": "pnpm@8.6.3"
|
|
64
64
|
}
|