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');
});

HTTP客户端

todo: fetch API, validation, and cast.

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请求对象。

所有的参数都被自动转换(反序列化)为定义的TypeScript类型并被验证。这是通过`@deepkit/type`包及其序列化验证功能实现的。

为了简单起见,下面展示了所有带有功能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

指定模型中的属性可以包含所有TypeScript类型和`@deepkit/type`支持的验证类型。参见序列化验证章节。

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;
}

Header

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请求,该请求将被直接中止,函数中的代码将永远不会被执行。

指定模型中的属性可以包含所有TypeScript类型和`@deepkit/type`支持的验证类型。参见序列化验证章节。

文件上传

主体模型上的一个特殊的属性类型可以用来允许客户端上传文件。可以使用任何数量的`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请求创建一个新的状态机实例,然后从一个位置跳到另一个位置。第一个位置是 "开始",最后一个是 "回应"。

http workflow

每个事件标记都有自己的事件类型,并有额外的信息。

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

安全

Sessions

中间件

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;
}

路径模式

`path`支持通配符*。

httpMiddleware.for(MyMiddleware).forRoutes({
    path: 'api/*'
})

RegExp

httpMiddleware.for(MyMiddleware).forRoutes({
    pathRegExp: /'api/.*'/
})

HTTP方法

通过一个HTTP方法过滤所有路由。

httpMiddleware.for(MyMiddleware).forRoutes({
    httpMethod: 'GET'
})

类别

`类别’和其对应的`排除类别’允许你按路线类别进行过滤。

@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'
})

每个模块

你可以限制一个模块的执行,对整个模块进行限制。

httpMiddleware.for(MyMiddleware).forModule(ApiModule)

每个自我模块

使用`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),
    ],
});

多种规则

为了组合多个过滤器,你可以连锁调用方法。

const ApiModule = new AppModule({
    controllers: [MyController],
    providers: [MyMiddleware],
    middlewares: [
        httpMiddleware.for(MyMiddleware).forControllers(MyController).excludeRouteNames('secondRoute')
    ],
});

快捷中间件

几乎所有的快递中间件都被支持。那些访问快递的某些请求方法还不被支持。

import * as compression from 'compression';

const ApiModule = new AppModule({
    middlewares: [
        httpMiddleware.for(compress()).forControllers(MyController)
    ],
});

解析器

路由器支持一种解析复杂参数类型的方法。例如,给定一个路由,如`/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容器自始至终都是同步的。