Skip to content

Commit

Permalink
feat: add app's timeout options.
Browse files Browse the repository at this point in the history
  • Loading branch information
chizuki committed Feb 24, 2023
1 parent 40e4af1 commit 1ba0062
Show file tree
Hide file tree
Showing 14 changed files with 130 additions and 79 deletions.
1 change: 1 addition & 0 deletions packages/core/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
"license": "MIT",
"dependencies": {
"consola": "^2.15.3",
"events": "^3.3.0",
"maybe-types": "^0.0.3",
"minimatch": "^5.1.0",
"query-string": "^7.1.1"
Expand Down
17 changes: 10 additions & 7 deletions packages/core/src/app.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,6 @@ import {
,
createServiceContext
} from "./shared";
import type { DelayMsType } from "./utils";
import {
createQuery,
createSingletonPromise, isMatch,
Expand All @@ -34,10 +33,6 @@ export interface FourzeAppOptions {

modules?: FourzeModule[]

/**
* 延时
*/
delay?: DelayMsType
/**
* 允许的路径规则,默认为所有
* @default []
Expand All @@ -51,6 +46,8 @@ export interface FourzeAppOptions {

meta?: FourzeAppMeta

timeout?: number

setup?: FourzeAppSetup

fallback?: FourzeNext
Expand All @@ -77,7 +74,7 @@ export function createApp(args: FourzeAppOptions | FourzeAppSetup = {}): FourzeA
const options = isOptions ? args : {};
const setup = isSetup ? args : options.setup ?? (() => { });

const { fallback } = options;
const { fallback, timeout = 5000 } = options;

const persistenceMiddlewareStore = createQuery<FourzeMiddlewareNode>();

Expand All @@ -101,6 +98,9 @@ export function createApp(args: FourzeAppOptions | FourzeAppSetup = {}): FourzeA

try {
if (app.isAllow(url)) {
const timer = setTimeout(() => {
response.sendError(408, "Request Timeout");
}, timeout);
const ms = app.match(url);

request.app = app;
Expand All @@ -119,6 +119,9 @@ export function createApp(args: FourzeAppOptions | FourzeAppSetup = {}): FourzeA
}
}
await doNext();

await response.wait();
clearTimeout(timer);
} else {
await next?.();
}
Expand Down Expand Up @@ -223,7 +226,7 @@ export function createApp(args: FourzeAppOptions | FourzeAppSetup = {}): FourzeA
middlewareStore.reset(persistenceMiddlewareStore);

try {
// 初始化app
// 初始化app
const setupReturn = await setup(this);

if (isArray(setupReturn)) {
Expand Down
38 changes: 22 additions & 16 deletions packages/core/src/endpoints/index.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import type { MaybePromise, MaybeRegex } from "maybe-types";
import { delay, isMatch, isUndef, overload } from "../utils";
import { delay, isError, isMatch, isUndef, overload } from "../utils";
import type { FourzeMiddleware, PropType } from "../shared";
import { defineMiddleware } from "../shared";
import type { DelayMsType } from "../utils";
Expand All @@ -10,9 +10,19 @@ export const JSON_WRAPPER_HEADER = "Fourze-Json-Wrapper";

export function delayHook(ms: DelayMsType): FourzeMiddleware {
return defineMiddleware("Delay", -1, async (req, res, next) => {
const delayMs = res.getHeader(DELAY_HEADER) ?? req.headers[DELAY_HEADER] ?? ms;
const time = await delay(delayMs);
res.setHeader(DELAY_HEADER, time);
const _send = res.send.bind(res);

res.send = function (...args: any[]) {
const delayMs = res.getHeader(DELAY_HEADER) ?? req.headers[DELAY_HEADER] ?? req.meta[DELAY_HEADER] ?? ms;
delay(delayMs).then((ms) => {
if (!res.writableEnded) {
res.setHeader(DELAY_HEADER, ms);
_send(...args as Parameters<typeof _send>);
}
});
return res;
};

await next?.();
});
}
Expand Down Expand Up @@ -78,26 +88,22 @@ export function jsonWrapperHook(
return defineMiddleware("JsonWrapper", -1, async (req, res, next) => {
const _send = res.send.bind(res);
res.send = function (payload, contentType) {
contentType = contentType ?? req.meta.contentType ?? res.getContentType(payload);
contentType = contentType ?? req.meta.contentType ?? res.getContentType();
const useJsonWrapper = res.getHeader(JSON_WRAPPER_HEADER) as string;
const isAllow = (isUndef(useJsonWrapper) || !["false", "0", "off"].includes(useJsonWrapper)) && !isExclude(req.path) && contentType?.startsWith("application/json");
const isAllow = (isUndef(useJsonWrapper) || !["false", "0", "off"].includes(useJsonWrapper)) && !isExclude(req.path) && (!contentType || contentType.startsWith("application/json"));

if (isAllow) {
payload = resolve(payload) ?? payload;
if (isError(payload) && reject) {
payload = reject(payload) ?? payload;
} else {
payload = resolve(payload) ?? payload;
}
}

_send(payload, contentType);
return res;
};

if (reject) {
const _sendError = res.sendError.bind(res);
res.sendError = function (...args: any[]) {
_sendError(...args);
_send(reject(res.error), "application/json");
return this;
};
}

await next();
});
}
Expand Down
5 changes: 4 additions & 1 deletion packages/core/src/polyfill/response.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,14 @@
import EventEmitter from "events";
import { getHeaderRawValue } from "./header";

export class PolyfillServerResponse {
export class PolyfillServerResponse extends EventEmitter {
headers: Record<string, string>;
_ended: boolean;
matched: boolean;
statusCode: number;

constructor() {
super();
this.headers = {};
this._ended = false;
this.matched = false;
Expand All @@ -33,6 +35,7 @@ export class PolyfillServerResponse {
cb();
}
this._ended = true;
this.emit("finish");
return this;
}

Expand Down
6 changes: 1 addition & 5 deletions packages/core/src/router.ts
Original file line number Diff line number Diff line change
Expand Up @@ -155,7 +155,7 @@ export function defineRouter(

try {
const _result = await route.handle(request, response);
if (!isUndefined(_result)) {
if (!isUndefined(_result) && !response.writableEnded) {
response.send(_result);
}
} catch (error: any) {
Expand All @@ -165,10 +165,6 @@ export function defineRouter(
logger.debug(
`Request matched -> ${normalizeRoute(request.path, method)}.`
);

if (!response.writableEnded) {
response.end();
}
} else {
logger.debug(`[${router.name}]`, `Request not matched -> ${normalizeRoute(request.path)}.`);
await next?.();
Expand Down
20 changes: 17 additions & 3 deletions packages/core/src/shared/response.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import type { OutgoingMessage, ServerResponse } from "http";
import { createLogger } from "../logger";
import { PolyfillServerResponse, getHeaderValue } from "../polyfill";
import { isDef, isObject, isString, isUint8Array, overload } from "../utils";
import { isDef, isError, isObject, isString, isUint8Array, overload } from "../utils";
import { FourzeError } from "./error";
import type { FourzeRequest } from "./request";

Expand Down Expand Up @@ -43,6 +43,8 @@ export interface FourzeResponse extends FourzeBaseResponse {

sendError(error?: string | Error): this

wait(): Promise<void>

readonly res?: OutgoingMessage

readonly request: FourzeRequest
Expand Down Expand Up @@ -86,6 +88,9 @@ export function createResponse(options: FourzeResponseOptions) {
};

response.send = function (payload: any, contentType?: string) {
if (isError(payload)) {
payload = payload.message;
}
contentType = contentType ?? this.getContentType(payload);
switch (contentType) {
case "application/json":
Expand Down Expand Up @@ -126,8 +131,8 @@ export function createResponse(options: FourzeResponseOptions) {

const defaultStatusCode = _error instanceof FourzeError ? _error.statusCode : 500;
this.statusCode = code ?? defaultStatusCode;
logger.error(error);
return this;
logger.error(_error);
return this.send(_error);
};

response.appendHeader = function (
Expand Down Expand Up @@ -168,9 +173,18 @@ export function createResponse(options: FourzeResponseOptions) {
response.redirect = function (url: string) {
this.statusCode = 302;
this.setHeader("Location", url);
this.end();
return this;
};

response.wait = function () {
return new Promise((resolve) => {
this.on("finish", () => {
resolve();
});
});
};

Object.defineProperties(response, {
[FOURZE_RESPONSE_SYMBOL]: {
get() {
Expand Down
43 changes: 43 additions & 0 deletions packages/core/src/utils/events.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
import EventEmitter from "events";

export function injectEventEmitter<T extends EventEmitter>(app: T) {
const _emitter = new EventEmitter();
app.addListener = function (
event: string,
listener: (...args: any[]) => void
) {
_emitter.addListener(event, listener);
return this;
};

app.on = function (event: string, listener: (...args: any[]) => void) {
_emitter.on(event, listener);
return this;
};

app.emit = function (event: string, ...args: any[]) {
return _emitter.emit(event, ...args);
};

app.once = function (event: string, listener: (...args: any[]) => void) {
_emitter.once(event, listener);
return this;
};

app.removeListener = function (
event: string,
listener: (...args: any[]) => void
) {
_emitter.removeListener(event, listener);
return this;
};

app.removeAllListeners = function (event?: string) {
_emitter.removeAllListeners(event);
return this;
};

app.listeners = function (event: string) {
return _emitter.listeners(event);
};
}
1 change: 1 addition & 0 deletions packages/core/src/utils/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,3 +11,4 @@ export * from "./random";
export * from "./storage";
export * from "./html";
export * from "./string";
export * from "./events";
4 changes: 4 additions & 0 deletions packages/core/src/utils/is.ts
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,10 @@ export function isURL(value: unknown): value is URL {
return globalThis.URL && value instanceof URL;
}

export function isError(error: unknown): error is Error {
return error instanceof Error;
}

export function isPrimitive(
value: unknown
): value is string | number | boolean {
Expand Down
6 changes: 6 additions & 0 deletions packages/integrations/unplugin/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,11 @@ export interface UnpluginFourzeOptions {

delay?: DelayMsType

/**
* @default 5000
*/
timeout?: number

allow?: string[]

deny?: string[]
Expand Down Expand Up @@ -157,6 +162,7 @@ const createFourzePlugin = createUnplugin((options: UnpluginFourzeOptions = {})
allow,
deny,
dir,
timeout: options.timeout ?? 5000,
files: options.files
});

Expand Down
5 changes: 2 additions & 3 deletions packages/server/src/server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import {
createApp,
createLogger,
createServiceContext,
injectEventEmitter,
isMatch,
overload
} from "@fourze/core";
Expand All @@ -19,7 +20,7 @@ import type {
FourzeServiceContext,
PropType
} from "@fourze/core";
import { injectEventEmitter, isAddressInfo, normalizeAddress } from "./utils";
import { isAddressInfo, normalizeAddress } from "./utils";

export interface FourzeServerOptions {
host?: string
Expand Down Expand Up @@ -92,7 +93,6 @@ export function createServer(...args: [FourzeApp, FourzeServerOptions] | [Fourze
404,
`Cannot ${request.method} ${request.url ?? "/"}`
);
response.end();
}
});

Expand All @@ -101,7 +101,6 @@ export function createServer(...args: [FourzeApp, FourzeServerOptions] | [Fourze
serverApp.emit("error", error, { request, response });
if (!response.writableEnded) {
response.sendError(500, "Internal Server Error");
response.end();
}
}
}) as FourzeServer;
Expand Down
Loading

0 comments on commit 1ba0062

Please sign in to comment.