Skip to content

Commit 4fbfb00

Browse files
Add support for chunked httpclient and httpresponse. (#823)
* Add support for chunked httpclient and chuncked httpresponse. * Do not append headers for X-GXOBJECT and X-UA-Compatible. X-GXOBJECT is set twice when called from SendHeaders and ValidateSpaRequest from webExecute. * Condition content-disposition=inline; filename to reports only. * Define new property EOF and method ReadChunk for HttpClient datatype. * Delay adding the header X-SPA-MP until the MasterPageObj is instantiated. Since sendSpaHeaders is called at the start of execution, the MasterPageObj is null at this moment. * Define method ChunkedStreaming to set chunkedResponse at procedure level * Setup Streaming after httpcontext is initialized * Remove extra changes in spacing and indentation. * _chunkedResponse condition depends only on TransferEncodingChunked header in the response. * Use BufferOutput instead of the deprecated Buffer property in HttpResponse. * Remove extra blank lines. * ReceiveData was null when httpclient response is chunked. Issue:103474 * Access SessionId at the beginning of Streamed https procs o to avoid HttpException: Session state has created a session id, but cannot save it because the response was already flushed by the application. * Limit BringSessionStateToLife to .NET Framework.
1 parent ef24acc commit 4fbfb00

File tree

5 files changed

+135
-48
lines changed

5 files changed

+135
-48
lines changed

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

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -12,12 +12,6 @@ namespace GeneXus.Http.Server
1212
using System.Linq;
1313
using Microsoft.AspNetCore.Http.Features;
1414
using System.Text;
15-
using System.Threading.Tasks;
16-
using Microsoft.AspNetCore.Mvc.Formatters;
17-
using System.Net.Http;
18-
using Stubble.Core.Contexts;
19-
using System.Net.Mime;
20-
2115
#endif
2216

2317
public class GxHttpCookie

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

Lines changed: 91 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,7 @@ public class GxHttpClient : IGxHttpClient, IDisposable
6666
const int StreamWriterDefaultBufferSize = 1024;
6767
Stream _sendStream;
6868
byte[] _receiveData;
69+
StreamReader _receiveStream;
6970
int _timeout = 30000;
7071
short _statusCode = 0;
7172
string _proxyHost;
@@ -133,6 +134,14 @@ internal byte[] ReceiveData
133134
{
134135
get
135136
{
137+
if (_chunkedResponse && _receiveData == null && _receiveStream!=null)
138+
{
139+
using (MemoryStream memstream = new MemoryStream())
140+
{
141+
_receiveStream.BaseStream.CopyTo(memstream);
142+
_receiveData = memstream.ToArray();
143+
}
144+
}
136145
return _receiveData;
137146
}
138147
}
@@ -414,6 +423,7 @@ public void AddHeader(string name, string value)
414423
GXLogging.Error(log, String.Format("Error parsing charset ", value, ex));
415424
}
416425
}
426+
417427
_headers.Set(name, value);
418428
}
419429
public void ClearVariables()
@@ -697,7 +707,7 @@ HttpResponseMessage ExecuteRequest(string method, string requestUrl, CookieConta
697707
reqStream.Seek(0, SeekOrigin.Begin);
698708
request.Content = new ByteArrayContent(reqStream.ToArray());
699709
setHeaders(request, handler.CookieContainer);
700-
response = client.SendAsync(request).GetAwaiter().GetResult();
710+
response = client.SendAsync(request, HttpCompletionOption.ResponseHeadersRead).GetAwaiter().GetResult();
701711
}
702712
}
703713
return response;
@@ -708,6 +718,7 @@ void ReadReponseContent(HttpResponseMessage response)
708718
try
709719
{
710720
Stream stream = response.Content.ReadAsStreamAsync().GetAwaiter().GetResult();
721+
_chunkedResponse = response.Headers.TransferEncodingChunked.HasValue && response.Headers.TransferEncodingChunked.Value;
711722
string charset;
712723
if (response.Content.Headers.ContentType == null)
713724
charset = null;
@@ -730,18 +741,28 @@ void ReadReponseContent(HttpResponseMessage response)
730741
charset = String.Empty;
731742
}
732743
}
733-
734-
using (MemoryStream ms = new MemoryStream())
744+
if (_chunkedResponse)
735745
{
736-
stream.CopyTo(ms);
737-
_receiveData = ms.ToArray();
746+
if (_encoding == null)
747+
_encoding = Encoding.UTF8;
748+
749+
_receiveStream = new StreamReader(stream, _encoding);
750+
_receiveData = null;
738751
}
739-
int bytesRead = _receiveData.Length;
740-
GXLogging.Debug(log, "BytesRead " + _receiveData.Length);
741-
if (bytesRead > 0 && !encodingFound)
752+
else
742753
{
743-
_encoding = DetectEncoding(charset, out encodingFound, _receiveData, bytesRead);
744-
}
754+
using (MemoryStream ms = new MemoryStream())
755+
{
756+
stream.CopyTo(ms);
757+
_receiveData = ms.ToArray();
758+
}
759+
int bytesRead = _receiveData.Length;
760+
GXLogging.Debug(log, "BytesRead " + _receiveData.Length);
761+
if (bytesRead > 0 && !encodingFound)
762+
{
763+
_encoding = DetectEncoding(charset, out encodingFound, _receiveData, bytesRead);
764+
}
765+
}
745766
}
746767
catch (IOException ioEx)
747768
{
@@ -787,7 +808,7 @@ public void Execute(string method, string name)
787808

788809
public void HttpClientExecute(string method, string name)
789810
{
790-
HttpResponseMessage response = null;
811+
response = null;
791812
Byte[] Buffer = new Byte[1024];
792813
_errCode = 0;
793814
_errDescription = string.Empty;
@@ -868,11 +889,11 @@ public void HttpClientExecute(string method, string name)
868889
_errDescription = "The remote server returned an error: (" + _statusCode + ") " + _statusDescription + ".";
869890
}
870891
ClearSendStream();
871-
GXLogging.DebugSanitized(log, "_responseString " + ToString());
872892
}
873893
NameValueCollection _respHeaders;
874894
private bool disposedValue;
875-
895+
private bool _chunkedResponse;
896+
HttpResponseMessage response;
876897
void LoadResponseHeaders(HttpResponseMessage resp)
877898
{
878899
_respHeaders = new NameValueCollection();
@@ -1360,14 +1381,65 @@ private Encoding ExtractEncodingFromCharset(string responseText, string regExpP,
13601381
}
13611382
return enc;
13621383
}
1384+
public bool Eof
1385+
{
1386+
get
1387+
{
1388+
if (_chunkedResponse && _receiveStream != null)
1389+
{
1390+
return _receiveStream.EndOfStream;
1391+
}
1392+
return true;
1393+
}
1394+
}
13631395

1364-
public override string ToString()
1396+
public string ReadChunk()
13651397
{
1366-
if (_encoding == null)
1367-
_encoding = Encoding.UTF8;
1368-
if (_receiveData == null)
1398+
if (_chunkedResponse)
1399+
{
1400+
if (_receiveStream != null)
1401+
{
1402+
if (!_receiveStream.EndOfStream)
1403+
{
1404+
string line = _receiveStream.ReadLine();
1405+
if (line != null)
1406+
return line;
1407+
}
1408+
else
1409+
{
1410+
_receiveStream.Dispose();
1411+
_receiveStream = null;
1412+
response.Dispose();
1413+
response = null;
1414+
}
1415+
}
13691416
return string.Empty;
1370-
return _encoding.GetString(_receiveData);
1417+
}
1418+
else
1419+
return ToString();
1420+
}
1421+
public override string ToString()
1422+
{
1423+
string responseString;
1424+
if (_chunkedResponse)
1425+
{
1426+
StringBuilder sb = new StringBuilder();
1427+
while (!Eof){
1428+
sb.Append(ReadChunk());
1429+
}
1430+
responseString = sb.ToString();
1431+
}
1432+
else
1433+
{
1434+
if (_encoding == null)
1435+
_encoding = Encoding.UTF8;
1436+
if (_receiveData == null)
1437+
return string.Empty;
1438+
1439+
responseString = _encoding.GetString(_receiveData);
1440+
}
1441+
GXLogging.DebugSanitized(log, "_responseString " + responseString);
1442+
return responseString;
13711443
}
13721444
public void ToFile(string fileName)
13731445
{
@@ -1457,6 +1529,7 @@ protected virtual void Dispose(bool disposing)
14571529
{
14581530
_receiveData = null;
14591531
_sendStream?.Dispose();
1532+
_receiveStream?.Dispose();
14601533
}
14611534
disposedValue = true;
14621535
}

dotnet/src/dotnetframework/GxClasses/Helpers/HttpHelper.cs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,13 @@ public class HttpHeader
4848
public static string LAST_MODIFIED = "Last-Modified";
4949
public static string EXPIRES = "Expires";
5050
public static string XGXFILENAME = "x-gx-filename";
51+
internal static string ACCEPT = "Accept";
52+
internal static string TRANSFER_ENCODING = "Transfer-Encoding";
53+
}
54+
internal class HttpHeaderValue
55+
{
56+
internal static string ACCEPT_SERVER_SENT_EVENT = "text/event-stream";
57+
internal static string TRANSFER_ENCODING_CHUNKED = "chunked";
5158
}
5259
[DataContract()]
5360
public class HttpJsonError

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

Lines changed: 25 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -1933,10 +1933,10 @@ public void ProcessRequest(HttpContext httpContext)
19331933
InitPrivates();
19341934
try
19351935
{
1936-
#if NETCORE
1936+
SetStreaming();
19371937
SendHeaders();
19381938
string clientid = context.ClientID; //Send clientid cookie (before response HasStarted) if necessary, since UseResponseBuffering is not in .netcore3.0
1939-
#endif
1939+
19401940
bool validSession = ValidWebSession();
19411941
if (validSession && IntegratedSecurityEnabled)
19421942
validSession = ValidSession();
@@ -1971,9 +1971,6 @@ public void ProcessRequest(HttpContext httpContext)
19711971
context.DispatchAjaxCommands();
19721972
}
19731973
SetCompression(httpContext);
1974-
#if !NETCORE
1975-
SendHeaders();
1976-
#endif
19771974
context.ResponseCommited = true;
19781975
}
19791976
catch (Exception e)
@@ -1988,6 +1985,25 @@ public void ProcessRequest(HttpContext httpContext)
19881985
throw new Exception("GXApplication exception", e);
19891986
}
19901987
}
1988+
protected virtual bool ChunkedStreaming() { return false; }
1989+
1990+
private void SetStreaming()
1991+
{
1992+
#if !NETCORE
1993+
if (ChunkedStreaming())
1994+
{
1995+
BringSessionStateToLife();
1996+
context.HttpContext.Response.BufferOutput = false;
1997+
}
1998+
#endif
1999+
}
2000+
#if !NETCORE
2001+
private void BringSessionStateToLife()
2002+
{
2003+
string sessionId = context.HttpContext.Session.SessionID;
2004+
GXLogging.Debug(log, "Session is alive: " + sessionId);
2005+
}
2006+
#endif
19912007
internal string DumpHeaders(HttpContext httpContext)
19922008
{
19932009
StringBuilder str = new StringBuilder();
@@ -2033,7 +2049,6 @@ private bool ValidWebSession()
20332049
#else
20342050
bool isExpired = IsFullAjaxRequest(HttpContext.Current) && this.AjaxOnSessionTimeout() == "Warn" && GxWebSession.IsSessionExpired(localHttpContext);
20352051
#endif
2036-
20372052
if (isExpired)
20382053
{
20392054
GXLogging.Info(log, "440 Session timeout. Web Session has expired and GX' OnSessionTimeout' Pty is set to 'WARN'");
@@ -2221,23 +2236,15 @@ public virtual void sendAdditionalHeaders()
22212236
return;
22222237

22232238
string safeIECompMode = IE_COMP_Edge.Equals(IE_COMP_EmulateIE7) ? IE_COMP_Edge : IE_COMP_Edge;
2224-
#if NETCORE
22252239
localHttpContext.Response.Headers["X-UA-Compatible"] = "IE=" + safeIECompMode;
2226-
#else
2227-
localHttpContext.Response.AddHeader("X-UA-Compatible", "IE=" + safeIECompMode);
2228-
#endif
22292240
}
22302241
}
22312242

22322243
}
22332244

22342245
protected virtual void sendSpaHeaders()
22352246
{
2236-
#if NETCORE
22372247
localHttpContext.Response.Headers[GX_SPA_GXOBJECT_RESPONSE_HEADER] = GetPgmname().ToLower();
2238-
#else
2239-
localHttpContext.Response.AddHeader(GX_SPA_GXOBJECT_RESPONSE_HEADER, GetPgmname().ToLower());
2240-
#endif
22412248
}
22422249

22432250
private void webExecuteWorker(object target)
@@ -3063,15 +3070,6 @@ public abstract class GXDataArea : GXHttpHandler
30633070

30643071
protected GXMasterPage MasterPageObj { get; set; }
30653072

3066-
protected override void sendSpaHeaders()
3067-
{
3068-
base.sendSpaHeaders();
3069-
if (MasterPageObj != null)
3070-
{
3071-
localHttpContext.Response.AddHeader(GX_SPA_MASTERPAGE_HEADER, MasterPageObj.GetPgmname());
3072-
}
3073-
}
3074-
30753073
protected override void ValidateSpaRequest()
30763074
{
30773075
string sourceMasterPage = localHttpContext.Request.Headers[GX_SPA_MASTERPAGE_HEADER];
@@ -3080,6 +3078,10 @@ protected override void ValidateSpaRequest()
30803078
context.DisableSpaRequest();
30813079
sendSpaHeaders();
30823080
}
3081+
if (MasterPageObj != null)
3082+
{
3083+
localHttpContext.Response.Headers[GX_SPA_MASTERPAGE_HEADER] = MasterPageObj.GetPgmname();
3084+
}
30833085
}
30843086
}
30853087
public class GXDataAreaControl

dotnet/src/dotnetframework/GxClasses/Model/GXWebProcedure.cs

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,11 @@ namespace GeneXus.Procedure
88
using System.Threading;
99
using GeneXus.Mime;
1010
using GeneXus.Utils;
11+
#if NETCORE
12+
using Microsoft.AspNetCore.Http;
13+
#else
14+
using System.Web;
15+
#endif
1116
using log4net;
1217
using GeneXus.Application;
1318
using GeneXus.Data.NTier;
@@ -46,7 +51,13 @@ public override void cleanup()
4651
context.DeleteReferer();
4752
}
4853
}
49-
54+
protected override void SetCompression(HttpContext httpContext)
55+
{
56+
if (!ChunkedStreaming())
57+
{
58+
base.SetCompression(httpContext);
59+
}
60+
}
5061
public void setContextReportHandler()
5162
{
5263

0 commit comments

Comments
 (0)