@aomex/web
Advanced tools
Comparing version 0.0.5 to 0.0.6
# @aomex/web | ||
## 0.0.6 | ||
### Patch Changes | ||
- [`d3a2757`](https://github.com/aomex/aomex/commit/d3a2757b38391ad70a206d95eac09520c64e9b85) Thanks [@geekact](https://github.com/geekact)! - fix(web): statusCode 覆盖无效 | ||
- [`07c8575`](https://github.com/aomex/aomex/commit/07c857565ede4eca7e88de4e8c5e5f244f9d16cb) Thanks [@geekact](https://github.com/geekact)! - feat(web): 删除 error-pre-respond 事件 | ||
- [`e80844a`](https://github.com/aomex/aomex/commit/e80844a6808fd0f3b9d613ec3163b06357316cae) Thanks [@geekact](https://github.com/geekact)! - feat(web): 导出 statuses 库 | ||
- [`146b6b5`](https://github.com/aomex/aomex/commit/146b6b5f09d4b9f557c1771686f3585bebf4cfc9) Thanks [@geekact](https://github.com/geekact)! - refactor(helper): 重命名 color 为 chalk | ||
- [`56f65c8`](https://github.com/aomex/aomex/commit/56f65c8509b40679272ed58f63e12d98e1818503) Thanks [@geekact](https://github.com/geekact)! - feat(web): 设置 body 时设置 headers | ||
- [`c6df4de`](https://github.com/aomex/aomex/commit/c6df4de61fc28f452f8a202c9a485e1a061b3eb6) Thanks [@geekact](https://github.com/geekact)! - feat(web): 动态获取 contentType | ||
- [`79e19c1`](https://github.com/aomex/aomex/commit/79e19c1ae2a35b38580a4d2a27b6dde634345126) Thanks [@geekact](https://github.com/geekact)! - fix(web): 仅在 body setter 中可以强制指定 statusCode | ||
- [`9adefaa`](https://github.com/aomex/aomex/commit/9adefaa8baaaafb7be610c7a98920c9830d10a3c) Thanks [@geekact](https://github.com/geekact)! - feat(web): 挂载中间件时检测到链条自动创建分割点 | ||
- [`a8bb601`](https://github.com/aomex/aomex/commit/a8bb601acaf1e1d580511ae886a4ae99bf19f699) Thanks [@geekact](https://github.com/geekact)! - feat(web): response 增加 isJSON 方法 | ||
- [`902597b`](https://github.com/aomex/aomex/commit/902597b2f98fda7e63f7419a5ef62ab2acb7c863) Thanks [@geekact](https://github.com/geekact)! - feat(web): 定制异常的响应格式使用事件触发 | ||
- Updated dependencies [[`146b6b5`](https://github.com/aomex/aomex/commit/146b6b5f09d4b9f557c1771686f3585bebf4cfc9), [`e8dee7c`](https://github.com/aomex/aomex/commit/e8dee7c3f064587cd2afb9cb95ab3341f63ccd27)]: | ||
- @aomex/helper@0.0.3 | ||
- @aomex/core@0.0.5 | ||
## 0.0.5 | ||
@@ -4,0 +32,0 @@ |
import { Chain, PureChain, PureMiddlewareToken, Next, Middleware, OpenAPI, Validator, TransformedValidator, ValidateResult, ValidatorOptions } from '@aomex/core'; | ||
import { NonReadonly } from '@aomex/helper'; | ||
import { IncomingMessage, ServerResponse, Server, RequestListener } from 'node:http'; | ||
import { Server, RequestListener, ServerResponse, IncomingMessage } from 'node:http'; | ||
import EventEmitter from 'node:events'; | ||
@@ -8,6 +8,7 @@ import { HttpError } from 'http-errors'; | ||
import { IParseOptions } from 'qs'; | ||
import { Accepts } from 'accepts'; | ||
import { Stream } from 'node:stream'; | ||
import contentDisposition from 'content-disposition'; | ||
import { Accepts } from 'accepts'; | ||
import { File } from 'formidable'; | ||
export { default as statuses } from 'statuses'; | ||
@@ -27,2 +28,21 @@ declare module '@aomex/core' { | ||
interface WebAppOption { | ||
debug?: boolean; | ||
globalChain?: WebChain; | ||
queryParser?: IParseOptions; | ||
} | ||
declare class WebApp extends EventEmitter { | ||
readonly options: WebAppOption; | ||
readonly chainPoints: string[]; | ||
protected readonly middlewareList: WebMiddlewareToken[]; | ||
constructor(options?: WebAppOption); | ||
get debug(): boolean; | ||
listen: Server['listen']; | ||
callback(): RequestListener<any, any>; | ||
log(err: HttpError): void; | ||
on(eventName: 'error', listener: (err: HttpError, ctx: WebContext) => void): this; | ||
on(eventName: string | symbol, listener: (...args: any[]) => void): this; | ||
mount(middleware: WebMiddlewareToken): void; | ||
} | ||
type UpperHeaderKeys = UpperArrayHeaderKeys | UpperStringHeaderKeys; | ||
@@ -35,30 +55,2 @@ type UpperArrayHeaderKeys = 'Set-Cookie'; | ||
declare class WebRequest extends IncomingMessage { | ||
app: WebApp; | ||
res: WebResponse; | ||
ctx: WebContext; | ||
readonly method: string; | ||
readonly url: string; | ||
params: Record<string, unknown>; | ||
protected _query?: any; | ||
protected _body?: any; | ||
protected _accept?: Accepts; | ||
get path(): string; | ||
get querystring(): string; | ||
get query(): Record<string, unknown>; | ||
get body(): Promise<unknown>; | ||
get contentType(): string; | ||
findContentType(type: string, ...types: string[]): string | null; | ||
get accept(): Accepts; | ||
get ip(): string; | ||
get fresh(): boolean; | ||
} | ||
declare module 'node:http' { | ||
type ExternalHeaders = { | ||
[K in LowerExternalStringHeaderKeys]?: string; | ||
}; | ||
interface IncomingHttpHeaders extends ExternalHeaders { | ||
} | ||
} | ||
type Body = string | object | Stream | Buffer | null; | ||
@@ -68,8 +60,9 @@ declare class WebResponse<Request extends WebRequest = WebRequest> extends ServerResponse<Request> { | ||
ctx: WebContext; | ||
protected _body: Body; | ||
protected _explicitBody: boolean; | ||
protected _explicitStatus: boolean; | ||
private _body; | ||
private _explicitBody; | ||
private _explicitStatus; | ||
private _statusCode; | ||
private _determineHeaders; | ||
private _determineNullBody; | ||
constructor(req: Request); | ||
/** @ts-expect-error */ | ||
set statusCode(code: number); | ||
readonly setHeader: { | ||
@@ -103,2 +96,4 @@ <T extends WebResponse>(name: UpperStringHeaderKeys, value: number | string): T; | ||
}): void; | ||
protected removeHeaders(...headers: Array<UpperStringHeaderKeys | UpperArrayHeaderKeys>): void; | ||
protected removeHeaders(...headers: Array<string>): void; | ||
redirect(url: string): void; | ||
@@ -113,3 +108,7 @@ redirect(statusCode: 300 | 301 | 302 | 303 | 305 | 307 | 308, url: string): void; | ||
set body(val: Body); | ||
flush(): void; | ||
/** | ||
* 判断body是否为JSON格式 | ||
*/ | ||
isJSON(body: Body): body is object; | ||
flush(): any; | ||
onError(error?: Error | HttpError | null): void; | ||
@@ -123,24 +122,41 @@ findContentType(type: string, ...types: string[]): string | null; | ||
varyAppend(header: string, field: string | string[]): string; | ||
protected setStatus(code: number): void; | ||
/** | ||
* 根据header设置头部信息 | ||
*/ | ||
protected determineHeaders(): void; | ||
/** | ||
* 重新整理body=null和状态的关系 | ||
* - status-code | ||
* - content-type | ||
*/ | ||
protected determineNullBody(): void; | ||
} | ||
interface WebAppOption { | ||
debug?: boolean; | ||
globalChain?: WebChain; | ||
queryParser?: IParseOptions; | ||
errorFormatter?: (msg: string, statusCode: number, helper: { | ||
err: HttpError; | ||
ctx: WebContext; | ||
}) => Body; | ||
declare class WebRequest extends IncomingMessage { | ||
app: WebApp; | ||
res: WebResponse; | ||
ctx: WebContext; | ||
readonly method: string; | ||
readonly url: string; | ||
params: Record<string, unknown>; | ||
protected _query?: any; | ||
protected _body?: any; | ||
protected _accept?: Accepts; | ||
get path(): string; | ||
get querystring(): string; | ||
get query(): Record<string, unknown>; | ||
get body(): Promise<unknown>; | ||
get contentType(): string; | ||
findContentType(type: string, ...types: string[]): string | null; | ||
get accept(): Accepts; | ||
get ip(): string; | ||
get fresh(): boolean; | ||
} | ||
declare class WebApp extends EventEmitter { | ||
readonly options: WebAppOption; | ||
readonly globalChainPoint?: string; | ||
protected readonly middlewareList: WebMiddlewareToken[]; | ||
constructor(options?: WebAppOption); | ||
get debug(): boolean; | ||
listen: Server['listen']; | ||
callback(): RequestListener<any, any>; | ||
log(err: HttpError): void; | ||
on(eventName: 'error', listener: (err: HttpError, ctx: WebContext) => void): this; | ||
mount(middleware: WebMiddlewareToken): void; | ||
declare module 'node:http' { | ||
type ExternalHeaders = { | ||
[K in LowerExternalStringHeaderKeys]?: string; | ||
}; | ||
interface IncomingHttpHeaders extends ExternalHeaders { | ||
} | ||
} | ||
@@ -147,0 +163,0 @@ |
@@ -136,3 +136,2 @@ // src/override/middleware.ts | ||
// src/app/context.ts | ||
import assert2 from "node:assert"; | ||
import createHttpError from "http-errors"; | ||
@@ -150,6 +149,2 @@ var WebContext = class { | ||
if (typeof statusOrBody === "number") { | ||
assert2( | ||
statusOrBody >= 100 && statusOrBody <= 999, | ||
`invalid status code: ${statusOrBody}` | ||
); | ||
this.response.statusCode = statusOrBody; | ||
@@ -174,3 +169,3 @@ } else { | ||
import { Chain as Chain2, compose as compose2 } from "@aomex/core"; | ||
import { color } from "@aomex/helper"; | ||
import { chalk } from "@aomex/helper"; | ||
@@ -266,2 +261,3 @@ // src/app/request.ts | ||
import stream, { Stream } from "node:stream"; | ||
import assert2 from "node:assert"; | ||
import statuses from "statuses"; | ||
@@ -300,11 +296,17 @@ import escapeHtml from "escape-html"; | ||
_explicitStatus = false; | ||
_statusCode; | ||
_determineHeaders = false; | ||
_determineNullBody = false; | ||
constructor(req) { | ||
super(req); | ||
super.statusCode = 404; | ||
this._statusCode = 404; | ||
Object.defineProperty(this, "statusCode", { | ||
get: () => { | ||
return this._statusCode; | ||
}, | ||
set: (code) => { | ||
this.setStatus(code); | ||
} | ||
}); | ||
} | ||
/** @ts-expect-error */ | ||
set statusCode(code) { | ||
this._explicitStatus = true; | ||
super.statusCode = code; | ||
} | ||
/** | ||
@@ -318,2 +320,5 @@ * 批量设置头部数据 | ||
} | ||
removeHeaders(...headers) { | ||
headers.forEach(this.removeHeader.bind(this)); | ||
} | ||
redirect(status, url) { | ||
@@ -341,17 +346,7 @@ url = typeof url === "string" ? url : status.toString(); | ||
get contentLength() { | ||
const body2 = this._body; | ||
if (statuses.empty[this.statusCode]) | ||
return 0; | ||
if (body2 === null && !this.findContentType("json")) | ||
return 0; | ||
if (body2 instanceof Stream) | ||
return -1; | ||
if (typeof body2 === "string") | ||
return Buffer.byteLength(body2); | ||
if (Buffer.isBuffer(body2)) | ||
return body2.length; | ||
return Buffer.byteLength(JSON.stringify(body2)); | ||
const length = this.getHeader("Content-Length"); | ||
return length ? Number(length) : 0; | ||
} | ||
set contentLength(length) { | ||
if (length >= 0 && !this.headersSent && !this.hasHeader("Transfer-Encoding")) { | ||
if (!this.hasHeader("Transfer-Encoding")) { | ||
this.setHeader("Content-Length", length); | ||
@@ -368,9 +363,6 @@ } | ||
if (mimeType === false) { | ||
throw new Error( | ||
`Fail to parse content type from literal "${typeOrFilenameOrExt}"` | ||
); | ||
throw new Error(`\u65E0\u6CD5\u6839\u636E\u53C2\u6570"${typeOrFilenameOrExt}"\u8BBE\u7F6EContent-Type`); | ||
} | ||
if (!this.headersSent) { | ||
this.setHeader("content-type", mimeType); | ||
} | ||
this.setHeader("Content-Type", mimeType); | ||
this.determineNullBody(); | ||
} | ||
@@ -386,58 +378,42 @@ get body() { | ||
} | ||
this.determineHeaders(); | ||
} | ||
/** | ||
* 判断body是否为JSON格式 | ||
*/ | ||
isJSON(body2) { | ||
return !(!body2 || typeof body2 === "string" || body2 instanceof Stream || Buffer.isBuffer(body2)); | ||
} | ||
flush() { | ||
if (!this.writable || this.headersSent) | ||
return; | ||
const { body: body2, req } = this; | ||
const missContentType = !this.hasHeader("Content-Type"); | ||
const ok = (chunk2) => { | ||
if (req.method === "HEAD") { | ||
this.end(); | ||
this.determineHeaders(); | ||
let output = this.body; | ||
if (statuses.empty[this.statusCode]) | ||
return this.end(); | ||
if (output === null) { | ||
if (this._explicitBody) { | ||
this._body = this.findContentType("json") ? String(null) : ""; | ||
} else { | ||
this.end(chunk2); | ||
this._body = String(this.statusMessage || this.statusCode); | ||
} | ||
}; | ||
if (!this._explicitStatus) { | ||
this.statusCode = 200; | ||
this.determineHeaders(); | ||
output = this.body; | ||
} | ||
if (statuses.empty[this.statusCode]) { | ||
this.removeHeader("Content-Type"); | ||
this.removeHeader("Content-Length"); | ||
this.removeHeader("Transfer-Encoding"); | ||
return ok(); | ||
} | ||
if (body2 === null && (missContentType || !this.findContentType("json"))) { | ||
missContentType && (this.contentType = "text"); | ||
if (this._explicitBody) { | ||
this.contentLength = 0; | ||
return ok(); | ||
} | ||
const msg = req.httpVersionMajor >= 2 ? String(this.statusCode) : this.statusMessage || String(this.statusCode); | ||
this.contentLength = Buffer.byteLength(msg); | ||
return ok(msg); | ||
} | ||
if (Buffer.isBuffer(body2)) { | ||
missContentType && (this.contentType = "bin"); | ||
this.contentLength = body2.length; | ||
return ok(body2); | ||
} | ||
if (typeof body2 === "string") { | ||
missContentType && (this.contentType = /^\s*</.test(body2) ? "html" : "text"); | ||
this.contentLength = Buffer.byteLength(body2); | ||
return ok(body2); | ||
} | ||
if (body2 instanceof Stream) { | ||
if (req.method === "HEAD") | ||
return; | ||
missContentType && (this.contentType = "bin"); | ||
if (this.req.method === "HEAD") | ||
return this.end(); | ||
if (typeof output === "string") | ||
return this.end(output); | ||
if (Buffer.isBuffer(output)) | ||
return this.end(output); | ||
if (output instanceof Stream) { | ||
stream.finished(this, () => { | ||
destroy(body2); | ||
destroy(output); | ||
}); | ||
body2.once("error", this.onError).pipe(this); | ||
return; | ||
if (!output.listenerCount("error")) { | ||
output.once("error", this.onError); | ||
} | ||
return output.pipe(this); | ||
} | ||
const chunk = JSON.stringify(body2); | ||
missContentType && (this.contentType = "json"); | ||
this.contentLength = Buffer.byteLength(chunk); | ||
return ok(chunk); | ||
return this.end(JSON.stringify(output)); | ||
} | ||
@@ -447,9 +423,4 @@ onError(error) { | ||
return; | ||
let err; | ||
if (!isHttpError(error)) { | ||
err = createHttpError2(error); | ||
} else { | ||
err = error; | ||
} | ||
this.getHeaderNames().forEach(this.removeHeader.bind(this)); | ||
const err = isHttpError(error) ? error : createHttpError2(error); | ||
this.removeHeaders(...this.getHeaderNames()); | ||
err.headers && this.setHeaders(err.headers); | ||
@@ -464,6 +435,4 @@ const code = err.status || err.statusCode; | ||
} | ||
this.body = err.expose ? err.message : statuses.message[this.statusCode]; | ||
this.app.emit("error", err, this.ctx); | ||
const msg = err.expose ? err.message : statuses.message[this.statusCode]; | ||
const formatter = this.app.options.errorFormatter; | ||
this.body = formatter ? formatter(msg, this.statusCode, { err, ctx: this.ctx }) : msg; | ||
this.flush(); | ||
@@ -481,2 +450,68 @@ } | ||
} | ||
setStatus(code) { | ||
assert2(code >= 100 && code <= 999, `\u65E0\u6548\u7684\u72B6\u6001\u7801\uFF1A${code}`); | ||
this._statusCode = code; | ||
this._explicitStatus = true; | ||
if (this.req.httpVersionMajor < 2) { | ||
this.statusMessage = String(statuses.message[code]); | ||
} | ||
if (statuses.empty[code]) { | ||
this.body = null; | ||
} else { | ||
this.determineNullBody(); | ||
} | ||
} | ||
/** | ||
* 根据header设置头部信息 | ||
*/ | ||
determineHeaders() { | ||
if (this._determineHeaders) | ||
return; | ||
this._determineHeaders = true; | ||
const { body: body2 } = this; | ||
const missType = !this.hasHeader("Content-Type"); | ||
if (body2 === null) { | ||
this.determineNullBody(); | ||
} else if (typeof body2 === "string") { | ||
this.contentLength = Buffer.byteLength(body2); | ||
if (missType) { | ||
this.contentType = /^\s*</.test(body2) ? "html" : "text"; | ||
} | ||
} else if (Buffer.isBuffer(body2)) { | ||
this.contentLength = body2.length; | ||
if (missType) { | ||
this.contentType = "bin"; | ||
} | ||
} else if (body2 instanceof Stream) { | ||
if (missType) { | ||
this.contentType = "bin"; | ||
} | ||
} else { | ||
this.contentType = "json"; | ||
this.contentLength = Buffer.byteLength(JSON.stringify(body2)); | ||
} | ||
this._determineHeaders = false; | ||
} | ||
/** | ||
* 重新整理body=null和状态的关系 | ||
* - status-code | ||
* - content-type | ||
*/ | ||
determineNullBody() { | ||
if (this._determineNullBody || this.body !== null) | ||
return; | ||
this._determineNullBody = true; | ||
if (statuses.empty[this.statusCode]) { | ||
this.removeHeaders("Content-Type", "Content-Length", "Transfer-Encoding"); | ||
} else if (this.contentType === "application/json") { | ||
this.contentLength = Buffer.byteLength(String(null)); | ||
} else if (this._explicitBody) { | ||
this.contentLength = 0; | ||
if (!this.hasHeader("Content-Type")) { | ||
this.contentType = "text"; | ||
} | ||
} else { | ||
} | ||
this._determineNullBody = false; | ||
} | ||
}; | ||
@@ -489,11 +524,5 @@ | ||
this.options = options; | ||
if (options.globalChain) { | ||
this.globalChainPoint = Chain2.createPoint(options.globalChain); | ||
this.middlewareList = [options.globalChain]; | ||
} else { | ||
this.middlewareList = []; | ||
} | ||
} | ||
globalChainPoint; | ||
middlewareList; | ||
chainPoints = []; | ||
middlewareList = []; | ||
get debug() { | ||
@@ -526,3 +555,3 @@ return this.options.debug || false; | ||
console.error( | ||
["", color.bgRed(msgs.shift()), msgs.join(EOL), ""].join(EOL) | ||
["", chalk.bgRed(msgs.shift()), msgs.join(EOL), ""].join(EOL) | ||
); | ||
@@ -534,2 +563,5 @@ } | ||
mount(middleware2) { | ||
if (middleware2 instanceof Chain2) { | ||
this.chainPoints.push(Chain2.createPoint(middleware2)); | ||
} | ||
this.middlewareList.push(middleware2); | ||
@@ -638,2 +670,3 @@ } | ||
import { HttpError } from "http-errors"; | ||
import { default as default2 } from "statuses"; | ||
export { | ||
@@ -655,4 +688,5 @@ FileValidator, | ||
query, | ||
skip | ||
skip, | ||
default2 as statuses | ||
}; | ||
//# sourceMappingURL=index.js.map |
{ | ||
"name": "@aomex/web", | ||
"version": "0.0.5", | ||
"version": "0.0.6", | ||
"description": "", | ||
@@ -29,6 +29,6 @@ "type": "module", | ||
"peerDependencies": { | ||
"@aomex/core": "^0.0.4" | ||
"@aomex/core": "^0.0.5" | ||
}, | ||
"dependencies": { | ||
"@aomex/helper": "^0.0.2", | ||
"@aomex/helper": "^0.0.3", | ||
"@types/accepts": "^1.3.5", | ||
@@ -39,2 +39,3 @@ "@types/content-disposition": "^0.5.5", | ||
"@types/qs": "^6.9.7", | ||
"@types/statuses": "^2.0.1", | ||
"accepts": "^1.3.8", | ||
@@ -60,3 +61,3 @@ "co-body": "^6.1.0", | ||
"devDependencies": { | ||
"@aomex/core": "^0.0.4", | ||
"@aomex/core": "^0.0.5", | ||
"@types/co-body": "^6.1.0", | ||
@@ -71,3 +72,2 @@ "@types/content-type": "^1.1.5", | ||
"@types/request-ip": "^0.0.37", | ||
"@types/statuses": "^2.0.1", | ||
"@types/type-is": "^1.6.3", | ||
@@ -74,0 +74,0 @@ "@types/vary": "^1.1.0" |
Sorry, the diff of this file is not supported yet
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
85732
12
1003
26
+ Added@types/statuses@^2.0.1
+ Added@aomex/cache@0.0.2(transitive)
+ Added@aomex/core@0.0.5(transitive)
+ Added@aomex/helper@0.0.3(transitive)
+ Added@aomex/middleware@0.0.3(transitive)
+ Added@aomex/validator@0.0.4(transitive)
+ Added@types/statuses@2.0.5(transitive)
- Removed@aomex/core@0.0.4(transitive)
- Removed@aomex/helper@0.0.2(transitive)
- Removed@aomex/middleware@0.0.2(transitive)
- Removed@aomex/storage@0.0.1(transitive)
- Removed@aomex/validator@0.0.3(transitive)
Updated@aomex/helper@^0.0.3