starlight-server 0.0.1
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/LICENSE +21 -0
- package/README.md +1 -0
- package/dist/demo/index.d.ts +1 -0
- package/dist/demo/index.js +27 -0
- package/dist/http/body/form-data.d.ts +35 -0
- package/dist/http/body/form-data.js +141 -0
- package/dist/http/body/index.d.ts +23 -0
- package/dist/http/body/index.js +47 -0
- package/dist/http/body/receive.d.ts +7 -0
- package/dist/http/body/receive.js +39 -0
- package/dist/http/http-status.d.ts +9 -0
- package/dist/http/http-status.js +64 -0
- package/dist/http/index.d.ts +9 -0
- package/dist/http/index.js +9 -0
- package/dist/http/mime-types.d.ts +14 -0
- package/dist/http/mime-types.js +764 -0
- package/dist/http/request.d.ts +25 -0
- package/dist/http/request.js +40 -0
- package/dist/http/response.d.ts +32 -0
- package/dist/http/response.js +66 -0
- package/dist/http/server.d.ts +31 -0
- package/dist/http/server.js +52 -0
- package/dist/http/types.d.ts +26 -0
- package/dist/http/types.js +12 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.js +4 -0
- package/dist/logging.d.ts +24 -0
- package/dist/logging.js +30 -0
- package/dist/router/cors.d.ts +24 -0
- package/dist/router/cors.js +35 -0
- package/dist/router/index.d.ts +38 -0
- package/dist/router/index.js +36 -0
- package/dist/router/match.d.ts +23 -0
- package/dist/router/match.js +172 -0
- package/dist/router/parameters.d.ts +51 -0
- package/dist/router/parameters.js +118 -0
- package/dist/router/router.d.ts +127 -0
- package/dist/router/router.js +97 -0
- package/dist/swagger/index.d.ts +1 -0
- package/dist/swagger/index.js +168 -0
- package/dist/swagger/openapi-spec.d.ts +261 -0
- package/dist/swagger/openapi-spec.js +5 -0
- package/dist/validators/array.d.ts +9 -0
- package/dist/validators/array.js +28 -0
- package/dist/validators/boolean.d.ts +4 -0
- package/dist/validators/boolean.js +28 -0
- package/dist/validators/common.d.ts +23 -0
- package/dist/validators/common.js +25 -0
- package/dist/validators/index.d.ts +20 -0
- package/dist/validators/index.js +38 -0
- package/dist/validators/number.d.ts +10 -0
- package/dist/validators/number.js +30 -0
- package/dist/validators/object.d.ts +13 -0
- package/dist/validators/object.js +36 -0
- package/dist/validators/string.d.ts +11 -0
- package/dist/validators/string.js +29 -0
- package/package.json +54 -0
- package/src/demo/index.ts +33 -0
- package/src/http/body/form-data.ts +164 -0
- package/src/http/body/index.ts +59 -0
- package/src/http/body/receive.ts +49 -0
- package/src/http/http-status.ts +65 -0
- package/src/http/index.ts +9 -0
- package/src/http/mime-types.ts +765 -0
- package/src/http/request.ts +44 -0
- package/src/http/response.ts +73 -0
- package/src/http/server.ts +67 -0
- package/src/http/types.ts +31 -0
- package/src/index.ts +4 -0
- package/src/logging.ts +57 -0
- package/src/router/cors.ts +54 -0
- package/src/router/index.ts +38 -0
- package/src/router/match.ts +194 -0
- package/src/router/parameters.ts +172 -0
- package/src/router/router.ts +233 -0
- package/src/swagger/index.ts +184 -0
- package/src/swagger/openapi-spec.ts +312 -0
- package/src/validators/array.ts +33 -0
- package/src/validators/boolean.ts +23 -0
- package/src/validators/common.ts +46 -0
- package/src/validators/index.ts +50 -0
- package/src/validators/number.ts +36 -0
- package/src/validators/object.ts +41 -0
- package/src/validators/string.ts +38 -0
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
/// <reference types="node" resolution-mode="require"/>
|
|
2
|
+
import type http from 'node:http';
|
|
3
|
+
import { type Logger } from '@anjianshi/utils';
|
|
4
|
+
import { RequestBody, type BodyOptions } from './body/index.js';
|
|
5
|
+
import { type NodeRequest } from './types.js';
|
|
6
|
+
/**
|
|
7
|
+
* 对 Node.js 请求内容的二次封装
|
|
8
|
+
* - 提供经过整理的请求信息
|
|
9
|
+
* - 自带 body 解析(实现了 “body 大小限制” 和 “JSON、form-data 等格式的内容解析”)
|
|
10
|
+
*
|
|
11
|
+
* Secondary encapsulation of the Node.js Request object
|
|
12
|
+
* - Organized request information
|
|
13
|
+
* - Comes with body parsing (implements 'body size limit' and 'content parsing for formats such as JSON and form-data').
|
|
14
|
+
*/
|
|
15
|
+
export declare class Request {
|
|
16
|
+
readonly nodeRequest: NodeRequest;
|
|
17
|
+
protected readonly logger: Logger;
|
|
18
|
+
readonly method: string;
|
|
19
|
+
readonly host: string;
|
|
20
|
+
readonly path: string;
|
|
21
|
+
readonly query: URLSearchParams;
|
|
22
|
+
readonly headers: http.IncomingHttpHeaders;
|
|
23
|
+
readonly body: RequestBody;
|
|
24
|
+
constructor(nodeRequest: NodeRequest, logger: Logger, bodyOptions: BodyOptions);
|
|
25
|
+
}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { RequestBody } from './body/index.js';
|
|
2
|
+
import { HTTPError } from './types.js';
|
|
3
|
+
/**
|
|
4
|
+
* 对 Node.js 请求内容的二次封装
|
|
5
|
+
* - 提供经过整理的请求信息
|
|
6
|
+
* - 自带 body 解析(实现了 “body 大小限制” 和 “JSON、form-data 等格式的内容解析”)
|
|
7
|
+
*
|
|
8
|
+
* Secondary encapsulation of the Node.js Request object
|
|
9
|
+
* - Organized request information
|
|
10
|
+
* - Comes with body parsing (implements 'body size limit' and 'content parsing for formats such as JSON and form-data').
|
|
11
|
+
*/
|
|
12
|
+
export class Request {
|
|
13
|
+
nodeRequest;
|
|
14
|
+
logger;
|
|
15
|
+
method;
|
|
16
|
+
host;
|
|
17
|
+
path;
|
|
18
|
+
query;
|
|
19
|
+
headers;
|
|
20
|
+
body;
|
|
21
|
+
constructor(nodeRequest, logger, bodyOptions) {
|
|
22
|
+
this.nodeRequest = nodeRequest;
|
|
23
|
+
this.logger = logger;
|
|
24
|
+
if (nodeRequest.method === undefined)
|
|
25
|
+
throw new HTTPError(405);
|
|
26
|
+
this.method = nodeRequest.method;
|
|
27
|
+
try {
|
|
28
|
+
const url = new URL(nodeRequest.url ?? '', `http://${nodeRequest.headers.host ?? ''}`);
|
|
29
|
+
this.host = url.host;
|
|
30
|
+
this.path = url.pathname;
|
|
31
|
+
this.query = url.searchParams;
|
|
32
|
+
}
|
|
33
|
+
catch (e) {
|
|
34
|
+
this.logger.warn('parse url failed', e);
|
|
35
|
+
throw new HTTPError(400, 'url invalid');
|
|
36
|
+
}
|
|
37
|
+
this.headers = nodeRequest.headers;
|
|
38
|
+
this.body = new RequestBody(nodeRequest, bodyOptions);
|
|
39
|
+
}
|
|
40
|
+
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
/// <reference types="node" resolution-mode="require"/>
|
|
2
|
+
import { type Logger } from '@anjianshi/utils';
|
|
3
|
+
import { type NodeResponse } from './types.js';
|
|
4
|
+
/**
|
|
5
|
+
* 封装响应内容输出函数
|
|
6
|
+
* Encapsulate functions for outputting response content.
|
|
7
|
+
*/
|
|
8
|
+
export declare class ResponseUtils {
|
|
9
|
+
readonly nodeResponse: NodeResponse;
|
|
10
|
+
readonly logger: Logger;
|
|
11
|
+
constructor(nodeResponse: NodeResponse, logger: Logger);
|
|
12
|
+
header(name: string, value: string): void;
|
|
13
|
+
headers(...items: [string, string][]): void;
|
|
14
|
+
/**
|
|
15
|
+
* Output CORS Headers
|
|
16
|
+
*/
|
|
17
|
+
cors(allowOrigin?: string, allowHeaders?: string): void;
|
|
18
|
+
text(content: string | Buffer): void;
|
|
19
|
+
/**
|
|
20
|
+
* Output JSON
|
|
21
|
+
*/
|
|
22
|
+
json(data: unknown): void;
|
|
23
|
+
/**
|
|
24
|
+
* 原样输出文件内容,并带上文件类型对应的 MIME Type
|
|
25
|
+
* Output the file content and include the MIME Type corresponding to the file type.
|
|
26
|
+
*/
|
|
27
|
+
file(content: Buffer | string, path: string): void;
|
|
28
|
+
/**
|
|
29
|
+
* Output HTTP Error
|
|
30
|
+
*/
|
|
31
|
+
error(error: unknown): void;
|
|
32
|
+
}
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import { path2MIMEType } from './mime-types.js';
|
|
2
|
+
import { HTTPError } from './types.js';
|
|
3
|
+
/**
|
|
4
|
+
* 封装响应内容输出函数
|
|
5
|
+
* Encapsulate functions for outputting response content.
|
|
6
|
+
*/
|
|
7
|
+
export class ResponseUtils {
|
|
8
|
+
nodeResponse;
|
|
9
|
+
logger;
|
|
10
|
+
constructor(nodeResponse, logger) {
|
|
11
|
+
this.nodeResponse = nodeResponse;
|
|
12
|
+
this.logger = logger;
|
|
13
|
+
}
|
|
14
|
+
header(name, value) {
|
|
15
|
+
this.nodeResponse.setHeader(name, value);
|
|
16
|
+
}
|
|
17
|
+
headers(...items) {
|
|
18
|
+
items.forEach(([name, value]) => this.header(name, value));
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* Output CORS Headers
|
|
22
|
+
*/
|
|
23
|
+
cors(allowOrigin = '*', allowHeaders = '*') {
|
|
24
|
+
this.headers(['Access-Control-Allow-Origin', allowOrigin], ['Access-Control-Allow-Headers', allowHeaders]);
|
|
25
|
+
}
|
|
26
|
+
text(content) {
|
|
27
|
+
this.nodeResponse.end(content);
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* Output JSON
|
|
31
|
+
*/
|
|
32
|
+
json(data) {
|
|
33
|
+
try {
|
|
34
|
+
const json = JSON.stringify(data);
|
|
35
|
+
this.header('Content-Type', 'application/json; charset=UTF-8');
|
|
36
|
+
this.nodeResponse.end(json);
|
|
37
|
+
}
|
|
38
|
+
catch (e) {
|
|
39
|
+
throw new HTTPError(500, 'Invalid JSON Response');
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* 原样输出文件内容,并带上文件类型对应的 MIME Type
|
|
44
|
+
* Output the file content and include the MIME Type corresponding to the file type.
|
|
45
|
+
*/
|
|
46
|
+
file(content, path) {
|
|
47
|
+
const mimeType = path2MIMEType(path);
|
|
48
|
+
if (mimeType !== null)
|
|
49
|
+
this.header('Content-Type', mimeType);
|
|
50
|
+
this.nodeResponse.end(content);
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* Output HTTP Error
|
|
54
|
+
*/
|
|
55
|
+
error(error) {
|
|
56
|
+
if (error instanceof HTTPError) {
|
|
57
|
+
this.nodeResponse.statusCode = error.status; // eslint-disable-line require-atomic-updates
|
|
58
|
+
this.nodeResponse.end(error.message);
|
|
59
|
+
}
|
|
60
|
+
else {
|
|
61
|
+
this.logger.error(error);
|
|
62
|
+
this.nodeResponse.statusCode = 500; // eslint-disable-line require-atomic-updates
|
|
63
|
+
this.nodeResponse.end(new HTTPError(500).message);
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { type Logger } from '@anjianshi/utils';
|
|
2
|
+
import { type BodyOptions } from './body/index.js';
|
|
3
|
+
import { Request } from './request.js';
|
|
4
|
+
import { ResponseUtils } from './response.js';
|
|
5
|
+
export type RequestHandler = (request: Request, response: ResponseUtils) => unknown;
|
|
6
|
+
/**
|
|
7
|
+
* 二次封装 Node.js HTTP Server,实现了更完善的 request 和 response 处理
|
|
8
|
+
* Secondary encapsulation of the Node.js HTTP Server, with more complete request and response handling.
|
|
9
|
+
*
|
|
10
|
+
* 功能点:
|
|
11
|
+
* - 经过整理的 request 信息,且自带 body 解析
|
|
12
|
+
* - 快速输出指定格式的响应内容
|
|
13
|
+
* - 自带异常处理
|
|
14
|
+
* - 任意处抛出 HTTPError,可以指定 HTTP Status 结束请求
|
|
15
|
+
* - 抛出其他 Error,则以 Status 500 结束请求
|
|
16
|
+
*
|
|
17
|
+
* Features:
|
|
18
|
+
* - Organized request information, and built-in body parsing
|
|
19
|
+
* - Quickly output response content in a specified format
|
|
20
|
+
* - Built-in exception handling
|
|
21
|
+
* - Throw HTTPError anywhere, and end the request with the specified HTTP Status
|
|
22
|
+
* - If another Error is thrown, end the request with Status 500
|
|
23
|
+
*
|
|
24
|
+
* Usage:
|
|
25
|
+
* startHTTPServer(handler, options)
|
|
26
|
+
*/
|
|
27
|
+
export declare function startHTTPServer(options: BodyOptions & {
|
|
28
|
+
handler?: RequestHandler;
|
|
29
|
+
logger: Logger;
|
|
30
|
+
port: number;
|
|
31
|
+
}): void;
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import http from 'node:http';
|
|
2
|
+
import { LogLevel } from '@anjianshi/utils';
|
|
3
|
+
import { Request } from './request.js';
|
|
4
|
+
import { ResponseUtils } from './response.js';
|
|
5
|
+
/**
|
|
6
|
+
* 二次封装 Node.js HTTP Server,实现了更完善的 request 和 response 处理
|
|
7
|
+
* Secondary encapsulation of the Node.js HTTP Server, with more complete request and response handling.
|
|
8
|
+
*
|
|
9
|
+
* 功能点:
|
|
10
|
+
* - 经过整理的 request 信息,且自带 body 解析
|
|
11
|
+
* - 快速输出指定格式的响应内容
|
|
12
|
+
* - 自带异常处理
|
|
13
|
+
* - 任意处抛出 HTTPError,可以指定 HTTP Status 结束请求
|
|
14
|
+
* - 抛出其他 Error,则以 Status 500 结束请求
|
|
15
|
+
*
|
|
16
|
+
* Features:
|
|
17
|
+
* - Organized request information, and built-in body parsing
|
|
18
|
+
* - Quickly output response content in a specified format
|
|
19
|
+
* - Built-in exception handling
|
|
20
|
+
* - Throw HTTPError anywhere, and end the request with the specified HTTP Status
|
|
21
|
+
* - If another Error is thrown, end the request with Status 500
|
|
22
|
+
*
|
|
23
|
+
* Usage:
|
|
24
|
+
* startHTTPServer(handler, options)
|
|
25
|
+
*/
|
|
26
|
+
export function startHTTPServer(options) {
|
|
27
|
+
const { handler = placeholderHandler, logger, port, ...bodyOptions } = options;
|
|
28
|
+
async function handleRequest(nodeRequest, nodeResponse) {
|
|
29
|
+
const logStart = Date.now();
|
|
30
|
+
const logMethod = nodeRequest.method ?? 'UNKNOWN';
|
|
31
|
+
const logUrl = nodeRequest.url ?? '';
|
|
32
|
+
logger.info('<--', logMethod, logUrl);
|
|
33
|
+
const response = new ResponseUtils(nodeResponse, logger);
|
|
34
|
+
try {
|
|
35
|
+
const request = new Request(nodeRequest, logger, bodyOptions);
|
|
36
|
+
await handler(request, response);
|
|
37
|
+
}
|
|
38
|
+
catch (error) {
|
|
39
|
+
response.error(error);
|
|
40
|
+
}
|
|
41
|
+
const logUsage = Date.now() - logStart;
|
|
42
|
+
const logStatus = nodeResponse.statusCode;
|
|
43
|
+
const logLevel = logStatus >= 400 ? LogLevel.Warning : LogLevel.Info;
|
|
44
|
+
logger.log(logLevel, ['-->', logMethod, logUrl, logStatus, `${logUsage}ms`]);
|
|
45
|
+
}
|
|
46
|
+
const server = http.createServer(handleRequest);
|
|
47
|
+
logger.info('Listening', port);
|
|
48
|
+
server.listen(port);
|
|
49
|
+
}
|
|
50
|
+
function placeholderHandler(request, response) {
|
|
51
|
+
response.json({ message: 'Hello World, from starlight.' });
|
|
52
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
/// <reference types="node" resolution-mode="require"/>
|
|
2
|
+
/**
|
|
3
|
+
* 重新定义 HTTP 请求相关类型
|
|
4
|
+
* Redefine HTTP request-related types.
|
|
5
|
+
*/
|
|
6
|
+
import type http from 'node:http';
|
|
7
|
+
/**
|
|
8
|
+
* Node.js 原生的请求对象
|
|
9
|
+
* Node.js native request object
|
|
10
|
+
*/
|
|
11
|
+
export type NodeRequest = http.IncomingMessage;
|
|
12
|
+
/**
|
|
13
|
+
* Node.js 原生的响应对象
|
|
14
|
+
* Node.js native response object
|
|
15
|
+
*/
|
|
16
|
+
export type NodeResponse = http.ServerResponse & {
|
|
17
|
+
req: http.IncomingMessage;
|
|
18
|
+
};
|
|
19
|
+
/**
|
|
20
|
+
* 带有 HTTP Status 的错误对象
|
|
21
|
+
* Error object with HTTP Status
|
|
22
|
+
*/
|
|
23
|
+
export declare class HTTPError extends Error {
|
|
24
|
+
readonly status: number;
|
|
25
|
+
constructor(status: number, message?: string);
|
|
26
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { HTTPStatusMap } from './http-status.js';
|
|
2
|
+
/**
|
|
3
|
+
* 带有 HTTP Status 的错误对象
|
|
4
|
+
* Error object with HTTP Status
|
|
5
|
+
*/
|
|
6
|
+
export class HTTPError extends Error {
|
|
7
|
+
status;
|
|
8
|
+
constructor(status, message) {
|
|
9
|
+
super(message ?? HTTPStatusMap.get(status) ?? status.toString());
|
|
10
|
+
this.status = status;
|
|
11
|
+
}
|
|
12
|
+
}
|
package/dist/index.d.ts
ADDED
package/dist/index.js
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 实现日志记录
|
|
3
|
+
*/
|
|
4
|
+
import { Logger } from '@anjianshi/utils';
|
|
5
|
+
import { type FileHandlerOptions } from '@anjianshi/utils/env-node/logging.js';
|
|
6
|
+
export type { FileHandlerOptions };
|
|
7
|
+
export interface LoggingOptions {
|
|
8
|
+
/**
|
|
9
|
+
* 指定日志等级(debug info warn err)
|
|
10
|
+
* Specify log level.
|
|
11
|
+
*/
|
|
12
|
+
level?: string;
|
|
13
|
+
/**
|
|
14
|
+
* 文件日志参数
|
|
15
|
+
* File log options
|
|
16
|
+
*/
|
|
17
|
+
file?: Partial<FileHandlerOptions>;
|
|
18
|
+
/**
|
|
19
|
+
* 是否把 debug 库的日志引入进来;可通过字符串指定匹配规则
|
|
20
|
+
* Whether to import logs from 'debug' library; can specify matching rules through strings
|
|
21
|
+
*/
|
|
22
|
+
debugLib?: boolean | string;
|
|
23
|
+
}
|
|
24
|
+
export declare function getLogger(options?: LoggingOptions): Logger;
|
package/dist/logging.js
ADDED
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 实现日志记录
|
|
3
|
+
*/
|
|
4
|
+
import { truthy, Logger, LogLevel, adaptDebugLib } from '@anjianshi/utils';
|
|
5
|
+
import { ConsoleHandler, FileHandler, } from '@anjianshi/utils/env-node/logging.js';
|
|
6
|
+
import debug from 'debug';
|
|
7
|
+
const levelMap = {
|
|
8
|
+
debug: LogLevel.Debug,
|
|
9
|
+
info: LogLevel.Info,
|
|
10
|
+
warn: LogLevel.Warning,
|
|
11
|
+
warning: LogLevel.Warning,
|
|
12
|
+
err: LogLevel.Error,
|
|
13
|
+
error: LogLevel.Error,
|
|
14
|
+
};
|
|
15
|
+
export function getLogger(options = {}) {
|
|
16
|
+
const logger = new Logger();
|
|
17
|
+
logger.addHandler(new ConsoleHandler());
|
|
18
|
+
if (options.level !== undefined) {
|
|
19
|
+
const level = levelMap[options.level.toLowerCase()];
|
|
20
|
+
if (level !== undefined)
|
|
21
|
+
logger.setLevel(level);
|
|
22
|
+
}
|
|
23
|
+
if (options.file) {
|
|
24
|
+
logger.addHandler(new FileHandler(options.file));
|
|
25
|
+
}
|
|
26
|
+
if (truthy(options.debugLib)) {
|
|
27
|
+
void adaptDebugLib(debug, options.debugLib === true ? '*' : options.debugLib, logger);
|
|
28
|
+
}
|
|
29
|
+
return logger;
|
|
30
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { type Request, type ResponseUtils } from '../http/index.js';
|
|
2
|
+
/**
|
|
3
|
+
* CORS 配置
|
|
4
|
+
*
|
|
5
|
+
* - false: 不处理跨域
|
|
6
|
+
* - true: 支持且不设限(等同于 { allowOrigin: '*', allowHeaders: '*', exposeHeaders: '*' })
|
|
7
|
+
* - { allowOrigin, allowHeaders, exposeHeaders }: 手动配置
|
|
8
|
+
*
|
|
9
|
+
* allowHeaders 仅对 Preflight 请求有效
|
|
10
|
+
* exposeHeaders 仅对非 Preflight 请求有效
|
|
11
|
+
*/
|
|
12
|
+
export type CORSOptions = boolean | {
|
|
13
|
+
allowOrigin?: string;
|
|
14
|
+
allowHeaders?: string;
|
|
15
|
+
exposeHeaders?: string;
|
|
16
|
+
};
|
|
17
|
+
/**
|
|
18
|
+
* 若当前请求是 CORS Preflight 请求,返回实际请求的 method,否则返回 null
|
|
19
|
+
*/
|
|
20
|
+
export declare function getMethodFromCORSPreflight(request: Request): string | null;
|
|
21
|
+
/**
|
|
22
|
+
* 输出 CORS 相关 headers
|
|
23
|
+
*/
|
|
24
|
+
export declare function outputCORSHeaders(response: ResponseUtils, cors: CORSOptions, isPreflight: boolean): void;
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 若当前请求是 CORS Preflight 请求,返回实际请求的 method,否则返回 null
|
|
3
|
+
*/
|
|
4
|
+
export function getMethodFromCORSPreflight(request) {
|
|
5
|
+
if (request.method === 'OPTIONS' &&
|
|
6
|
+
request.headers['Access-Control-Request-Methods'] !== undefined) {
|
|
7
|
+
const rawMethod = request.headers['Access-Control-Request-Methods'];
|
|
8
|
+
return Array.isArray(rawMethod) ? rawMethod[0] : rawMethod;
|
|
9
|
+
}
|
|
10
|
+
return null;
|
|
11
|
+
}
|
|
12
|
+
/**
|
|
13
|
+
* 输出 CORS 相关 headers
|
|
14
|
+
*/
|
|
15
|
+
export function outputCORSHeaders(response, cors, isPreflight) {
|
|
16
|
+
if (cors === true) {
|
|
17
|
+
response.headers(['Access-Control-Allow-Origin', '*']);
|
|
18
|
+
if (isPreflight)
|
|
19
|
+
response.headers(['Access-Control-Allow-Headers', '*']);
|
|
20
|
+
else
|
|
21
|
+
response.headers(['Access-Control-Expose-Headers', '*']);
|
|
22
|
+
}
|
|
23
|
+
else if (cors !== false) {
|
|
24
|
+
if (cors.allowOrigin !== undefined)
|
|
25
|
+
response.header('Access-Control-Allow-Origin', cors.allowOrigin);
|
|
26
|
+
if (isPreflight && cors.allowHeaders !== undefined)
|
|
27
|
+
response.header('Access-Control-Allow-Headers', cors.allowHeaders);
|
|
28
|
+
if (!isPreflight && cors.exposeHeaders !== undefined)
|
|
29
|
+
response.header('Access-Control-Expose-Headers', cors.exposeHeaders);
|
|
30
|
+
}
|
|
31
|
+
else {
|
|
32
|
+
// 若指定为“不处理跨域”,则不输出 headers,浏览器会默认服务端不允许跨域请求
|
|
33
|
+
// (后续 route handler 仍有机会自行设置 CORS headers)
|
|
34
|
+
}
|
|
35
|
+
}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 实现路由注册
|
|
3
|
+
* Implement route registration
|
|
4
|
+
*
|
|
5
|
+
* Usage:
|
|
6
|
+
* const router = new DefaultRouter()
|
|
7
|
+
* router.register('GET', '/hello/:name', async ({ request, response }: { request: Request, response: ResponseUtils }, params: { name: string }) => {
|
|
8
|
+
* response.json({ hello: params.name })
|
|
9
|
+
* })
|
|
10
|
+
* startHTTPServer({
|
|
11
|
+
* handler: router.handle
|
|
12
|
+
* ...
|
|
13
|
+
* })
|
|
14
|
+
*
|
|
15
|
+
* Usage With Custom Context:
|
|
16
|
+
* interface MyContext extends BaseContext {
|
|
17
|
+
* foo: () => void
|
|
18
|
+
* }
|
|
19
|
+
* class MyRouter extends BaseRouter<MyContext> {
|
|
20
|
+
* executeWithContext(baseContext: BaseContext, route: Route<MyContext>, params: PathParamaters) {
|
|
21
|
+
* function foo() { console.log('bar') }
|
|
22
|
+
* const context = { ...baseContext, foo }
|
|
23
|
+
* route.handle(context)
|
|
24
|
+
* }
|
|
25
|
+
* }
|
|
26
|
+
* const router = new MyRouter()
|
|
27
|
+
* router.register('GET', '/foobar', async ({ request, response, foo }) => {
|
|
28
|
+
* foo()
|
|
29
|
+
* response.json({})
|
|
30
|
+
* })
|
|
31
|
+
* startHTTPServer({
|
|
32
|
+
* handler: router.handle
|
|
33
|
+
* ...
|
|
34
|
+
* })
|
|
35
|
+
*/
|
|
36
|
+
export * from './router.js';
|
|
37
|
+
export { type PathParameters } from './match.js';
|
|
38
|
+
export type { BasicDataType, Parameter, ParameterDataType } from './parameters.js';
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 实现路由注册
|
|
3
|
+
* Implement route registration
|
|
4
|
+
*
|
|
5
|
+
* Usage:
|
|
6
|
+
* const router = new DefaultRouter()
|
|
7
|
+
* router.register('GET', '/hello/:name', async ({ request, response }: { request: Request, response: ResponseUtils }, params: { name: string }) => {
|
|
8
|
+
* response.json({ hello: params.name })
|
|
9
|
+
* })
|
|
10
|
+
* startHTTPServer({
|
|
11
|
+
* handler: router.handle
|
|
12
|
+
* ...
|
|
13
|
+
* })
|
|
14
|
+
*
|
|
15
|
+
* Usage With Custom Context:
|
|
16
|
+
* interface MyContext extends BaseContext {
|
|
17
|
+
* foo: () => void
|
|
18
|
+
* }
|
|
19
|
+
* class MyRouter extends BaseRouter<MyContext> {
|
|
20
|
+
* executeWithContext(baseContext: BaseContext, route: Route<MyContext>, params: PathParamaters) {
|
|
21
|
+
* function foo() { console.log('bar') }
|
|
22
|
+
* const context = { ...baseContext, foo }
|
|
23
|
+
* route.handle(context)
|
|
24
|
+
* }
|
|
25
|
+
* }
|
|
26
|
+
* const router = new MyRouter()
|
|
27
|
+
* router.register('GET', '/foobar', async ({ request, response, foo }) => {
|
|
28
|
+
* foo()
|
|
29
|
+
* response.json({})
|
|
30
|
+
* })
|
|
31
|
+
* startHTTPServer({
|
|
32
|
+
* handler: router.handle
|
|
33
|
+
* ...
|
|
34
|
+
* })
|
|
35
|
+
*/
|
|
36
|
+
export * from './router.js';
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 标准化路径
|
|
3
|
+
* 标准化后,相同的两个路径一定也是相同的字符串(例如 /Abc/Def 和 abc/def/ 都会变成 abc/def)。
|
|
4
|
+
*
|
|
5
|
+
* - 移除首尾和重复的 '/',完成后有 path 有这几种可能的格式: ''、'abc'、'abc/def'
|
|
6
|
+
* - 统一改为小写
|
|
7
|
+
*/
|
|
8
|
+
export declare function normalizePath(path: string): string;
|
|
9
|
+
/**
|
|
10
|
+
* 返回匹配的路由及匹配上的路径参数
|
|
11
|
+
*/
|
|
12
|
+
export declare function matchRoutes<R extends BasicRoute>(requestPath: string, routes: R[]): RouteMatch<R>[];
|
|
13
|
+
export type BasicRoute = {
|
|
14
|
+
path: string;
|
|
15
|
+
};
|
|
16
|
+
export interface RouteMatch<R> {
|
|
17
|
+
route: R;
|
|
18
|
+
pathParameters: PathParameters;
|
|
19
|
+
}
|
|
20
|
+
export type PathParameters = {
|
|
21
|
+
[name: string]: string | undefined;
|
|
22
|
+
'*'?: string;
|
|
23
|
+
};
|