1
1
// Licensed to the .NET Foundation under one or more agreements.
2
2
// The .NET Foundation licenses this file to you under the MIT license.
3
3
4
+ using System . Collections . Generic ;
4
5
using System . ComponentModel ;
5
6
using System . Diagnostics ;
6
7
using System . Diagnostics . CodeAnalysis ;
12
13
using System . Net . Http . Headers ;
13
14
using System . Net . Security ;
14
15
using System . Net . Sockets ;
16
+ using System . Runtime . CompilerServices ;
15
17
using System . Runtime . Serialization ;
16
18
using System . Security . Authentication ;
17
19
using System . Security . Cryptography . X509Certificates ;
@@ -40,6 +42,7 @@ public class HttpWebRequest : WebRequest, ISerializable
40
42
private IWebProxy ? _proxy = WebRequest . DefaultWebProxy ;
41
43
42
44
private Task < HttpResponseMessage > ? _sendRequestTask ;
45
+ private HttpRequestMessage ? _sendRequestMessage ;
43
46
44
47
private static int _defaultMaxResponseHeadersLength = HttpHandlerDefaults . DefaultMaxResponseHeadersLength ;
45
48
private static int _defaultMaximumErrorResponseLength = - 1 ;
@@ -62,7 +65,7 @@ public class HttpWebRequest : WebRequest, ISerializable
62
65
private bool _hostHasPort ;
63
66
private Uri ? _hostUri ;
64
67
65
- private RequestStream ? _requestStream ;
68
+ private Stream ? _requestStream ;
66
69
private TaskCompletionSource < Stream > ? _requestStreamOperation ;
67
70
private TaskCompletionSource < WebResponse > ? _responseOperation ;
68
71
private AsyncCallback ? _requestStreamCallback ;
@@ -78,6 +81,8 @@ public class HttpWebRequest : WebRequest, ISerializable
78
81
private static readonly object s_syncRoot = new object ( ) ;
79
82
private static volatile HttpClient ? s_cachedHttpClient ;
80
83
private static HttpClientParameters ? s_cachedHttpClientParameters ;
84
+ private bool _disposeRequired ;
85
+ private HttpClient ? _httpClient ;
81
86
82
87
//these should be safe.
83
88
[ Flags ]
@@ -1003,17 +1008,17 @@ public override void Abort()
1003
1008
{
1004
1009
_responseCallback ( _responseOperation . Task ) ;
1005
1010
}
1006
-
1007
- // Cancel the underlying send operation.
1008
- Debug . Assert ( _sendRequestCts != null ) ;
1009
- _sendRequestCts . Cancel ( ) ;
1010
1011
}
1011
- else if ( _requestStreamOperation != null )
1012
+ if ( _requestStreamOperation != null )
1012
1013
{
1013
1014
if ( _requestStreamOperation . TrySetCanceled ( ) && _requestStreamCallback != null )
1014
1015
{
1015
1016
_requestStreamCallback ( _requestStreamOperation . Task ) ;
1016
1017
}
1018
+
1019
+ // Cancel the underlying send operation.
1020
+ Debug . Assert ( _sendRequestCts != null ) ;
1021
+ _sendRequestCts . Cancel ( ) ;
1017
1022
}
1018
1023
}
1019
1024
@@ -1041,8 +1046,7 @@ public override WebResponse GetResponse()
1041
1046
{
1042
1047
try
1043
1048
{
1044
- _sendRequestCts = new CancellationTokenSource ( ) ;
1045
- return SendRequest ( async: false ) . GetAwaiter ( ) . GetResult ( ) ;
1049
+ return HandleResponse ( async: false ) . GetAwaiter ( ) . GetResult ( ) ;
1046
1050
}
1047
1051
catch ( Exception ex )
1048
1052
{
@@ -1052,10 +1056,11 @@ public override WebResponse GetResponse()
1052
1056
1053
1057
public override Stream GetRequestStream ( )
1054
1058
{
1059
+ CheckRequestStream ( ) ;
1055
1060
return InternalGetRequestStream ( ) . Result ;
1056
1061
}
1057
1062
1058
- private Task < Stream > InternalGetRequestStream ( )
1063
+ private void CheckRequestStream ( )
1059
1064
{
1060
1065
CheckAbort ( ) ;
1061
1066
@@ -1073,10 +1078,28 @@ private Task<Stream> InternalGetRequestStream()
1073
1078
{
1074
1079
throw new InvalidOperationException ( SR . net_reqsubmitted ) ;
1075
1080
}
1081
+ }
1076
1082
1077
- _requestStream = new RequestStream ( ) ;
1083
+ private async Task < Stream > InternalGetRequestStream ( )
1084
+ {
1085
+ // If we aren't buffering we need to open the connection right away.
1086
+ // Because we need to send the data as soon as possible when it's available from the RequestStream.
1087
+ // Making this allows us to keep the sync send request path for buffering cases.
1088
+ if ( AllowWriteStreamBuffering is false )
1089
+ {
1090
+ // We're calling SendRequest with async, because we need to open the connection and send the request
1091
+ // Otherwise, sync path will block the current thread until the request is sent.
1092
+ TaskCompletionSource < Stream > getStreamTcs = new ( ) ;
1093
+ TaskCompletionSource completeTcs = new ( ) ;
1094
+ _sendRequestTask = SendRequest ( async: true , new RequestStreamContent ( getStreamTcs , completeTcs ) ) ;
1095
+ _requestStream = new RequestStream ( await getStreamTcs . Task . ConfigureAwait ( false ) , completeTcs ) ;
1096
+ }
1097
+ else
1098
+ {
1099
+ _requestStream = new RequestBufferingStream ( ) ;
1100
+ }
1078
1101
1079
- return Task . FromResult ( ( Stream ) _requestStream ) ;
1102
+ return _requestStream ;
1080
1103
}
1081
1104
1082
1105
public Stream EndGetRequestStream ( IAsyncResult asyncResult , out TransportContext ? context )
@@ -1100,6 +1123,8 @@ public override IAsyncResult BeginGetRequestStream(AsyncCallback? callback, obje
1100
1123
throw new InvalidOperationException ( SR . net_repcall ) ;
1101
1124
}
1102
1125
1126
+ CheckRequestStream ( ) ;
1127
+
1103
1128
_requestStreamCallback = callback ;
1104
1129
_requestStreamOperation = InternalGetRequestStream ( ) . ToApm ( callback , state ) ;
1105
1130
@@ -1133,78 +1158,95 @@ public override Stream EndGetRequestStream(IAsyncResult asyncResult)
1133
1158
return stream ;
1134
1159
}
1135
1160
1136
- private async Task < WebResponse > SendRequest ( bool async )
1161
+ private Task < HttpResponseMessage > SendRequest ( bool async , HttpContent ? content = null )
1137
1162
{
1138
1163
if ( RequestSubmitted )
1139
1164
{
1140
1165
throw new InvalidOperationException ( SR . net_reqsubmitted ) ;
1141
1166
}
1142
1167
1143
- var request = new HttpRequestMessage ( HttpMethod . Parse ( _originVerb ) , _requestUri ) ;
1168
+ _sendRequestMessage = new HttpRequestMessage ( HttpMethod . Parse ( _originVerb ) , _requestUri ) ;
1169
+ _sendRequestCts = new CancellationTokenSource ( ) ;
1170
+ _httpClient = GetCachedOrCreateHttpClient ( async , out _disposeRequired ) ;
1144
1171
1145
- bool disposeRequired = false ;
1146
- HttpClient ? client = null ;
1147
- try
1172
+ if ( content is not null )
1173
+ {
1174
+ _sendRequestMessage. Content = content;
1175
+ }
1176
+
1177
+ if ( _hostUri is not null )
1148
1178
{
1149
- client = GetCachedOrCreateHttpClient ( async , out disposeRequired ) ;
1150
- if ( _requestStream != null )
1179
+ _sendRequestMessage. Headers. Host = Host;
1180
+ }
1181
+
1182
+ AddCacheControlHeaders( _sendRequestMessage ) ;
1183
+
1184
+ // Copy the HttpWebRequest request headers from the WebHeaderCollection into HttpRequestMessage.Headers and
1185
+ // HttpRequestMessage.Content.Headers.
1186
+ foreach ( string headerName in _webHeaderCollection)
1187
+ {
1188
+ // The System.Net.Http APIs require HttpRequestMessage headers to be properly divided between the request headers
1189
+ // collection and the request content headers collection for all well-known header names. And custom headers
1190
+ // are only allowed in the request headers collection and not in the request content headers collection.
1191
+ if ( IsWellKnownContentHeader ( headerName ) )
1151
1192
{
1152
- ArraySegment < byte > bytes = _requestStream . GetBuffer ( ) ;
1153
- request . Content = new ByteArrayContent ( bytes . Array ! , bytes . Offset , bytes . Count ) ;
1193
+ _sendRequestMessage . Content ? ? = new ByteArrayContent ( Array . Empty < byte > ( ) ) ;
1194
+ _sendRequestMessage . Content . Headers . TryAddWithoutValidation ( headerName , _webHeaderCollection [ headerName ! ] ) ;
1154
1195
}
1155
-
1156
- if ( _hostUri != null )
1196
+ else
1157
1197
{
1158
- request . Headers . Host = Host ;
1198
+ _sendRequestMessage . Headers . TryAddWithoutValidation ( headerName , _webHeaderCollection [ headerName ! ] ) ;
1159
1199
}
1200
+ }
1160
1201
1161
- AddCacheControlHeaders ( request ) ;
1202
+ if ( _servicePoint ? . Expect100Continue == true)
1203
+ {
1204
+ _sendRequestMessage . Headers . ExpectContinue = true;
1205
+ }
1162
1206
1163
- // Copy the HttpWebRequest request headers from the WebHeaderCollection into HttpRequestMessage.Headers and
1164
- // HttpRequestMessage.Content.Headers.
1165
- foreach ( string headerName in _webHeaderCollection )
1166
- {
1167
- // The System.Net.Http APIs require HttpRequestMessage headers to be properly divided between the request headers
1168
- // collection and the request content headers collection for all well-known header names. And custom headers
1169
- // are only allowed in the request headers collection and not in the request content headers collection.
1170
- if ( IsWellKnownContentHeader ( headerName ) )
1171
- {
1172
- // Create empty content so that we can send the entity-body header.
1173
- request . Content ??= new ByteArrayContent ( Array . Empty < byte > ( ) ) ;
1207
+ _sendRequestMessage . Headers . TransferEncodingChunked = SendChunked ;
1174
1208
1175
- request . Content . Headers . TryAddWithoutValidation ( headerName , _webHeaderCollection [ headerName ! ] ) ;
1176
- }
1177
- else
1178
- {
1179
- request . Headers . TryAddWithoutValidation ( headerName , _webHeaderCollection [ headerName ! ] ) ;
1180
- }
1181
- }
1209
+ if ( KeepAlive )
1210
+ {
1211
+ _sendRequestMessage. Headers. Connection. Add( HttpKnownHeaderNames. KeepAlive) ;
1212
+ }
1213
+ else
1214
+ {
1215
+ _sendRequestMessage. Headers. ConnectionClose = true;
1216
+ }
1182
1217
1183
- request . Headers . TransferEncodingChunked = SendChunked ;
1218
+ _sendRequestMessage. Version = ProtocolVersion;
1219
+ HttpCompletionOption completionOption = _allowReadStreamBuffering ? HttpCompletionOption. ResponseContentRead : HttpCompletionOption. ResponseHeadersRead;
1220
+ // If we're not buffering, there is no way to open the connection and not send the request without async.
1221
+ // So we should use Async, if we're not buffering.
1222
+ _sendRequestTask = async || ! AllowWriteStreamBuffering ?
1223
+ _httpClient. SendAsync( _sendRequestMessage , completionOption , _sendRequestCts . Token ) :
1224
+ Task . FromResult ( _httpClient . Send ( _sendRequestMessage , completionOption , _sendRequestCts . Token ) ) ;
1184
1225
1185
- if ( KeepAlive )
1186
- {
1187
- request . Headers . Connection . Add ( HttpKnownHeaderNames . KeepAlive ) ;
1188
- }
1189
- else
1190
- {
1191
- request . Headers . ConnectionClose = true ;
1192
- }
1226
+ return _sendRequestTask ! ;
1227
+ }
1193
1228
1194
- if ( _servicePoint ? . Expect100Continue == true )
1195
- {
1196
- request . Headers . ExpectContinue = true ;
1197
- }
1229
+ private async Task < WebResponse > HandleResponse ( bool async )
1230
+ {
1231
+ // If user code used requestStream and didn't dispose it
1232
+ // We're completing it here.
1233
+ if ( _requestStream is RequestStream requestStream)
1234
+ {
1235
+ requestStream. Complete( ) ;
1236
+ }
1198
1237
1199
- request . Version = ProtocolVersion ;
1238
+ if ( _sendRequestTask is null && _requestStream is RequestBufferingStream requestBufferingStream)
1239
+ {
1240
+ ArraySegment< byte > buffer = requestBufferingStream. GetBuffer( ) ;
1241
+ _sendRequestTask = SendRequest( async , new ByteArrayContent ( buffer . Array ! , buffer . Offset , buffer . Count ) ) ;
1242
+ }
1200
1243
1201
- _sendRequestTask = async ?
1202
- client . SendAsync ( request , _allowReadStreamBuffering ? HttpCompletionOption . ResponseContentRead : HttpCompletionOption . ResponseHeadersRead , _sendRequestCts ! . Token ) :
1203
- Task . FromResult ( client . Send ( request , _allowReadStreamBuffering ? HttpCompletionOption . ResponseContentRead : HttpCompletionOption . ResponseHeadersRead , _sendRequestCts ! . Token ) ) ;
1244
+ _sendRequestTask ??= SendRequest ( async ) ;
1204
1245
1246
+ try
1247
+ {
1205
1248
HttpResponseMessage responseMessage = await _sendRequestTask . ConfigureAwait ( false ) ;
1206
-
1207
- HttpWebResponse response = new HttpWebResponse ( responseMessage , _requestUri , _cookieContainer ) ;
1249
+ HttpWebResponse response = new ( responseMessage , _requestUri , _cookieContainer ) ;
1208
1250
1209
1251
int maxSuccessStatusCode = AllowAutoRedirect ? 299 : 399 ;
1210
1252
if ( ( int ) response . StatusCode > maxSuccessStatusCode || ( int ) response . StatusCode < 200 )
@@ -1220,9 +1262,15 @@ private async Task<WebResponse> SendRequest(bool async)
1220
1262
}
1221
1263
finally
1222
1264
{
1223
- if ( disposeRequired )
1265
+ _sendRequestMessage ? . Dispose ( ) ;
1266
+ if ( _requestStream is RequestBufferingStream bufferStream)
1224
1267
{
1225
- client ? . Dispose ( ) ;
1268
+ bufferStream . GetMemoryStream ( ) . Dispose ( ) ;
1269
+ }
1270
+
1271
+ if ( _disposeRequired )
1272
+ {
1273
+ _httpClient? . Dispose( ) ;
1226
1274
}
1227
1275
}
1228
1276
}
@@ -1348,9 +1396,8 @@ public override IAsyncResult BeginGetResponse(AsyncCallback? callback, object? s
1348
1396
throw new InvalidOperationException ( SR . net_repcall ) ;
1349
1397
}
1350
1398
1351
- _sendRequestCts = new CancellationTokenSource ( ) ;
1352
1399
_responseCallback = callback;
1353
- _responseOperation = SendRequest ( async: true ) . ToApm ( callback , state ) ;
1400
+ _responseOperation = HandleResponse ( async : true) . ToApm ( callback , state ) ;
1354
1401
1355
1402
return _responseOperation . Task ;
1356
1403
}
0 commit comments