HTTP
处理HTTP查询有时是服务器最著名的任务。它将输入(HTTP请求)转换为输出(HTTP响应),并执行一项特定的任务。客户端可以通过HTTP请求以各种方式向服务器发送数据,这些数据必须被正确读取和处理。除了HTTP主体外,HTTP查询或HTTP头值也是可能的。数据如何被实际处理取决于服务器。
这里的首要任务不仅是正确执行用户期望的内容,而且要正确转换(反序列化)和验证来自HTTP请求的任何输入。
HTTP请求在服务器上通过的管道可能是多样化和复杂的。许多简单的HTTP库只是简单地传递给定路由的HTTP请求和HTTP响应,并期望开发者直接处理HTTP响应。一个中间件API允许管道根据需要进行扩展。
Express Example
const http = express();
http.get('/user/:id', (request, response) => {
response.send({id: request.params.id, username: 'Peter' );
});
这对简单的用例来说是非常合适的,但随着应用程序的增长,很快就会变得混乱,因为所有输入和输出都必须手动序列化或反序列化并进行验证。还必须考虑到如何从应用程序本身获得对象和服务,如数据库抽象。
Deepkit的HTTP库利用了TypeScript和依赖注入的力量,迫使开发者在上面放置一个架构,以映射这些强制性功能。串行化/反串行化和任何值的验证都是根据定义的类型自动发生的。此外,它允许通过功能API(如上面的例子)或通过控制器类来定义路由,以涵盖架构的不同需求。
它还允许你通过一个功能性的API来定义路由,就像上面的例子一样,或者通过控制器类来覆盖所选架构的不同需求。这两种API变体都可以访问依赖性注入容器,因此可以方便地从应用程序中获得数据库抽象和配置等对象。
Deepkit 示例
import { Positive } from '@deepkit/type';
import { http } from '@deepkit/http';
//Functional API
router.get('/user/:id', (id: number & Positive, database: Database) => {
//id is guaranteed to be a number and positive.
//database is injected by the DI Container.
return database.query(User).filter({id}).findOne();
});
//Controller API
class UserController {
constructor(private database: Database) {}
@http.GET('/user/:id')
user(id: number & Positive) {
return this.database.query(User).filter({id}).findOne();
}
}
Installation
由于Deepkit中的CLI程序是基于运行时类型的,因此有必要正确安装`@deepkit/type`。参见运行时类型安装。
如果成功完成,可以安装@deepkit/app或Deepkit框架,该框架已经在引擎盖下使用该库。
npm install @deepkit/http
请注意,控制器API的`@deepkit/http`是基于TypeScript装饰器的,一旦使用控制器API,就必须用`experimentalDecorators`相应地启用这一功能。
文件:tsconfig.json
{
"compilerOptions": {
"module": "CommonJS",
"target": "es6",
"moduleResolution": "node",
"experimentalDecorators": true
},
"reflection": true
}
一旦库被安装,可以直接使用其中的API。
功能API
功能API是基于函数的,可以通过路由器注册表注册,可以通过应用程序的DI容器获得。
import { App } from '@deepkit/app';
import { FrameworkModule } from '@deepkit/framework';
import { HttpRouterRegistry } from '@deepkit/http';
const app = new App({
imports: [new FrameworkModule]
});
const router = app.get(HttpRouterRegistry);
router.get('/', () => {
return "Hello World!";
});
app.run();
路由器注册表也可以在事件监听器或引导中获得,这样就可以根据模块、配置和其他提供者注册各种路由。
import { App } from '@deepkit/app';
import { FrameworkModule } from '@deepkit/framework';
const app = new App({
bootstrap: (router: HttpRouterRegistry) => {
router.get('/', () => {
return "Hello World!";
});
},
imports: [new FrameworkModule]
});
一旦使用了模块,功能路由也可以由模块动态提供。
import { App, createModule } from '@deepkit/app';
import { FrameworkModule } from '@deepkit/framework';
import { HttpRouterRegistry } from '@deepkit/http';
class MyModule extends createModule({}) {
override process() {
const router = this.setupGlobalProvider(HttpRouterRegistry);
router.get('/', () => {
return "Hello World!";
});
}
}
const app = new App({
imports: [new FrameworkModule, new MyModule]
});
参见框架模块以了解更多关于应用模块的信息。
控制器API
控制器API基于类,可以通过App API在`controllers`选项下注册。
import { App } from '@deepkit/app';
import { FrameworkModule } from '@deepkit/framework';
import { http } from '@deepkit/http';
class MyPage {
@http.GET('/')
helloWorld() {
return "Hello World!";
}
}
new App({
controllers: [MyPage],
imports: [new FrameworkModule]
}).run();
一旦使用了模块,控制器也可以由模块提供。
import { App, createModule } from '@deepkit/app';
import { FrameworkModule } from '@deepkit/framework';
import { http } from '@deepkit/http';
class MyPage {
@http.GET('/')
helloWorld() {
return "Hello World!";
}
}
class MyModule extends createModule({
controllers: [MyPage]
}) {
}
const app = new App({
imports: [new FrameworkModule, new MyModule]
});
为了动态地提供控制器(例如,根据配置选项),可以使用`process`钩子。
class MyModuleConfiguration {
debug: boolean = false;
}
class MyModule extends createModule({
config: MyModuleConfiguration
}) {
override process() {
if (this.config.debug) {
class DebugController {
@http.GET('/debug/')
root() {
return 'Hello Debugger';
}
}
this.addController(DebugController);
}
}
}
参见框架模块以了解更多关于应用模块的信息。
HTTP服务器
如果使用Deepkit Framework,已经内置了一个HTTP服务器。然而,HTTP库也可以在不使用Deepkit框架的情况下使用自己的HTTP服务器。
import { Server } from 'http';
import { HttpRequest, HttpResponse } from '@deepkit/http';
const app = new App({
controllers: [MyPage],
imports: [new HttpModule]
});
const httpKernel = app.get(HttpKernel);
new Server(
{ IncomingMessage: HttpRequest, ServerResponse: HttpResponse, },
((req, res) => {
httpKernel.handleRequest(req as HttpRequest, res as HttpResponse);
})
).listen(8080, () => {
console.log('listen at 8080');
});
Route Names
Routes can be given a unique name that can be referenced in a redirect.
//functional API
router.get({
path: '/user/:id',
name: 'userDetail'
}, (id: number) => {
return {userId: id};
});
//controller API
class UserController {
@http.GET('/user/:id').name('userDetail')
userDetail(id: number) {
return {userId: id};
}
}
从所有有名字的路由中,可以通过`Router.resolveUrl()`来请求URL。
import { HttpRouter } from '@deepkit/http';
const router = app.get(HttpRouter);
router.resolveUrl('userDetail', {id: 2}); //=> '/user/2'
依赖注入
路由器函数以及控制器类和控制器方法可以定义任意的依赖关系,由依赖注入容器来解决。
例如,如果一个数据库作为提供者被提供,它可以被注入:
class Database {
//...
}
const app = new App({
providers: [
Database,
],
});
功能型API:
router.get('/user/:id', async (id: number, database: Database) => {
return await database.query(User).filter({id}).findOne();
});
控制器API:
class UserController {
constructor(private database: Database) {}
@http.GET('/user/:id')
async userDetail(id: number) {
return await this.database.query(User).filter({id}).findOne();
}
}
//alternatively directly in the method
class UserController {
@http.GET('/user/:id')
async userDetail(id: number, database: Database) {
return await database.query(User).filter({id}).findOne();
}
}
更多信息见依赖注入。
输入
以下所有的输入变化对功能型和控制器API的功能都是一样的。 这不仅大大提高了安全性,而且还简化了单元测试, 因为严格来说,为了测试路由,甚至不需要存在一个HTTP请求对象。
为了简单起见,下面展示了所有带有功能API的例子。
路径参数
路径参数是从路由的URL中提取的值。 这种转换是通过软类型转换这一特性自动完成的。
router.get('/:text', (text: string) => {
return 'Hello ' + text;
});
$ curl http://localhost:8080/galaxy
Hello galaxy
如果一个路径参数被定义为字符串以外的类型,它将被正确转换。
router.get('/user/:id', (id: number) => {
return `${id} ${typeof id}`;
});
$ curl http://localhost:8080/user/23
23 number
额外的验证约束也可以应用于该类型。
import { Positive } from '@deepkit/type';
router.get('/user/:id', (id: number & Positive) => {
return `${id} ${typeof id}`;
});
可以应用`@deepkit/type`中的所有验证类型。更多相关信息请见 HTTP Validation。
路径参数在URL匹配时默认设置为`[^/]+`正则表达式。这方面的正则表达式可以调整如下:
import { HttpRegExp } from '@deepkit/http';
import { Positive } from '@deepkit/type';
router.get('/user/:id', (id: HttpRegExp<number & Positive, '[0-9]+'>) => {
return `${id} ${typeof id}`;
});
这只有在特殊情况下才需要,因为通常情况下,与验证类型本身相结合的类型已经正确地限制了可能的值。
查询参数
查询参数是URL中`?`字符后面的值,可以用`HttpQuery<T>`类型读取。
import { HttpQuery } from '@deepkit/http';
router.get('/', (text: HttpQuery<number>) => {
return `Hello ${text}`;
});
$ curl http://localhost:8080/\?text\=galaxy
Hello galaxy
查询参数的名称与查询参数的名称相对应。
import { HttpQuery } from '@deepkit/http';
import { MinLength } from '@deepkit/type';
router.get('/', (text: HttpQuery<string> & MinLength<3>) => {
return 'Hello ' + text;
}
$ curl http://localhost:8080/\?text\=galaxy
Hello galaxy
$ curl http://localhost:8080/\?text\=ga
error
可以应用`@deepkit/type`中的所有验证类型。关于这一点,请参阅HTTP Validation。
警告。参数值没有被转义/消毒。在路由中直接以字符串的形式将它们作为HTML返回,会打开一个安全漏洞(XSS)。确保从不信任外部输入,并在必要时过滤/净化/转换数据。
查询模型
由于有很多查询参数,很快就会变得混乱。
import { HttpQueries } from '@deepkit/http';
class HelloWorldQuery {
text!: string;
page: number = 0;
}
router.get('/', (query: HttpQueries<HelloWorldQuery>) {
return 'Hello ' + query.text + ' at page ' + query.page;
}
$ curl http://localhost:8080/\?text\=galaxy&page=1
Hello galaxy at page 1
Body
对于允许HTTP主体的HTTP方法,也可以指定一个主体模型。
来自HTTP请求的body内容类型必须是`application/x-www-form-urlencoded`、multipart/form-data`或`application/json
,以便Deepkit自动将其转换为JavaScript对象。
import { HttpBody } from '@deepkit/type';
class HelloWorldBody {
text!: string;
}
router.post('/', (body: HttpBody<HelloWorldBody>) => {
return 'Hello ' + body.text;
}
Stream
手动验证处理
为了手动处理body模型的验证,可以使用一个特殊类型`HttpBodyValidation<T>`。它允许接收无效的主体数据,并对错误信息作出非常具体的反应。
import { HttpBodyValidation } from '@deepkit/type';
class HelloWorldBody {
text!: string;
}
router.post('/', (body: HttpBodyValidation<HelloWorldBody>) => {
if (!body.valid()) {
// Houston, we got some errors.
const textError = body.getErrorMessageForPath('text');
return 'Text is invalid, please fix it. ' + textError;
}
return 'Hello ' + body.text;
})
一旦`valid()返回`false
,指定模型中的值可能处于不正确状态。这意味着验证失败。如果没有使用`HttpBodyValidation`,并且收到了一个不正确的HTTP请求,该请求将被直接中止,函数中的代码将永远不会被执行。
文件上传
主体模型上的一个特殊的属性类型可以用来允许客户端上传文件。可以使用任何数量的`UploadedFile`。
import { UploadedFile, HttpBody } from '@deepkit/http';
import { readFileSync } from 'fs';
class HelloWordBody {
file!: UploadedFile;
}
router.post('/', (body: HttpBody<HelloWordBody>) => {
const content = readFileSync(body.file.path);
return {
uploadedFile: body.file
};
})
$ curl http://localhost:8080/ -X POST -H "Content-Type: multipart/form-data" -F "[email protected]/23931.png"
{
"uploadedFile": {
"size":6430,
"path":"/var/folders/pn/40jxd3dj0fg957gqv_nhz5dw0000gn/T/upload_dd0c7241133326bf6afddc233e34affa",
"name":"23931.png",
"type":"image/png",
"lastModifiedDate":"2021-06-11T19:19:14.775Z"
}
}
默认情况下,路由器将所有上传的文件存储在一个临时文件夹中,一旦路由中的代码被执行,就将其删除。因此,有必要读取`path`中指定路径的文件,并将其保存到一个永久的位置(本地硬盘、云存储、数据库)。
验证
HTTP服务器中的验证是一个强制性的功能,因为不值得信任的数据几乎总是被处理的。数据验证的地方越多,服务器就越稳定。HTTP路由中的验证可以方便地通过类型和验证约束来使用,并通过来自`@deepkit/type`的高度优化的验证器进行检查,因此在这方面不存在性能问题。因此,强烈建议使用这些验证能力。
所有的输入,如路径参数、查询参数和主体参数都会自动验证为指定的TypeScript类型。如果通过`@deepkit/type`的类型指定了额外的约束,这些约束也会被检查。
import { HttpQuery, HttpQueries, HttpBody } from '@deepkit/http';
import { MinLength } from '@deepkit/type';
router.get('/:text', (text: string & MinLength<3>) => {
return 'Hello ' + text;
}
router.get('/', (text: HttpQuery<string> & MinLength<3>) => {
return 'Hello ' + text;
}
interface MyQuery {
text: string & MinLength<3>;
}
router.get('/', (query: HttpQueries<MyQuery>) => {
return 'Hello ' + query.text;
}
router.post('/', (body: HttpBody<MyQuery>) => {
return 'Hello ' + body.text;
}
请参阅Validation获取更多相关信息。
输出
路由可以返回各种数据结构。其中一些是以特殊的方式处理的,如重定向和模板,而其他的,如简单的对象,只是作为JSON发送。
JSON
默认情况下,正常的JavaScript值会以JSON形式返回给客户端,标题为`application/json; charset=utf-8`。
router.get('/', () => {
// will be sent as application/json
return {hello: 'world'}
});
如果在函数或方法中指定了明确的返回类型,那么数据将根据该类型被Deepkit JSON序列化器序列化为JSON。
interface ResultType {
hello: string;
}
router.get('/', (): ResultType => {
// will be sent as application/json and additionalProperty is dropped
return {hello: 'world', additionalProperty: 'value'};
});
HTML
要发送HTML,有两种可能。
import { HtmlResponse } from '@deepkit/http';
router.get('/', () => {
// will be sent as Content-Type: text/html
return new HtmlResponse('<b>Hello World</b>');
});
router.get('/', () => {
// will be sent as Content-Type: text/html
return <b>Hello World</b>;
});
带有TSX的模板引擎变体的优点是,使用的变量会自动进行HTML转义。见Template。
自定义内容
除了HTML和JSON,还可以发送具有特定内容类型的文本或二进制数据。这是通过对象`Response`
import { Response } from '@deepkit/http';
router.get('/', () => {
return new Response('<title>Hello World</title>', 'text/xml');
});
HTTP错误
通过抛出各种HTTP错误,可以立即中断HTTP请求的处理,并输出错误的相应HTTP状态。
import { HttpNotFoundError } from '@deepkit/http';
router.get('/user/:id', async (id: number, database: Database) => {
const user = await database.query(User).filter({id}).findOneOrUndefined();
if (!user) throw new HttpNotFoundError('User not found');
return user;
});
默认情况下,所有错误都以JSON格式返回给客户端。这个行为可以根据需要在事件系统中的`httpWorkflow.onControllerError’事件下进行调整。见HTTP Events一节。
Error class | Status |
---|---|
HttpBadRequestError |
400 |
HttpUnauthorizedError |
401 |
HttpAccessDeniedError |
403 |
HttpNotFoundError |
404 |
HttpMethodNotAllowedError |
405 |
HttpNotAcceptableError |
406 |
HttpTimeoutError |
408 |
HttpConflictError |
409 |
HttpGoneError |
410 |
HttpTooManyRequestsError |
429 |
HttpInternalServerError |
500 |
HttpNotImplementedError |
501 |
错误 "HttpAccessDeniedError "是一个特殊情况。一旦它被抛出,HTTP工作流(见HTTP Events)就不会跳到`controllerError`,而是跳到`accessDenied`。
用户定义的HTTP错误可以用`createHttpError’来创建和抛出。
export class HttpMyError extends createHttpError(412, 'My Error Message') {
}
Additional Header
为了改变HTTP响应的头,可以在对象`Response`、`JSONResponse`和`HTMLResponse`上调用其他方法。
import { Response } from '@deepkit/http';
router.get('/', () => {
return new Response('Access Denied', 'text/plain')
.header('X-Reason', 'unknown')
.status(403);
});
Redirect
要返回301或302重定向作为响应,可以使用`Redirect.toRoute`或`Redirect.toUrl`。
import { Redirect } from '@deepkit/http';
router.get({path: '/', name: 'homepage'}, () => {
return <b>Hello World</b>;
});
router.get({path: '/registration/complete'}, () => {
return Redirect.toRoute('homepage');
});
方法`Redirect.toRoute`使用路由的名称。如何设置路由名称可以在HTTP路由名称部分看到。如果这个被引用的路由(查询或路径)包含参数,这些参数可以通过第二个参数指定。
router.get({path: '/user/:id', name: 'user_detail'}, (id: number) => {
});
router.post('/user', (user: HttpBody<User>) => {
//... store user and redirect to its detail page
return Redirect.toRoute('user_detail', {id: 23});
});
或者,用`Redirect.toUrl`重定向到一个URL。
router.post('/user', (user: HttpBody<User>) => {
//... store user and redirect to its detail page
return Redirect.toUrl('/user/' + 23);
});
默认情况下,两者都使用302重定向。
Scope
所有的HTTP控制器和功能路由都在`http`依赖性注入范围内管理。HTTP控制器为每个HTTP请求进行相应的实例化。这也意味着两者都可以访问为`http`范围注册的提供者。因此,另外来自`@deepkit/http`的`HttpRequest`和`HttpResponse`可以作为依赖关系使用。如果使用Deepkit Framework,`@deepkit/framework’中的`SessionHandler’也是可用的。
import { HttpResponse } from '@deepkit/http';
router.get('/user/:id', (id: number, request: HttpRequest) => {
});
router.get('/', (response: HttpResponse) => {
response.end('Hello');
});
在`http’范围内放置提供者可能相当有用,例如为每个HTTP请求重新实例化服务。一旦HTTP请求被处理,`http`作用域的DI容器就会被删除,从而从垃圾收集器(GC)中清理掉所有的提供者实例。
参见依赖注入作用域,了解如何将提供者放在`http`作用域中。
Events
HTTP模块基于一个工作流引擎,它提供了各种事件标记,可以用来钩住处理HTTP请求的整个过程。
工作流引擎是一个有限状态机,为每个HTTP请求创建一个新的状态机实例,然后从一个位置跳到另一个位置。第一个位置是 "开始",最后一个是 "回应"。

每个事件标记都有自己的事件类型,并有额外的信息。
Event-Token | Description |
---|---|
httpWorkflow.onRequest |
When a new request comes in |
httpWorkflow.onRoute |
When the route should be resolved from the request |
httpWorkflow.onRouteNotFound |
When the route is not found |
httpWorkflow.onAuth |
When authentication happens |
httpWorkflow.onResolveParameters |
When route parameters are resolved |
httpWorkflow.onAccessDenied |
When access is denied |
httpWorkflow.onController |
When the controller action is called |
httpWorkflow.onControllerError |
When the controller action threw an error |
httpWorkflow.onParametersFailed |
When route parameters resolving failed |
httpWorkflow.onResponse |
When the controller action has been called. This is the place where the result is converted to a response. |
由于所有的HTTP事件都是基于工作流引擎的,它的行为可以通过使用指定的事件并通过`event.next()`方法跳转到那里来修改。
HTTP模块在这些事件标记上使用自己的事件监听器来实现HTTP请求的处理。所有这些事件监听器的优先级都是100,这意味着当你监听一个事件时,你的监听器将被默认首先执行(因为默认优先级是0)。
例如,假设你想捕捉一个控制器被调用的事件,那么你可以在HTTP模块的事件监听者之后添加一个高于100的优先级。如果一个特定的控制器要被调用,我们要检查用户是否有权限访问它。如果用户有权限,我们继续。但如果不是,我们就跳到下一个工作流程项目`accessDenied`。在那里,访问拒绝的程序会被自动进一步处理。
import { App } from '@deepkit/app';
import { FrameworkModule } from '@deepkit/framework';
import { HtmlResponse, http, httpWorkflow } from '@deepkit/http';
import { eventDispatcher } from '@deepkit/event';
class MyWebsite {
@http.GET('/')
open() {
return 'Welcome';
}
@http.GET('/admin').group('secret')
secret() {
return 'Welcome to the dark side';
}
}
class SecretRouteListeners {
@eventDispatcher.listen(httpWorkflow.onController)
onController(event: typeof httpWorkflow.onController.event) {
if (event.route.groups.includes('secret')) {
//check here for authentication information like cookie session, JWT, etc.
//this jumps to the 'accessDenied' workflow state,
// essentially executing all onAccessDenied listeners.
//since our listener is called before the HTTP kernel one,
// the standard controller action will never be called.
//this calls event.next('accessDenied', ...) under the hood
event.accessDenied();
}
}
/**
* We change the default accessDenied implementation.
*/
@eventDispatcher.listen(httpWorkflow.onAccessDenied)
onAccessDenied(event: typeof httpWorkflow.onAccessDenied.event): void {
if (event.sent) return;
if (event.hasNext()) return;
event.send(new HtmlResponse('No access to this area.', 403));
}
}
new App({
controllers: [MyWebsite],
listeners: [SecretRouteListeners],
imports: [new FrameworkModule]
}).run();
$ curl http://localhost:8080/
Welcome
$ curl http://localhost:8080/admin
No access to this area
中间件
HTTP中间件允许你作为HTTP事件的替代品,钩住请求/响应周期。它的API允许你使用Express/Connect框架的所有中间件。
中间件 一个中间件可以是一个类(由依赖注入容器实例化),也可以是一个简单的函数。
import { HttpMiddleware, httpMiddleware, HttpRequest, HttpResponse } from '@deepkit/http';
class MyMiddleware implements HttpMiddleware {
async execute(request: HttpRequest, response: HttpResponse, next: (err?: any) => void) {
response.setHeader('middleware', '1');
next();
}
}
function myMiddlewareFunction(request: HttpRequest, response: HttpResponse, next: (err?: any) => void) {
response.setHeader('middleware', '1');
next();
}
new App({
providers: [MyMiddleware],
middlewares: [
httpMiddleware.for(MyMiddleware),
httpMiddleware.for(myMiddlewareFunction),
],
imports: [new FrameworkModule]
}).run();
全球
通过使用httpMiddleware.for(MyMiddleware),在全球范围内为所有路由注册一个中间件。
import { httpMiddleware } from '@deepkit/http';
new App({
providers: [MyMiddleware],
middlewares: [
httpMiddleware.for(MyMiddleware)
],
imports: [new FrameworkModule]
}).run();
每个控制器
你可以通过两种方式将中间件限制在一个或多个控制器上。可以通过使用`@http.controller`或`httpMiddleware.for(T).forControllers()`。`excludeControllers`允许你排除控制器。
@http.middleware(MyMiddleware)
class MyFirstController {
}
new App({
providers: [MyMiddleware],
controllers: [MainController, UsersCommand],
middlewares: [
httpMiddleware.for(MyMiddleware).forControllers(MyFirstController, MySecondController)
],
imports: [new FrameworkModule]
}).run();
每个路线名称
`forRouteNames`和其对应的`excludeRouteNames`允许你根据路由名称过滤中间件的执行。
class MyFirstController {
@http.GET('/hello').name('firstRoute')
myAction() {
}
@http.GET('/second').name('secondRoute')
myAction2() {
}
}
new App({
controllers: [MainController, UsersCommand],
providers: [MyMiddleware],
middlewares: [
httpMiddleware.for(MyMiddleware).forRouteNames('firstRoute', 'secondRoute')
],
imports: [new FrameworkModule]
}).run();
每个行动/路线
要想只为某个路由执行一个中间件,你可以使用`@http.GET().middleware()`或 `httpMiddleware.for(T).forRoute()`其中forRoute有多个选项来过滤路由。
class MyFirstController {
@http.GET('/hello').middleware(MyMiddleware)
myAction() {
}
}
new App({
controllers: [MainController, UsersCommand],
providers: [MyMiddleware],
middlewares: [
httpMiddleware.for(MyMiddleware).forRoutes({
path: 'api/*'
})
],
imports: [new FrameworkModule]
}).run();
`forRoutes()`允许作为第一个参数的几种方式来过滤路线。
{
path?: string;
pathRegExp?: RegExp;
httpMethod?: 'GET' | 'HEAD' | 'POST' | 'PATCH' | 'PUT' | 'DELETE' | 'OPTIONS' | 'TRACE';
category?: string;
excludeCategory?: string;
group?: string;
excludeGroup?: string;
}
类别
`类别’和其对应的`排除类别’允许你按路线类别进行过滤。
@http.category('myCategory')
class MyFirstController {
}
class MySecondController {
@http.GET().category('myCategory')
myAction() {
}
}
httpMiddleware.for(MyMiddleware).forRoutes({
category: 'myCategory'
})
Group
`group`和其对应的`excludeGroup`允许你按路由组过滤。
@http.group('myGroup')
class MyFirstController {
}
class MySecondController {
@http.GET().group('myGroup')
myAction() {
}
}
httpMiddleware.for(MyMiddleware).forRoutes({
group: 'myGroup'
})
每个自我模块
使用`forSelfModules()`来为注册了中间件的模块的所有控制器/路由执行一个中间件。
const ApiModule new AppModule({
controllers: [MainController, UsersCommand],
providers: [MyMiddleware],
middlewares: [
//for all controllers registered of the same module
httpMiddleware.for(MyMiddleware).forSelfModules(),
],
});
超时
所有的中间件迟早都需要执行`next()'。如果一个中间件在超时内没有执行`next()`,就会记录一个警告并执行下一个中间件。要把默认的4秒改为其他的,请使用timeout(milliseconds)。
const ApiModule = new AppModule({
controllers: [MainController, UsersCommand],
providers: [MyMiddleware],
middlewares: [
//for all controllers registered of the same module
httpMiddleware.for(MyMiddleware).timeout(15_000),
],
});
解析器
路由器支持一种解析复杂参数类型的方法。例如,给定一个路由,如`/user/:id`,这个`id`可以通过一个解析器解析到路由之外的`user`对象。这进一步解耦了HTTP抽象和路由代码,进一步简化了测试和模块化。
import { App } from '@deepkit/app';
import { FrameworkModule } from '@deepkit/framework';
import { http, RouteParameterResolverContext, RouteParameterResolver } from '@deepkit/http';
class UserResolver implements RouteParameterResolver {
constructor(protected database: Database) {}
async resolve(context: RouteParameterResolverContext) {
if (!context.parameters.id) throw new Error('No :id given');
return await this.database.getUser(parseInt(context.parameters.id, 10));
}
}
@http.resolveParameter(User, UserResolver)
class MyWebsite {
@http.GET('/user/:id')
getUser(user: User) {
return 'Hello ' + user.username;
}
}
new App({
controllers: [MyWebsite],
providers: [UserDatabase, UserResolver],
imports: [new FrameworkModule]
})
.run();
`@http.resolveParameter`中的装饰器指定了要用`UserResolver`来解决的类。只要指定的类`User`在函数或方法中被指定为参数,解析器就会被用来提供它。
如果在类中指定了`@http.resolveParameter`,这个类的所有方法都会接收这个解析器。该装饰器也可以按方法应用。
class MyWebsite {
@http.GET('/user/:id').resolveParameter(User, UserResolver)
getUser(user: User) {
return 'Hello ' + user.username;
}
}
也可以使用功能性API。
router.add(
http.GET('/user/:id').resolveParameter(User, UserResolver),
(user: User) => {
return 'Hello ' + user.username;
}
);
对象`User’不一定要依赖一个参数。它也可以依赖于会话或HTTP头,只在用户登录时提供。在`RouteParameterResolverContext`中,有很多关于HTTP请求的信息,因此可以映射出很多用例。
原则上,也可以通过依赖注入容器从`http`范围提供复杂的参数类型,因为这些也可以在路由函数或方法中使用。然而,这有一个缺点,就是不能使用异步函数调用,因为DI容器自始至终都是同步的。