forked from adonisjs/http-server
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathindex.ts
220 lines (196 loc) · 6.35 KB
/
index.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
/**
* @adonisjs/http-server
*
* (c) Harminder Virk <[email protected]>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
/// <reference path="../../adonis-typings/index.ts" />
import ms from 'ms'
import { Server as HttpsServer } from 'https'
import { ProfilerRowContract } from '@ioc:Adonis/Core/Profiler'
import { EncryptionContract } from '@ioc:Adonis/Core/Encryption'
import { HttpContextContract } from '@ioc:Adonis/Core/HttpContext'
import { ApplicationContract } from '@ioc:Adonis/Core/Application'
import { IncomingMessage, ServerResponse, Server as HttpServer } from 'http'
import { ServerContract, ServerConfig, ErrorHandler } from '@ioc:Adonis/Core/Server'
import { Hooks } from './Hooks'
import { Router } from '../Router'
import { Request } from '../Request'
import { Response } from '../Response'
import { PreCompiler } from './PreCompiler'
import { HttpContext } from '../HttpContext'
import { RequestHandler } from './RequestHandler'
import { MiddlewareStore } from '../MiddlewareStore'
import { ExceptionManager } from './ExceptionManager'
import {
asyncHttpContextEnabled,
AsyncHttpContext,
setAsyncHttpContextEnabled,
} from '../AsyncHttpContext'
/**
* Server class handles the HTTP requests by using all Adonis micro modules.
*/
export class Server implements ServerContract {
/**
* The server itself doesn't create the http server instance. However, the consumer
* of this class can create one and set the instance for further reference. This
* is what ignitor does.
*/
public instance?: HttpServer | HttpsServer
/**
* The middleware store to register global and named middleware
*/
public middleware = new MiddlewareStore(this.application.container)
/**
* The route to register routes
*/
public router = new Router(this.encryption, (route) => this.precompiler.compileRoute(route))
/**
* Server before/after hooks
*/
public hooks = new Hooks()
/**
* Precompiler to set the finalHandler for the route
*/
private precompiler = new PreCompiler(this.application.container, this.middleware)
/**
* Exception manager to handle exceptions
*/
private exception = new ExceptionManager(this.application.container)
/**
* Request handler to handle request after route is found
*/
private requestHandler = new RequestHandler(this.middleware, this.router)
constructor(
private application: ApplicationContract,
private encryption: EncryptionContract,
private httpConfig: ServerConfig
) {
/*
* Pre process config to convert max age string to seconds.
*/
if (httpConfig.cookie.maxAge && typeof httpConfig.cookie.maxAge === 'string') {
httpConfig.cookie.maxAge = ms(httpConfig.cookie.maxAge) / 1000
}
setAsyncHttpContextEnabled(httpConfig.enableAsyncHttpContext || false)
}
/**
* Handles HTTP request
*/
private async handleRequest(ctx: HttpContextContract) {
/*
* Start with before hooks upfront. If they raise error
* then execute error handler.
*/
return this.hooks.executeBefore(ctx).then((shortcircuit) => {
if (!shortcircuit) {
return this.requestHandler.handle(ctx)
}
})
}
/**
* Returns the profiler row
*/
private getProfilerRow(request: Request) {
return this.application.profiler.create('http:request', {
request_id: request.id(),
url: request.url(),
method: request.method(),
})
}
/**
* Returns the context for the request
*/
private getContext(request: Request, response: Response, profilerRow: ProfilerRowContract) {
return new HttpContext(
request,
response,
this.application.logger.child({
request_id: request.id(),
serializers: {},
}),
profilerRow
)
}
/**
* Returns a new async HTTP context for the new request
*/
private getAsyncContext(ctx: HttpContextContract): AsyncHttpContext {
return new AsyncHttpContext(ctx)
}
/**
* Define custom error handler to handler all errors
* occurred during HTTP request
*/
public errorHandler(handler: ErrorHandler): this {
this.exception.registerHandler(handler)
return this
}
/**
* Optimizes internal handlers, based upon the existence of
* before handlers and global middleware. This helps in
* increasing throughput by 10%
*/
public optimize() {
this.router.commit()
this.hooks.commit()
this.requestHandler.commit()
}
/**
* Handles a given HTTP request. This method can be attached to any HTTP
* server
*/
public async handle(req: IncomingMessage, res: ServerResponse): Promise<void> {
/*
* Reset accept header when `forceContentNegotiationToJSON = true`
*/
if (this.httpConfig.forceContentNegotiationTo) {
req.headers['accept'] = this.httpConfig.forceContentNegotiationTo
}
const request = new Request(req, res, this.encryption, this.httpConfig)
const response = new Response(req, res, this.encryption, this.httpConfig, this.router)
const requestAction = this.getProfilerRow(request)
const ctx = this.getContext(request, response, requestAction)
if (asyncHttpContextEnabled) {
const asyncContext = this.getAsyncContext(ctx)
return asyncContext.run(() => this.handleImpl(ctx, requestAction, res))
} else {
this.handleImpl(ctx, requestAction, res)
}
}
private async handleImpl(
ctx: HttpContext,
requestAction: ProfilerRowContract,
res: ServerResponse
) {
/*
* Handle request by executing hooks, request middleware stack
* and route handler
*/
try {
await this.handleRequest(ctx)
} catch (error) {
await this.exception.handle(error, ctx)
}
/*
* Excute hooks when there are one or more hooks. The `ctx.response.finish`
* is intentionally inside both the `try` and `catch` blocks as a defensive
* measure.
*
* When we call `response.finish`, it will serialize the response body and may
* encouter errors while doing so and hence will be catched by the catch
* block.
*/
try {
await this.hooks.executeAfter(ctx)
requestAction.end({ status_code: res.statusCode })
ctx.response.finish()
} catch (error) {
await this.exception.handle(error, ctx)
requestAction.end({ status_code: res.statusCode, error })
ctx.response.finish()
}
}
}