Skip to content

Commit 23f883b

Browse files
committed
feat: add AsyncHttpContext
1 parent 3ac1567 commit 23f883b

File tree

5 files changed

+129
-25
lines changed

5 files changed

+129
-25
lines changed

adonis-typings/async-http-context.ts

+30
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
/**
2+
* @adonisjs/http-server
3+
*
4+
* (c) Harminder Virk <[email protected]>
5+
*
6+
* For the full copyright and license information, please view the LICENSE
7+
* file that was distributed with this source code.
8+
*/
9+
10+
declare module '@ioc:Adonis/Core/AsyncHttpContext' {
11+
import { HttpContextContract } from '@ioc:Adonis/Core/HttpContext'
12+
13+
/**
14+
* Async Http context available during the lifecycle of HTTP requests
15+
*/
16+
export interface AsyncHttpContextContract {
17+
/**
18+
* Returns the current HTTP context or null if called outside of a request.
19+
*/
20+
getContext(): HttpContextContract | null
21+
22+
/**
23+
* Returns the current HTTP context or throws if called outside of a request.
24+
*/
25+
getContextOrFail(): HttpContextContract
26+
}
27+
28+
const AsyncHttpContext: AsyncHttpContextContract
29+
export default AsyncHttpContext
30+
}

adonis-typings/index.ts

+1
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
* file that was distributed with this source code.
88
*/
99

10+
/// <reference path="./async-http-context.ts" />
1011
/// <reference path="./container.ts" />
1112
/// <reference path="./context.ts" />
1213
/// <reference path="./http-server.ts" />

providers/HttpServerProvider.ts

+10
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,8 @@
1010
import { Exception } from '@poppinss/utils'
1111
import { ApplicationContract } from '@ioc:Adonis/Core/Application'
1212

13+
import { asyncHttpContext } from '../src/AsyncHttpContext'
14+
1315
export default class HttpServerProvider {
1416
constructor(protected application: ApplicationContract) {}
1517

@@ -80,6 +82,13 @@ export default class HttpServerProvider {
8082
})
8183
}
8284

85+
/**
86+
* Register the async HTTP context
87+
*/
88+
protected registerAsyncHttpContext() {
89+
this.application.container.singleton('Adonis/Core/AsyncHttpContext', () => asyncHttpContext)
90+
}
91+
8392
/**
8493
* Register the router. The router points to the instance of router used
8594
* by the middleware
@@ -98,6 +107,7 @@ export default class HttpServerProvider {
98107
this.registerMiddlewareStore()
99108
this.registerHttpServer()
100109
this.registerHTTPContext()
110+
this.registerAsyncHttpContext()
101111
this.registerRouter()
102112
}
103113
}

src/AsyncHttpContext/index.ts

+49
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
/**
2+
* @adonisjs/http-server
3+
*
4+
* (c) Harminder Virk <[email protected]>
5+
*
6+
* For the full copyright and license information, please view the LICENSE
7+
* file that was distributed with this source code.
8+
*/
9+
10+
/// <reference path="../../adonis-typings/index.ts" />
11+
12+
import { AsyncLocalStorage } from 'async_hooks'
13+
import { Exception } from '@poppinss/utils'
14+
import { AsyncHttpContextContract } from '@ioc:Adonis/Core/AsyncHttpContext'
15+
import { HttpContextContract } from '@ioc:Adonis/Core/HttpContext'
16+
17+
const adonisLocalStorage = new AsyncLocalStorage<InternalAsyncHttpContext>()
18+
19+
export class InternalAsyncHttpContext {
20+
constructor(private ctx: HttpContextContract) {}
21+
22+
public getContext() {
23+
return this.ctx
24+
}
25+
26+
public run(callback: () => any) {
27+
return adonisLocalStorage.run(this, callback)
28+
}
29+
}
30+
31+
class AsyncHttpContext implements AsyncHttpContextContract {
32+
public getContext() {
33+
const store = adonisLocalStorage.getStore()
34+
if (store) {
35+
return store.getContext()
36+
}
37+
return null
38+
}
39+
40+
public getContextOrFail() {
41+
const store = adonisLocalStorage.getStore()
42+
if (store) {
43+
return store.getContext()
44+
}
45+
throw new Exception('AsyncHttpContext accessed outside of a request context')
46+
}
47+
}
48+
49+
export const asyncHttpContext = new AsyncHttpContext()

src/Server/index.ts

+39-25
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ import { HttpContext } from '../HttpContext'
2727
import { RequestHandler } from './RequestHandler'
2828
import { MiddlewareStore } from '../MiddlewareStore'
2929
import { ExceptionManager } from './ExceptionManager'
30+
import { InternalAsyncHttpContext } from '../AsyncHttpContext'
3031

3132
/**
3233
* Server class handles the HTTP requests by using all Adonis micro modules.
@@ -123,6 +124,13 @@ export class Server implements ServerContract {
123124
)
124125
}
125126

127+
/**
128+
* Returns a new async HTTP context for the new request
129+
*/
130+
private getAsyncContext(ctx: HttpContextContract): InternalAsyncHttpContext {
131+
return new InternalAsyncHttpContext(ctx)
132+
}
133+
126134
/**
127135
* Define custom error handler to handler all errors
128136
* occurred during HTTP request
@@ -160,34 +168,40 @@ export class Server implements ServerContract {
160168

161169
const requestAction = this.getProfilerRow(request)
162170
const ctx = this.getContext(request, response, requestAction)
171+
const asyncContext = this.getAsyncContext(ctx)
163172

164173
/*
165-
* Handle request by executing hooks, request middleware stack
166-
* and route handler
174+
* Run everything within the async HTTP context
167175
*/
168-
try {
169-
await this.handleRequest(ctx)
170-
} catch (error) {
171-
await this.exception.handle(error, ctx)
172-
}
176+
return asyncContext.run(async () => {
177+
/*
178+
* Handle request by executing hooks, request middleware stack
179+
* and route handler
180+
*/
181+
try {
182+
await this.handleRequest(ctx)
183+
} catch (error) {
184+
await this.exception.handle(error, ctx)
185+
}
173186

174-
/*
175-
* Excute hooks when there are one or more hooks. The `ctx.response.finish`
176-
* is intentionally inside both the `try` and `catch` blocks as a defensive
177-
* measure.
178-
*
179-
* When we call `response.finish`, it will serialize the response body and may
180-
* encouter errors while doing so and hence will be catched by the catch
181-
* block.
182-
*/
183-
try {
184-
await this.hooks.executeAfter(ctx)
185-
requestAction.end({ status_code: res.statusCode })
186-
ctx.response.finish()
187-
} catch (error) {
188-
await this.exception.handle(error, ctx)
189-
requestAction.end({ status_code: res.statusCode, error })
190-
ctx.response.finish()
191-
}
187+
/*
188+
* Excute hooks when there are one or more hooks. The `ctx.response.finish`
189+
* is intentionally inside both the `try` and `catch` blocks as a defensive
190+
* measure.
191+
*
192+
* When we call `response.finish`, it will serialize the response body and may
193+
* encouter errors while doing so and hence will be catched by the catch
194+
* block.
195+
*/
196+
try {
197+
await this.hooks.executeAfter(ctx)
198+
requestAction.end({ status_code: res.statusCode })
199+
ctx.response.finish()
200+
} catch (error) {
201+
await this.exception.handle(error, ctx)
202+
requestAction.end({ status_code: res.statusCode, error })
203+
ctx.response.finish()
204+
}
205+
})
192206
}
193207
}

0 commit comments

Comments
 (0)