Skip to content

Commit 26cc6f1

Browse files
Delay reading the HttpClient response until the ReceiveData property is accessed (#879)
* Delay reading the HttpClient response until the ReceiveData property is accessed. It allows chunked reading when the ReadChunk method is called. * Rename _chunkedResponse to _response (the previous local variable 'response' is now an instance variable named '_response'). Rename ReadReponseData to ReadResponseData. * Reset the _receiveData variable on HttpClientExecute; otherwise, a second execution on the same HttpClient instance may read the previous content. * Disable compression in ASP.NET when streaming, as buffering is not supported alongside compression.
1 parent a849d02 commit 26cc6f1

File tree

8 files changed

+311
-73
lines changed

8 files changed

+311
-73
lines changed

dotnet/DotNetStandardClasses.sln

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -249,6 +249,7 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "GXAzureEventGrid", "src\dot
249249
EndProject
250250
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "DotNetCoreAttackMitigationTest", "test\DotNetCoreAttackMitigationTest\DotNetCoreAttackMitigationTest.csproj", "{2D615969-53E2-4B77-9A9A-75C33865CF76}"
251251
EndProject
252+
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "DotNetCoreChunkedTest", "test\DotNetCoreChunkedTest\DotNetCoreChunkedTest.csproj", "{5D2B1299-479F-430A-8D72-34D44FB299FD}"
252253
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "DotNetPDFUnitTest", "test\DotNetPdfTest\DotNetPDFUnitTest.csproj", "{0FCFB078-5584-469F-92CC-61B0A6216D0D}"
253254
EndProject
254255
Global
@@ -605,6 +606,10 @@ Global
605606
{2D615969-53E2-4B77-9A9A-75C33865CF76}.Debug|Any CPU.Build.0 = Debug|Any CPU
606607
{2D615969-53E2-4B77-9A9A-75C33865CF76}.Release|Any CPU.ActiveCfg = Release|Any CPU
607608
{2D615969-53E2-4B77-9A9A-75C33865CF76}.Release|Any CPU.Build.0 = Release|Any CPU
609+
{5D2B1299-479F-430A-8D72-34D44FB299FD}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
610+
{5D2B1299-479F-430A-8D72-34D44FB299FD}.Debug|Any CPU.Build.0 = Debug|Any CPU
611+
{5D2B1299-479F-430A-8D72-34D44FB299FD}.Release|Any CPU.ActiveCfg = Release|Any CPU
612+
{5D2B1299-479F-430A-8D72-34D44FB299FD}.Release|Any CPU.Build.0 = Release|Any CPU
608613
{0FCFB078-5584-469F-92CC-61B0A6216D0D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
609614
{0FCFB078-5584-469F-92CC-61B0A6216D0D}.Debug|Any CPU.Build.0 = Debug|Any CPU
610615
{0FCFB078-5584-469F-92CC-61B0A6216D0D}.Release|Any CPU.ActiveCfg = Release|Any CPU
@@ -728,6 +733,7 @@ Global
728733
{5BBC75F0-E51A-4EBD-A628-92498D319B1D} = {4C43F2DA-59E5-46F5-B691-195449498555}
729734
{7250CDB1-95C4-4822-B01B-3CBD73324CC9} = {30159B0F-BE61-4DB7-AC02-02851426BE4B}
730735
{2D615969-53E2-4B77-9A9A-75C33865CF76} = {1D6F1776-FF4B-46C2-9B3D-BC46CCF049DC}
736+
{5D2B1299-479F-430A-8D72-34D44FB299FD} = {1D6F1776-FF4B-46C2-9B3D-BC46CCF049DC}
731737
{0FCFB078-5584-469F-92CC-61B0A6216D0D} = {1D6F1776-FF4B-46C2-9B3D-BC46CCF049DC}
732738
EndGlobalSection
733739
GlobalSection(ExtensibilityGlobals) = postSolution

dotnet/src/dotnetcore/GxClasses.Web/Properties/AssemblyInfo.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,3 +7,4 @@
77
[assembly: InternalsVisibleTo("GeneXus.Deploy.AzureFunctions.Handlers")]
88
[assembly: InternalsVisibleTo("AzureFunctionsTest")]
99
[assembly: InternalsVisibleTo("DotNetCoreAttackMitigationTest")]
10+
[assembly: InternalsVisibleTo("DotNetCoreChunkedTest")]

dotnet/src/dotnetcore/GxClasses/Properties/AssemblyInfo.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,3 +14,4 @@
1414
[assembly: InternalsVisibleTo("GeneXus.Deploy.AzureFunctions.Handlers")]
1515
[assembly: InternalsVisibleTo("AzureFunctionsTest")]
1616
[assembly: InternalsVisibleTo("GXMessageBroker")]
17+
[assembly: InternalsVisibleTo("DotNetCoreChunkedTest")]

dotnet/src/dotnetframework/GxClasses/Core/Web/GxHttpServer.cs

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -148,7 +148,16 @@ public void AppendHeader( string name, string value)
148148
{
149149
value = GXUtil.EncodeContentDispositionHeader(value, _context.GetBrowserType());
150150
}
151-
if (_context!=null)
151+
#if !NETCORE
152+
else if (string.Compare(name,"Content-Type", true) == 0)
153+
{
154+
if (string.Compare(value, "text/event-stream", true) == 0)
155+
{
156+
_context.HttpContext.Response.BufferOutput = false;
157+
}
158+
}
159+
#endif
160+
if (_context!=null)
152161
_context.SetHeader(name, value);
153162
}
154163

dotnet/src/dotnetframework/GxClasses/Domain/GxHttpClient.cs

Lines changed: 127 additions & 72 deletions
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,11 @@ public class GxHttpClient : IGxHttpClient, IDisposable
7777
NameValueCollection _headers;
7878
NameValueCollection _formVars;
7979
MultiPartTemplate _multipartTemplate;
80+
bool _isChunkedResponse;
81+
HttpResponseMessage _response;
82+
bool _eof;
83+
bool _encodingFound;
84+
string _charset;
8085

8186

8287
string _scheme = "http://";
@@ -135,6 +140,7 @@ internal byte[] ReceiveData
135140
{
136141
get
137142
{
143+
ReadResponseData();
138144
return _receiveData;
139145
}
140146
}
@@ -703,58 +709,40 @@ HttpResponseMessage ExecuteRequest(string method, string requestUrl, CookieConta
703709
reqStream.Seek(0, SeekOrigin.Begin);
704710
request.Content = new ByteArrayContent(reqStream.ToArray());
705711
setHeaders(request, handler.CookieContainer);
706-
response = client.SendAsync(request).GetAwaiter().GetResult();
712+
response = client.SendAsync(request, HttpCompletionOption.ResponseHeadersRead).GetAwaiter().GetResult();
707713
}
708714
}
709715
return response;
710-
}
711-
void ReadReponseContent(HttpResponseMessage response)
716+
}
717+
void ReadResponseData()
712718
{
713-
_receiveData = Array.Empty<byte>();
714-
try
719+
if (_receiveData == null && _response!=null)
715720
{
716-
Stream stream = response.Content.ReadAsStreamAsync().GetAwaiter().GetResult();
717-
string charset;
718-
if (response.Content.Headers.ContentType == null)
719-
charset = null;
720-
else
721-
charset = response.Content.Headers.ContentType.CharSet;
722-
bool encodingFound = false;
723-
if (!string.IsNullOrEmpty(charset))
721+
_receiveData = Array.Empty<byte>();
722+
try
724723
{
725-
int idx = charset.IndexOf("charset=");
726-
if (idx > 0)
724+
Stream stream = _response.Content.ReadAsStreamAsync().GetAwaiter().GetResult();
725+
726+
using (MemoryStream ms = new MemoryStream())
727727
{
728-
idx += 8;
729-
charset = charset.Substring(idx, charset.Length - idx);
730-
_encoding = GetEncoding(charset);
731-
if (_encoding != null)
732-
encodingFound = true;
728+
stream.CopyTo(ms);
729+
_receiveData = ms.ToArray();
733730
}
734-
else
731+
_eof = true;
732+
int bytesRead = _receiveData.Length;
733+
GXLogging.Debug(log, "BytesRead " + _receiveData.Length);
734+
if (bytesRead > 0 && !_encodingFound)
735735
{
736-
charset = String.Empty;
736+
_encoding = DetectEncoding(_charset, out _encodingFound, _receiveData, bytesRead);
737737
}
738738
}
739-
740-
using (MemoryStream ms = new MemoryStream())
739+
catch (IOException ioEx)
741740
{
742-
stream.CopyTo(ms);
743-
_receiveData = ms.ToArray();
741+
if (_errCode == 1)
742+
GXLogging.Warn(log, "Could not read response", ioEx);
743+
else
744+
throw ioEx;
744745
}
745-
int bytesRead = _receiveData.Length;
746-
GXLogging.Debug(log, "BytesRead " + _receiveData.Length);
747-
if (bytesRead > 0 && !encodingFound)
748-
{
749-
_encoding = DetectEncoding(charset, out encodingFound, _receiveData, bytesRead);
750-
}
751-
}
752-
catch (IOException ioEx)
753-
{
754-
if (_errCode == 1)
755-
GXLogging.Warn(log, "Could not read response", ioEx);
756-
else
757-
throw ioEx;
758746
}
759747
}
760748
bool UseOldHttpClient(string name)
@@ -790,10 +778,22 @@ public void Execute(string method, string name)
790778
HttpClientExecute(method, name);
791779
}
792780
}
793-
781+
internal void ProcessResponse(HttpResponseMessage httpResponse)
782+
{
783+
_response = httpResponse;
784+
LoadResponseHeaders(_response);
785+
_statusCode = ((short)_response.StatusCode);
786+
_statusDescription = GetStatusCodeDescrption(_response);
787+
if ((_statusCode >= 400 && _statusCode < 600) && _errCode != 1)
788+
{
789+
_errCode = 1;
790+
_errDescription = "The remote server returned an error: (" + _statusCode + ") " + _statusDescription + ".";
791+
}
792+
}
794793
public void HttpClientExecute(string method, string name)
795794
{
796-
HttpResponseMessage response = null;
795+
_receiveData = null;
796+
_response = null;
797797
Byte[] Buffer = new Byte[1024];
798798
_errCode = 0;
799799
_errDescription = string.Empty;
@@ -803,7 +803,7 @@ public void HttpClientExecute(string method, string name)
803803
string requestUrl = GetRequestURL(name);
804804
bool contextCookies = _context != null && !String.IsNullOrEmpty(requestUrl);
805805
CookieContainer cookies = contextCookies ? _context.GetCookieContainer(requestUrl, IncludeCookies) : new CookieContainer();
806-
response = ExecuteRequest(method, requestUrl, cookies);
806+
_response = ExecuteRequest(method, requestUrl, cookies);
807807

808808
if (contextCookies)
809809
_context.UpdateSessionCookieContainer();
@@ -819,9 +819,9 @@ public void HttpClientExecute(string method, string name)
819819
_errDescription = aex.InnerException.Message;
820820
else
821821
_errDescription = aex.Message;
822-
response = new HttpResponseMessage();
823-
response.Content = new StringContent(HttpHelper.StatusCodeToTitle(HttpStatusCode.InternalServerError));
824-
response.StatusCode = HttpStatusCode.InternalServerError;
822+
_response = new HttpResponseMessage();
823+
_response.Content = new StringContent(HttpHelper.StatusCodeToTitle(HttpStatusCode.InternalServerError));
824+
_response.StatusCode = HttpStatusCode.InternalServerError;
825825
}
826826
#endif
827827
catch (HttpRequestException e)
@@ -832,22 +832,22 @@ public void HttpClientExecute(string method, string name)
832832
_errDescription = e.Message + " " + e.InnerException.Message;
833833
else
834834
_errDescription = e.Message;
835-
response = new HttpResponseMessage();
836-
response.Content = new StringContent(HttpHelper.StatusCodeToTitle(HttpStatusCode.InternalServerError));
835+
_response = new HttpResponseMessage();
836+
_response.Content = new StringContent(HttpHelper.StatusCodeToTitle(HttpStatusCode.InternalServerError));
837837
#if NETCORE
838-
response.StatusCode = (HttpStatusCode)(e.StatusCode != null ? e.StatusCode : HttpStatusCode.InternalServerError);
838+
_response.StatusCode = (HttpStatusCode)(e.StatusCode != null ? e.StatusCode : HttpStatusCode.InternalServerError);
839839
#else
840-
response.StatusCode = HttpStatusCode.InternalServerError;
840+
_response.StatusCode = HttpStatusCode.InternalServerError;
841841
#endif
842842
}
843843
catch (TaskCanceledException e)
844844
{
845845
GXLogging.Warn(log, "Error Execute", e);
846846
_errCode = 1;
847847
_errDescription = "The request has timed out. " + e.Message;
848-
response = new HttpResponseMessage();
849-
response.StatusCode = 0;
850-
response.Content = new StringContent(String.Empty);
848+
_response = new HttpResponseMessage();
849+
_response.StatusCode = 0;
850+
_response.Content = new StringContent(String.Empty);
851851
}
852852
catch (Exception e)
853853
{
@@ -857,29 +857,20 @@ public void HttpClientExecute(string method, string name)
857857
_errDescription = e.Message + " " + e.InnerException.Message;
858858
else
859859
_errDescription = e.Message;
860-
response = new HttpResponseMessage();
861-
response.Content = new StringContent(HttpHelper.StatusCodeToTitle(HttpStatusCode.InternalServerError));
862-
response.StatusCode = HttpStatusCode.InternalServerError;
860+
_response = new HttpResponseMessage();
861+
_response.Content = new StringContent(HttpHelper.StatusCodeToTitle(HttpStatusCode.InternalServerError));
862+
_response.StatusCode = HttpStatusCode.InternalServerError;
863863
}
864864
GXLogging.Debug(log, "Reading response...");
865-
if (response == null)
865+
if (_response == null)
866866
return;
867-
LoadResponseHeaders(response);
868-
ReadReponseContent(response);
869-
_statusCode = ((short)response.StatusCode);
870-
_statusDescription = GetStatusCodeDescrption(response);
871-
if ((_statusCode >= 400 && _statusCode < 600) && _errCode != 1)
872-
{
873-
_errCode = 1;
874-
_errDescription = "The remote server returned an error: (" + _statusCode + ") " + _statusDescription + ".";
875-
}
867+
ProcessResponse(_response);
876868
ClearSendStream();
877-
GXLogging.DebugSanitized(log, "_responseString " + ToString());
878869
}
879870
NameValueCollection _respHeaders;
880871
private bool disposedValue;
881872

882-
void LoadResponseHeaders(HttpResponseMessage resp)
873+
internal void LoadResponseHeaders(HttpResponseMessage resp)
883874
{
884875
_respHeaders = new NameValueCollection();
885876
foreach (KeyValuePair<string, IEnumerable<string>> header in resp.Headers)
@@ -890,6 +881,29 @@ void LoadResponseHeaders(HttpResponseMessage resp)
890881
{
891882
_respHeaders.Add(header.Key, String.Join(",", header.Value));
892883
}
884+
_isChunkedResponse = resp.Headers.TransferEncodingChunked.HasValue && resp.Headers.TransferEncodingChunked.Value;
885+
886+
if (_response.Content.Headers.ContentType == null)
887+
_charset = null;
888+
else
889+
_charset = _response.Content.Headers.ContentType.CharSet;
890+
_encodingFound = false;
891+
if (!string.IsNullOrEmpty(_charset))
892+
{
893+
int idx = _charset.IndexOf("charset=");
894+
if (idx > 0)
895+
{
896+
idx += 8;
897+
_charset = _charset.Substring(idx, _charset.Length - idx);
898+
_encoding = GetEncoding(_charset);
899+
if (_encoding != null)
900+
_encodingFound = true;
901+
}
902+
else
903+
{
904+
_charset = String.Empty;
905+
}
906+
}
893907
}
894908

895909
private string GetStatusCodeDescrption(HttpResponseMessage message)
@@ -1366,14 +1380,54 @@ private Encoding ExtractEncodingFromCharset(string responseText, string regExpP,
13661380
}
13671381
return enc;
13681382
}
1383+
public bool Eof
1384+
{
1385+
get
1386+
{
1387+
return _eof;
1388+
1389+
}
1390+
}
1391+
StreamReader _receivedChunkedStream;
1392+
public string ReadChunk()
1393+
{
1394+
if (!_isChunkedResponse)
1395+
return ToString();
1396+
1397+
if (_response == null)
1398+
return string.Empty;
1399+
try
1400+
{
1401+
if (_receivedChunkedStream == null)
1402+
{
1403+
_receivedChunkedStream = new StreamReader(_response.Content.ReadAsStreamAsync().GetAwaiter().GetResult());
1404+
}
1405+
_eof = _receivedChunkedStream.EndOfStream;
1406+
if (!_eof)
1407+
{
1408+
string line = _receivedChunkedStream.ReadLine();
1409+
if (line != null)
1410+
{
1411+
return line;
1412+
}
1413+
}
1414+
}
1415+
catch (Exception ex)
1416+
{
1417+
GXLogging.Error(log, String.Format("Error reading chunk", ex));
1418+
}
1419+
return string.Empty;
13691420

1421+
}
13701422
public override string ToString()
13711423
{
1424+
if (ReceiveData == null)
1425+
return string.Empty;
13721426
if (_encoding == null)
13731427
_encoding = Encoding.UTF8;
1374-
if (_receiveData == null)
1375-
return string.Empty;
1376-
return _encoding.GetString(_receiveData);
1428+
string responseString = _encoding.GetString(ReceiveData);
1429+
GXLogging.DebugSanitized(log, "_responseString " + responseString);
1430+
return responseString;
13771431
}
13781432
public void ToFile(string fileName)
13791433
{
@@ -1385,9 +1439,9 @@ public void ToFile(string fileName)
13851439
if (fileName.IndexOfAny(new char[] { '\\', ':' }) == -1)
13861440
pathName = Path.Combine(GxContext.StaticPhysicalPath(), fileName);
13871441

1388-
if (_receiveData != null)
1442+
if (ReceiveData != null)
13891443
{
1390-
File.WriteAllBytes(pathName, _receiveData);
1444+
File.WriteAllBytes(pathName, ReceiveData);
13911445
}
13921446
}
13931447

@@ -1463,6 +1517,7 @@ protected virtual void Dispose(bool disposing)
14631517
{
14641518
_receiveData = null;
14651519
_sendStream?.Dispose();
1520+
_receivedChunkedStream?.Dispose();
14661521
}
14671522
disposedValue = true;
14681523
}

dotnet/src/dotnetframework/GxClasses/Middleware/GXHttp.cs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2275,7 +2275,11 @@ public bool IsReusable
22752275

22762276
protected virtual void SetCompression(HttpContext httpContext)
22772277
{
2278+
#if NETCORE
22782279
if (CompressHtmlResponse())
2280+
#else
2281+
if (CompressHtmlResponse() && httpContext.Response.BufferOutput)
2282+
#endif
22792283
{
22802284
GXUtil.SetGZip(httpContext);
22812285
}

0 commit comments

Comments
 (0)