33// deno-lint-ignore-file no-console
44
55import { EventEmitter , once } from "node:events" ;
6- import { createHook } from "node:async_hooks" ;
6+ import {
7+ AsyncLocalStorage ,
8+ createHook ,
9+ executionAsyncResource ,
10+ } from "node:async_hooks" ;
711import http , {
812 IncomingMessage ,
913 type RequestOptions ,
@@ -16,6 +20,7 @@ import net, { type AddressInfo, Socket } from "node:net";
1620import fs from "node:fs" ;
1721import type { Duplex } from "node:stream" ;
1822import { text } from "node:stream/consumers" ;
23+ import { channel } from "node:diagnostics_channel" ;
1924
2025import { assert , assertEquals , assertStringIncludes , fail } from "@std/assert" ;
2126import { assertSpyCalls , spy } from "@std/testing/mock" ;
@@ -2653,6 +2658,125 @@ Deno.test("[node/http] keep-alive timer is suspended during active request", asy
26532658 }
26542659} ) ;
26552660
2661+ Deno . test ( "[node/http] AsyncLocalStorage propagates into request handler" , async ( ) => {
2662+ const storage = new AsyncLocalStorage < string > ( ) ;
2663+ const { promise, resolve, reject } = Promise . withResolvers < void > ( ) ;
2664+ const responseDone = Promise . withResolvers < void > ( ) ;
2665+ const requestStart = channel ( "http.server.request.start" ) ;
2666+ const subscriber = ( ) => storage . enterWith ( "request-context" ) ;
2667+ const server = http . createServer ( ( _req , res ) => {
2668+ try {
2669+ assertEquals ( storage . getStore ( ) , "request-context" ) ;
2670+ resolve ( ) ;
2671+ } catch ( err ) {
2672+ reject ( err ) ;
2673+ } finally {
2674+ res . end ( "ok" ) ;
2675+ }
2676+ } ) ;
2677+
2678+ requestStart . subscribe ( subscriber ) ;
2679+ await new Promise < void > ( ( resolve ) => server . listen ( 0 , resolve ) ) ;
2680+ try {
2681+ const port = ( server . address ( ) as AddressInfo ) . port ;
2682+ const req = http . get ( `http://127.0.0.1:${ port } ` , ( res ) => {
2683+ res . resume ( ) ;
2684+ res . on ( "end" , responseDone . resolve ) ;
2685+ res . on ( "error" , responseDone . reject ) ;
2686+ } ) ;
2687+ req . on ( "error" , reject ) ;
2688+ await Promise . all ( [ promise , responseDone . promise ] ) ;
2689+ } finally {
2690+ requestStart . unsubscribe ( subscriber ) ;
2691+ storage . disable ( ) ;
2692+ await new Promise < void > ( ( resolve ) => server . close ( ( ) => resolve ( ) ) ) ;
2693+ }
2694+ } ) ;
2695+
2696+ Deno . test ( "[node/http] AsyncLocalStorage enterWith in request handler is isolated" , async ( ) => {
2697+ const storage = new AsyncLocalStorage < string > ( ) ;
2698+ const firstDone = Promise . withResolvers < void > ( ) ;
2699+ const secondDone = Promise . withResolvers < void > ( ) ;
2700+ let requests = 0 ;
2701+ const server = http . createServer ( ( _req , res ) => {
2702+ try {
2703+ requests ++ ;
2704+ if ( requests === 1 ) {
2705+ assertEquals ( storage . getStore ( ) , undefined ) ;
2706+ storage . enterWith ( "first-request" ) ;
2707+ assertEquals ( storage . getStore ( ) , "first-request" ) ;
2708+ } else {
2709+ assertEquals ( storage . getStore ( ) , undefined ) ;
2710+ }
2711+ res . end ( "ok" ) ;
2712+ if ( requests === 1 ) {
2713+ firstDone . resolve ( ) ;
2714+ } else {
2715+ secondDone . resolve ( ) ;
2716+ }
2717+ } catch ( err ) {
2718+ firstDone . reject ( err ) ;
2719+ secondDone . reject ( err ) ;
2720+ res . destroy ( err as Error ) ;
2721+ }
2722+ } ) ;
2723+
2724+ await new Promise < void > ( ( resolve ) => server . listen ( 0 , resolve ) ) ;
2725+ try {
2726+ const port = ( server . address ( ) as AddressInfo ) . port ;
2727+ const request = ( ) =>
2728+ new Promise < void > ( ( resolve , reject ) => {
2729+ const req = http . get ( `http://127.0.0.1:${ port } ` , ( res ) => {
2730+ res . resume ( ) ;
2731+ res . on ( "end" , resolve ) ;
2732+ res . on ( "error" , reject ) ;
2733+ } ) ;
2734+ req . on ( "error" , reject ) ;
2735+ } ) ;
2736+
2737+ await request ( ) ;
2738+ await firstDone . promise ;
2739+ await request ( ) ;
2740+ await secondDone . promise ;
2741+ } finally {
2742+ storage . disable ( ) ;
2743+ await new Promise < void > ( ( resolve ) => server . close ( ( ) => resolve ( ) ) ) ;
2744+ }
2745+ } ) ;
2746+
2747+ Deno . test ( "[node/http] async_hooks observes request execution resource" , async ( ) => {
2748+ const { promise, resolve, reject } = Promise . withResolvers < void > ( ) ;
2749+ const responseDone = Promise . withResolvers < void > ( ) ;
2750+ const hook = createHook ( {
2751+ before ( ) { } ,
2752+ } ) ;
2753+ const server = http . createServer ( ( req , res ) => {
2754+ try {
2755+ assertEquals ( executionAsyncResource ( ) , req ) ;
2756+ res . end ( "ok" ) ;
2757+ resolve ( ) ;
2758+ } catch ( err ) {
2759+ reject ( err ) ;
2760+ }
2761+ } ) ;
2762+
2763+ hook . enable ( ) ;
2764+ try {
2765+ await new Promise < void > ( ( resolve ) => server . listen ( 0 , resolve ) ) ;
2766+ const port = ( server . address ( ) as AddressInfo ) . port ;
2767+ const req = http . get ( `http://127.0.0.1:${ port } ` , ( res ) => {
2768+ res . resume ( ) ;
2769+ res . on ( "end" , responseDone . resolve ) ;
2770+ res . on ( "error" , responseDone . reject ) ;
2771+ } ) ;
2772+ req . on ( "error" , reject ) ;
2773+ await Promise . all ( [ promise , responseDone . promise ] ) ;
2774+ } finally {
2775+ hook . disable ( ) ;
2776+ await new Promise < void > ( ( resolve ) => server . close ( ( ) => resolve ( ) ) ) ;
2777+ }
2778+ } ) ;
2779+
26562780Deno . test ( "[node/http] abandoned suspended keep-alive timer emits async_hooks destroy" , async ( ) => {
26572781 const server = http . createServer (
26582782 { keepAliveTimeout : 10 } ,
0 commit comments