@@ -26,7 +26,10 @@ internal class HttpContextBuilder : IHttpBodyControlFeature, IHttpResetFeature
26
26
private bool _pipelineFinished ;
27
27
private bool _returningResponse ;
28
28
private object _testContext ;
29
+ private Pipe _requestPipe ;
30
+
29
31
private Action < HttpContext > _responseReadCompleteCallback ;
32
+ private Task _sendRequestStreamTask ;
30
33
31
34
internal HttpContextBuilder ( ApplicationWrapper application , bool allowSynchronousIO , bool preserveExecutionContext )
32
35
{
@@ -41,9 +44,11 @@ internal HttpContextBuilder(ApplicationWrapper application, bool allowSynchronou
41
44
request . Protocol = "HTTP/1.1" ;
42
45
request . Method = HttpMethods . Get ;
43
46
44
- var pipe = new Pipe ( ) ;
45
- _responseReaderStream = new ResponseBodyReaderStream ( pipe , ClientInitiatedAbort , ( ) => _responseReadCompleteCallback ? . Invoke ( _httpContext ) ) ;
46
- _responsePipeWriter = new ResponseBodyPipeWriter ( pipe , ReturnResponseMessageAsync ) ;
47
+ _requestPipe = new Pipe ( ) ;
48
+
49
+ var responsePipe = new Pipe ( ) ;
50
+ _responseReaderStream = new ResponseBodyReaderStream ( responsePipe , ClientInitiatedAbort , ( ) => _responseReadCompleteCallback ? . Invoke ( _httpContext ) ) ;
51
+ _responsePipeWriter = new ResponseBodyPipeWriter ( responsePipe , ReturnResponseMessageAsync ) ;
47
52
_responseFeature . Body = new ResponseBodyWriterStream ( _responsePipeWriter , ( ) => AllowSynchronousIO ) ;
48
53
_responseFeature . BodyWriter = _responsePipeWriter ;
49
54
@@ -56,14 +61,24 @@ internal HttpContextBuilder(ApplicationWrapper application, bool allowSynchronou
56
61
57
62
public bool AllowSynchronousIO { get ; set ; }
58
63
59
- internal void Configure ( Action < HttpContext > configureContext )
64
+ internal void Configure ( Action < HttpContext , PipeReader > configureContext )
60
65
{
61
66
if ( configureContext == null )
62
67
{
63
68
throw new ArgumentNullException ( nameof ( configureContext ) ) ;
64
69
}
65
70
66
- configureContext ( _httpContext ) ;
71
+ configureContext ( _httpContext , _requestPipe . Reader ) ;
72
+ }
73
+
74
+ internal void SendRequestStream ( Func < PipeWriter , Task > sendRequestStream )
75
+ {
76
+ if ( sendRequestStream == null )
77
+ {
78
+ throw new ArgumentNullException ( nameof ( sendRequestStream ) ) ;
79
+ }
80
+
81
+ _sendRequestStreamTask = sendRequestStream ( _requestPipe . Writer ) ;
67
82
}
68
83
69
84
internal void RegisterResponseReadCompleteCallback ( Action < HttpContext > responseReadCompleteCallback )
@@ -92,10 +107,10 @@ async Task RunRequestAsync()
92
107
// since we are now inside the Server's execution context. If it happens outside this cont
93
108
// it will be lost when we abandon the execution context.
94
109
_testContext = _application . CreateContext ( _httpContext . Features ) ;
95
-
96
110
try
97
111
{
98
112
await _application . ProcessRequestAsync ( _testContext ) ;
113
+ await CompleteRequestAsync ( ) ;
99
114
await CompleteResponseAsync ( ) ;
100
115
_application . DisposeContext ( _testContext , exception : null ) ;
101
116
}
@@ -134,8 +149,40 @@ internal void ClientInitiatedAbort()
134
149
// We don't want to trigger the token for already completed responses.
135
150
_requestLifetimeFeature . Cancel ( ) ;
136
151
}
152
+
137
153
// Writes will still succeed, the app will only get an error if they check the CT.
138
154
_responseReaderStream . Abort ( new IOException ( "The client aborted the request." ) ) ;
155
+
156
+ // Cancel any pending request async activity when the client aborts a duplex
157
+ // streaming scenario by disposing the HttpResponseMessage.
158
+ CancelRequestBody ( ) ;
159
+ }
160
+
161
+ private async Task CompleteRequestAsync ( )
162
+ {
163
+ if ( ! _requestPipe . Reader . TryRead ( out var result ) || ! result . IsCompleted )
164
+ {
165
+ // If request is still in progress then abort it.
166
+ CancelRequestBody ( ) ;
167
+ }
168
+ else
169
+ {
170
+ // Writer was already completed in send request callback.
171
+ await _requestPipe . Reader . CompleteAsync ( ) ;
172
+ }
173
+
174
+ if ( _sendRequestStreamTask != null )
175
+ {
176
+ try
177
+ {
178
+ // Ensure duplex request is either completely read or has been aborted.
179
+ await _sendRequestStreamTask ;
180
+ }
181
+ catch ( OperationCanceledException )
182
+ {
183
+ // Request was canceled, likely because it wasn't read before the request ended.
184
+ }
185
+ }
139
186
}
140
187
141
188
internal async Task CompleteResponseAsync ( )
@@ -192,6 +239,13 @@ internal void Abort(Exception exception)
192
239
_responseReaderStream . Abort ( exception ) ;
193
240
_requestLifetimeFeature . Cancel ( ) ;
194
241
_responseTcs . TrySetException ( exception ) ;
242
+ CancelRequestBody ( ) ;
243
+ }
244
+
245
+ private void CancelRequestBody ( )
246
+ {
247
+ _requestPipe . Writer . CancelPendingFlush ( ) ;
248
+ _requestPipe . Reader . CancelPendingRead ( ) ;
195
249
}
196
250
197
251
void IHttpResetFeature . Reset ( int errorCode )
0 commit comments