@@ -24,7 +24,50 @@ export type GetPreRequestCb = (browserPreRequest?: BrowserPreRequest) => void
24
24
type PendingRequest = {
25
25
ctxDebug
26
26
callback : GetPreRequestCb
27
- timeout : ReturnType < typeof setTimeout >
27
+ timeout : NodeJS . Timeout
28
+ }
29
+
30
+ type PendingPreRequest = {
31
+ browserPreRequest : BrowserPreRequest
32
+ timestamp : number
33
+ }
34
+
35
+ /**
36
+ * Data structure that organizes items with duplicated keys into stacks.
37
+ */
38
+ class StackMap < T > {
39
+ private stacks : Record < string , Array < T > > = { }
40
+ push ( stackKey : string , value : T ) {
41
+ if ( ! this . stacks [ stackKey ] ) this . stacks [ stackKey ] = [ ]
42
+
43
+ this . stacks [ stackKey ] . push ( value )
44
+ }
45
+ pop ( stackKey : string ) : T | undefined {
46
+ const stack = this . stacks [ stackKey ]
47
+
48
+ if ( ! stack ) return
49
+
50
+ const item = stack . pop ( )
51
+
52
+ if ( stack . length === 0 ) delete this . stacks [ stackKey ]
53
+
54
+ return item
55
+ }
56
+ removeMatching ( filterFn : ( value : T ) => boolean ) {
57
+ Object . entries ( this . stacks ) . forEach ( ( [ stackKey , stack ] ) => {
58
+ this . stacks [ stackKey ] = stack . filter ( filterFn )
59
+ if ( this . stacks [ stackKey ] . length === 0 ) delete this . stacks [ stackKey ]
60
+ } )
61
+ }
62
+ removeExact ( stackKey : string , value : T ) {
63
+ const i = this . stacks [ stackKey ] . findIndex ( ( v ) => v === value )
64
+
65
+ this . stacks [ stackKey ] . splice ( i , 1 )
66
+ if ( this . stacks [ stackKey ] . length === 0 ) delete this . stacks [ stackKey ]
67
+ }
68
+ get length ( ) {
69
+ return Object . values ( this . stacks ) . reduce ( ( prev , cur ) => prev + cur . length , 0 )
70
+ }
28
71
}
29
72
30
73
// This class' purpose is to match up incoming "requests" (requests from the browser received by the http proxy)
@@ -35,9 +78,8 @@ type PendingRequest = {
35
78
// ever comes in, we don't want to block proxied requests indefinitely.
36
79
export class PreRequests {
37
80
requestTimeout : number
38
- pendingPreRequests : Record < string , BrowserPreRequest > = { }
39
- pendingRequests : Record < string , PendingRequest > = { }
40
- prerequestTimestamps : Record < string , number > = { }
81
+ pendingPreRequests = new StackMap < PendingPreRequest > ( )
82
+ pendingRequests = new StackMap < PendingRequest > ( )
41
83
sweepInterval : ReturnType < typeof setInterval >
42
84
43
85
constructor ( requestTimeout = 500 ) {
@@ -55,59 +97,63 @@ export class PreRequests {
55
97
this . sweepInterval = setInterval ( ( ) => {
56
98
const now = Date . now ( )
57
99
58
- Object . entries ( this . prerequestTimestamps ) . forEach ( ( [ key , timestamp ] ) => {
100
+ this . pendingPreRequests . removeMatching ( ( { timestamp, browserPreRequest } ) => {
59
101
if ( timestamp + this . requestTimeout * 2 < now ) {
60
- debugVerbose ( 'timed out unmatched pre-request %s : %o' , key , this . pendingPreRequests [ key ] )
102
+ debugVerbose ( 'timed out unmatched pre-request: %o' , browserPreRequest )
61
103
metrics . unmatchedPreRequests ++
62
- delete this . pendingPreRequests [ key ]
63
- delete this . prerequestTimestamps [ key ]
104
+
105
+ return false
64
106
}
107
+
108
+ return true
65
109
} )
66
110
} , this . requestTimeout * 2 )
67
111
}
68
112
69
113
addPending ( browserPreRequest : BrowserPreRequest ) {
70
114
metrics . browserPreRequestsReceived ++
71
115
const key = `${ browserPreRequest . method } -${ browserPreRequest . url } `
116
+ const pendingRequest = this . pendingRequests . pop ( key )
72
117
73
- if ( this . pendingRequests [ key ] ) {
118
+ if ( pendingRequest ) {
74
119
debugVerbose ( 'Incoming pre-request %s matches pending request. %o' , key , browserPreRequest )
75
- clearTimeout ( this . pendingRequests [ key ] . timeout )
76
- this . pendingRequests [ key ] . callback ( browserPreRequest )
77
- delete this . pendingRequests [ key ]
120
+ clearTimeout ( pendingRequest . timeout )
121
+ pendingRequest . callback ( browserPreRequest )
122
+
123
+ return
78
124
}
79
125
80
126
debugVerbose ( 'Caching pre-request %s to be matched later. %o' , key , browserPreRequest )
81
- this . pendingPreRequests [ key ] = browserPreRequest
82
- this . prerequestTimestamps [ key ] = Date . now ( )
127
+ this . pendingPreRequests . push ( key , {
128
+ browserPreRequest,
129
+ timestamp : Date . now ( ) ,
130
+ } )
83
131
}
84
132
85
133
get ( req : CypressIncomingRequest , ctxDebug , callback : GetPreRequestCb ) {
86
134
metrics . proxyRequestsReceived ++
87
135
const key = `${ req . method } -${ req . proxiedUrl } `
136
+ const pendingPreRequest = this . pendingPreRequests . pop ( key )
88
137
89
- if ( this . pendingPreRequests [ key ] ) {
138
+ if ( pendingPreRequest ) {
90
139
metrics . immediatelyMatchedRequests ++
91
- ctxDebug ( 'Incoming request %s matches known pre-request: %o' , key , this . pendingPreRequests [ key ] )
92
- callback ( this . pendingPreRequests [ key ] )
93
-
94
- delete this . pendingPreRequests [ key ]
95
- delete this . prerequestTimestamps [ key ]
140
+ ctxDebug ( 'Incoming request %s matches known pre-request: %o' , key , pendingPreRequest )
141
+ callback ( pendingPreRequest . browserPreRequest )
96
142
97
143
return
98
144
}
99
145
100
- const timeout = setTimeout ( ( ) => {
101
- callback ( )
102
- ctxDebug ( 'Never received pre-request for request %s after waiting %sms. Continuing without one.' , key , this . requestTimeout )
103
- metrics . unmatchedRequests ++
104
- delete this . pendingRequests [ key ]
105
- } , this . requestTimeout )
106
-
107
- this . pendingRequests [ key ] = {
146
+ const pendingRequest : PendingRequest = {
108
147
ctxDebug,
109
148
callback,
110
- timeout,
149
+ timeout : setTimeout ( ( ) => {
150
+ ctxDebug ( 'Never received pre-request for request %s after waiting %sms. Continuing without one.' , key , this . requestTimeout )
151
+ metrics . unmatchedRequests ++
152
+ this . pendingRequests . removeExact ( key , pendingRequest )
153
+ callback ( )
154
+ } , this . requestTimeout ) ,
111
155
}
156
+
157
+ this . pendingRequests . push ( key , pendingRequest )
112
158
}
113
159
}
0 commit comments