From e7e1bb533374b204a7a9623cc10b04037369f3d9 Mon Sep 17 00:00:00 2001 From: cmurialdo Date: Thu, 1 Jun 2023 15:53:44 -0300 Subject: [PATCH 01/14] Add support for chunked httpclient and chuncked httpresponse. --- .../GxClasses/Core/Web/GxHttpServer.cs | 29 ++++++-- .../GxClasses/Domain/GxHttpClient.cs | 66 +++++++++++++++---- .../GxClasses/Helpers/HttpHelper.cs | 7 ++ .../GxClasses/Middleware/GXHttp.cs | 6 +- .../GxClasses/Model/GXWebProcedure.cs | 24 ++++--- 5 files changed, 98 insertions(+), 34 deletions(-) diff --git a/dotnet/src/dotnetframework/GxClasses/Core/Web/GxHttpServer.cs b/dotnet/src/dotnetframework/GxClasses/Core/Web/GxHttpServer.cs index 8d767c5c5..b0b96dd00 100644 --- a/dotnet/src/dotnetframework/GxClasses/Core/Web/GxHttpServer.cs +++ b/dotnet/src/dotnetframework/GxClasses/Core/Web/GxHttpServer.cs @@ -96,7 +96,9 @@ public class GxHttpResponse { HttpResponse _httpRes; IGxContext _context; - +#if !NETCORE + bool _chunkedResponse; +#endif public GxHttpResponse(IGxContext context) { _context = context; @@ -137,8 +139,14 @@ public void AddString( string s) { if (Response != null) { +#if !NETCORE + if (_chunkedResponse) + { + Response.Buffer = false; + } +#endif Response.Write(s); - } + } } public void AddFile( string fileName) @@ -150,14 +158,23 @@ public void AddFile( string fileName) } public void AppendHeader( string name, string value) { - if(string.Compare(name, "Content-Disposition", true) == 0) + bool transferEncodingHeader = (name.Equals(HttpHeader.TRANSFER_ENCODING, StringComparison.OrdinalIgnoreCase) && value.Equals(HttpHeaderValue.TRANSFER_ENCODING_CHUNKED, StringComparison.OrdinalIgnoreCase)); +#if !NETCORE + if (transferEncodingHeader) + _chunkedResponse = true; +#endif + + if (string.Compare(name, "Content-Disposition", true) == 0) { value = GXUtil.EncodeContentDispositionHeader(value, _context.GetBrowserType()); } - if (_context!=null) - _context.SetHeader(name, value); + if (!transferEncodingHeader) + { + if (_context != null) + _context.SetHeader(name, value); + } } - + } public class GxSoapRequest : GxHttpRequest diff --git a/dotnet/src/dotnetframework/GxClasses/Domain/GxHttpClient.cs b/dotnet/src/dotnetframework/GxClasses/Domain/GxHttpClient.cs index debd2d04b..f395baecc 100644 --- a/dotnet/src/dotnetframework/GxClasses/Domain/GxHttpClient.cs +++ b/dotnet/src/dotnetframework/GxClasses/Domain/GxHttpClient.cs @@ -66,6 +66,7 @@ public class GxHttpClient : IGxHttpClient, IDisposable const int StreamWriterDefaultBufferSize = 1024; Stream _sendStream; byte[] _receiveData; + StreamReader _receiveStream; int _timeout = 30000; short _statusCode = 0; string _proxyHost; @@ -414,6 +415,9 @@ public void AddHeader(string name, string value) GXLogging.Error(log, String.Format("Error parsing charset ", value, ex)); } } + if (name.Equals( HttpHeader.ACCEPT, StringComparison.OrdinalIgnoreCase) && value.Equals(HttpHeaderValue.ACCEPT_SERVER_SENT_EVENT, StringComparison.OrdinalIgnoreCase)) + _chunkedResponse = true; + _headers.Set(name, value); } public void ClearVariables() @@ -675,6 +679,7 @@ HttpResponseMessage ExecuteRequest(string method, string requestUrl, CookieConta handler.ReceiveDataTimeout = milliseconds; handler.ReceiveHeadersTimeout = milliseconds; #endif + using (client = new HttpClient(handler)) { client.Timeout = milliseconds; @@ -697,7 +702,10 @@ HttpResponseMessage ExecuteRequest(string method, string requestUrl, CookieConta reqStream.Seek(0, SeekOrigin.Begin); request.Content = new ByteArrayContent(reqStream.ToArray()); setHeaders(request, handler.CookieContainer); - response = client.SendAsync(request).GetAwaiter().GetResult(); + if (_chunkedResponse) + response = client.SendAsync(request, HttpCompletionOption.ResponseHeadersRead).GetAwaiter().GetResult(); + else + response = client.SendAsync(request).GetAwaiter().GetResult(); } } return response; @@ -730,18 +738,27 @@ void ReadReponseContent(HttpResponseMessage response) charset = String.Empty; } } - - using (MemoryStream ms = new MemoryStream()) + if (_chunkedResponse) { - stream.CopyTo(ms); - _receiveData = ms.ToArray(); + if (_encoding == null) + _encoding = Encoding.UTF8; + + _receiveStream = new StreamReader(stream, _encoding); } - int bytesRead = _receiveData.Length; - GXLogging.Debug(log, "BytesRead " + _receiveData.Length); - if (bytesRead > 0 && !encodingFound) + else { - _encoding = DetectEncoding(charset, out encodingFound, _receiveData, bytesRead); - } + using (MemoryStream ms = new MemoryStream()) + { + stream.CopyTo(ms); + _receiveData = ms.ToArray(); + } + int bytesRead = _receiveData.Length; + GXLogging.Debug(log, "BytesRead " + _receiveData.Length); + if (bytesRead > 0 && !encodingFound) + { + _encoding = DetectEncoding(charset, out encodingFound, _receiveData, bytesRead); + } + } } catch (IOException ioEx) { @@ -787,7 +804,7 @@ public void Execute(string method, string name) public void HttpClientExecute(string method, string name) { - HttpResponseMessage response = null; + response = null; Byte[] Buffer = new Byte[1024]; _errCode = 0; _errDescription = string.Empty; @@ -868,11 +885,15 @@ public void HttpClientExecute(string method, string name) _errDescription = "The remote server returned an error: (" + _statusCode + ") " + _statusDescription + "."; } ClearSendStream(); - GXLogging.DebugSanitized(log, "_responseString " + ToString()); + if (!_chunkedResponse) + { + GXLogging.DebugSanitized(log, "_responseString " + ToString()); + } } NameValueCollection _respHeaders; private bool disposedValue; - + private bool _chunkedResponse; + HttpResponseMessage response; void LoadResponseHeaders(HttpResponseMessage resp) { _respHeaders = new NameValueCollection(); @@ -1363,6 +1384,25 @@ private Encoding ExtractEncodingFromCharset(string responseText, string regExpP, public override string ToString() { + if (_chunkedResponse && _receiveStream != null) + { + if (!_receiveStream.EndOfStream) + { + string line= _receiveStream.ReadLine(); + if (line != null) + return line; + else + return "EOF"; + } + else + { + _receiveStream.Dispose(); + _receiveStream = null; + response.Dispose(); + response = null; + return "EOF"; + } + } if (_encoding == null) _encoding = Encoding.UTF8; if (_receiveData == null) diff --git a/dotnet/src/dotnetframework/GxClasses/Helpers/HttpHelper.cs b/dotnet/src/dotnetframework/GxClasses/Helpers/HttpHelper.cs index e1453811b..8cebcb0b6 100644 --- a/dotnet/src/dotnetframework/GxClasses/Helpers/HttpHelper.cs +++ b/dotnet/src/dotnetframework/GxClasses/Helpers/HttpHelper.cs @@ -48,6 +48,13 @@ public class HttpHeader public static string LAST_MODIFIED = "Last-Modified"; public static string EXPIRES = "Expires"; public static string XGXFILENAME = "x-gx-filename"; + internal static string ACCEPT = "Accept"; + internal static string TRANSFER_ENCODING = "Transfer-Encoding"; + } + internal class HttpHeaderValue + { + internal static string ACCEPT_SERVER_SENT_EVENT = "text/event-stream"; + internal static string TRANSFER_ENCODING_CHUNKED = "chunked"; } [DataContract()] public class HttpJsonError diff --git a/dotnet/src/dotnetframework/GxClasses/Middleware/GXHttp.cs b/dotnet/src/dotnetframework/GxClasses/Middleware/GXHttp.cs index d44745f1e..ff7e8647c 100644 --- a/dotnet/src/dotnetframework/GxClasses/Middleware/GXHttp.cs +++ b/dotnet/src/dotnetframework/GxClasses/Middleware/GXHttp.cs @@ -1877,10 +1877,9 @@ public void ProcessRequest(HttpContext httpContext) InitPrivates(); try { -#if NETCORE SendHeaders(); string clientid = context.ClientID; //Send clientid cookie (before response HasStarted) if necessary, since UseResponseBuffering is not in .netcore3.0 -#endif + bool validSession = ValidWebSession(); if (validSession && IntegratedSecurityEnabled) validSession = ValidSession(); @@ -1915,9 +1914,6 @@ public void ProcessRequest(HttpContext httpContext) context.DispatchAjaxCommands(); } SetCompression(httpContext); -#if !NETCORE - SendHeaders(); -#endif context.ResponseCommited = true; } catch (Exception e) diff --git a/dotnet/src/dotnetframework/GxClasses/Model/GXWebProcedure.cs b/dotnet/src/dotnetframework/GxClasses/Model/GXWebProcedure.cs index 3cfff2994..1158593e8 100644 --- a/dotnet/src/dotnetframework/GxClasses/Model/GXWebProcedure.cs +++ b/dotnet/src/dotnetframework/GxClasses/Model/GXWebProcedure.cs @@ -18,6 +18,7 @@ public class GXWebProcedure : GXHttpHandler protected IReportHandler oldReportHandler; string outputFileName; string outputType; + bool fileContentInline; protected int lineHeight; protected int Gx_line; @@ -115,18 +116,20 @@ protected override void sendCacheHeaders() private void setOuputFileName() { - string fileName = GetType().Name; - string fileType = "pdf"; - if (!string.IsNullOrEmpty(outputFileName)) + if (fileContentInline) { - fileName = outputFileName; - } - if (!string.IsNullOrEmpty(outputType)) - { - fileType = outputType.ToLower(); + string fileName = GetType().Name; + string fileType = "pdf"; + if (!string.IsNullOrEmpty(outputFileName)) + { + fileName = outputFileName; + } + if (!string.IsNullOrEmpty(outputType)) + { + fileType = outputType.ToLower(); + } + context.HttpContext.Response.AddHeader(HttpHeader.CONTENT_DISPOSITION, $"inline; filename={fileName}.{fileType}"); } - - context.HttpContext.Response.AddHeader(HttpHeader.CONTENT_DISPOSITION, $"inline; filename={fileName}.{fileType}"); } public virtual int getOutputType() @@ -138,6 +141,7 @@ protected bool initPrinter(String output, int gxXPage, int gxYPage, String iniFi string idiom; if (!Config.GetValueOf("LANGUAGE", out idiom)) idiom = "eng"; + fileContentInline = true; #if NETCORE setOuputFileName(); #endif From 780baa1da5bf3d125e10b371a7a2e6c0dfa37702 Mon Sep 17 00:00:00 2001 From: cmurialdo Date: Tue, 6 Jun 2023 22:41:05 -0300 Subject: [PATCH 02/14] Do not append headers for X-GXOBJECT and X-UA-Compatible. X-GXOBJECT is set twice when called from SendHeaders and ValidateSpaRequest from webExecute. --- dotnet/src/dotnetframework/GxClasses/Middleware/GXHttp.cs | 8 -------- 1 file changed, 8 deletions(-) diff --git a/dotnet/src/dotnetframework/GxClasses/Middleware/GXHttp.cs b/dotnet/src/dotnetframework/GxClasses/Middleware/GXHttp.cs index ff7e8647c..410960122 100644 --- a/dotnet/src/dotnetframework/GxClasses/Middleware/GXHttp.cs +++ b/dotnet/src/dotnetframework/GxClasses/Middleware/GXHttp.cs @@ -2161,11 +2161,7 @@ public virtual void sendAdditionalHeaders() return; string safeIECompMode = IE_COMP_Edge.Equals(IE_COMP_EmulateIE7) ? IE_COMP_Edge : IE_COMP_Edge; -#if NETCORE localHttpContext.Response.Headers["X-UA-Compatible"] = "IE=" + safeIECompMode; -#else - localHttpContext.Response.AddHeader("X-UA-Compatible", "IE=" + safeIECompMode); -#endif } } @@ -2173,11 +2169,7 @@ public virtual void sendAdditionalHeaders() protected virtual void sendSpaHeaders() { -#if NETCORE localHttpContext.Response.Headers[GX_SPA_GXOBJECT_RESPONSE_HEADER] = GetPgmname().ToLower(); -#else - localHttpContext.Response.AddHeader(GX_SPA_GXOBJECT_RESPONSE_HEADER, GetPgmname().ToLower()); -#endif } private void webExecuteWorker(object target) From dc26fbd37d7f88aabeef3cd982089c7a937a2b72 Mon Sep 17 00:00:00 2001 From: cmurialdo Date: Tue, 6 Jun 2023 22:46:59 -0300 Subject: [PATCH 03/14] Condition content-disposition=inline; filename to reports only. --- .../GxClasses/Model/GXWebProcedure.cs | 23 ++++++++----------- 1 file changed, 9 insertions(+), 14 deletions(-) diff --git a/dotnet/src/dotnetframework/GxClasses/Model/GXWebProcedure.cs b/dotnet/src/dotnetframework/GxClasses/Model/GXWebProcedure.cs index 1158593e8..ad7871941 100644 --- a/dotnet/src/dotnetframework/GxClasses/Model/GXWebProcedure.cs +++ b/dotnet/src/dotnetframework/GxClasses/Model/GXWebProcedure.cs @@ -18,7 +18,6 @@ public class GXWebProcedure : GXHttpHandler protected IReportHandler oldReportHandler; string outputFileName; string outputType; - bool fileContentInline; protected int lineHeight; protected int Gx_line; @@ -116,20 +115,17 @@ protected override void sendCacheHeaders() private void setOuputFileName() { - if (fileContentInline) + string fileName = GetType().Name; + string fileType = "pdf"; + if (!string.IsNullOrEmpty(outputFileName)) { - string fileName = GetType().Name; - string fileType = "pdf"; - if (!string.IsNullOrEmpty(outputFileName)) - { - fileName = outputFileName; - } - if (!string.IsNullOrEmpty(outputType)) - { - fileType = outputType.ToLower(); - } - context.HttpContext.Response.AddHeader(HttpHeader.CONTENT_DISPOSITION, $"inline; filename={fileName}.{fileType}"); + fileName = outputFileName; + } + if (!string.IsNullOrEmpty(outputType)) + { + fileType = outputType.ToLower(); } + context.HttpContext.Response.AddHeader(HttpHeader.CONTENT_DISPOSITION, $"inline; filename={fileName}.{fileType}"); } public virtual int getOutputType() @@ -141,7 +137,6 @@ protected bool initPrinter(String output, int gxXPage, int gxYPage, String iniFi string idiom; if (!Config.GetValueOf("LANGUAGE", out idiom)) idiom = "eng"; - fileContentInline = true; #if NETCORE setOuputFileName(); #endif From 1ce8bd7cbdc44e4bfd16445922be6348d8079b06 Mon Sep 17 00:00:00 2001 From: cmurialdo Date: Thu, 15 Jun 2023 18:48:25 -0300 Subject: [PATCH 04/14] Define new property EOF and method ReadChunk for HttpClient datatype. --- .../GxClasses/Domain/GxHttpClient.cs | 68 +++++++++++++------ 1 file changed, 48 insertions(+), 20 deletions(-) diff --git a/dotnet/src/dotnetframework/GxClasses/Domain/GxHttpClient.cs b/dotnet/src/dotnetframework/GxClasses/Domain/GxHttpClient.cs index f395baecc..e24736698 100644 --- a/dotnet/src/dotnetframework/GxClasses/Domain/GxHttpClient.cs +++ b/dotnet/src/dotnetframework/GxClasses/Domain/GxHttpClient.cs @@ -1381,33 +1381,61 @@ private Encoding ExtractEncodingFromCharset(string responseText, string regExpP, } return enc; } + public bool Eof + { + get + { + if (_chunkedResponse && _receiveStream != null) + { + return _receiveStream.EndOfStream; + } + return true; + } + } - public override string ToString() + public string ReadChunk() { - if (_chunkedResponse && _receiveStream != null) - { - if (!_receiveStream.EndOfStream) + if (_chunkedResponse) + { + if (_receiveStream != null) { - string line= _receiveStream.ReadLine(); - if (line != null) - return line; + if (!_receiveStream.EndOfStream) + { + string line = _receiveStream.ReadLine(); + if (line != null) + return line; + } else - return "EOF"; + { + _receiveStream.Dispose(); + _receiveStream = null; + response.Dispose(); + response = null; + } } - else - { - _receiveStream.Dispose(); - _receiveStream = null; - response.Dispose(); - response = null; - return "EOF"; + return string.Empty; + } + else + return ToString(); + } + public override string ToString() + { + if (_chunkedResponse) + { + StringBuilder sb = new StringBuilder(); + while (!Eof){ + sb.Append(ReadChunk()); } + return sb.ToString(); + } + else + { + if (_encoding == null) + _encoding = Encoding.UTF8; + if (_receiveData == null) + return string.Empty; + return _encoding.GetString(_receiveData); } - if (_encoding == null) - _encoding = Encoding.UTF8; - if (_receiveData == null) - return string.Empty; - return _encoding.GetString(_receiveData); } public void ToFile(string fileName) { From 8a9872df7888cca8781cfd26ccd924a7b15e07ff Mon Sep 17 00:00:00 2001 From: cmurialdo Date: Fri, 16 Jun 2023 12:12:43 -0300 Subject: [PATCH 05/14] 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. --- .../dotnetframework/GxClasses/Middleware/GXHttp.cs | 13 ++++--------- 1 file changed, 4 insertions(+), 9 deletions(-) diff --git a/dotnet/src/dotnetframework/GxClasses/Middleware/GXHttp.cs b/dotnet/src/dotnetframework/GxClasses/Middleware/GXHttp.cs index 410960122..821a231c5 100644 --- a/dotnet/src/dotnetframework/GxClasses/Middleware/GXHttp.cs +++ b/dotnet/src/dotnetframework/GxClasses/Middleware/GXHttp.cs @@ -3004,15 +3004,6 @@ public abstract class GXDataArea : GXHttpHandler protected GXMasterPage MasterPageObj { get; set; } - protected override void sendSpaHeaders() - { - base.sendSpaHeaders(); - if (MasterPageObj != null) - { - localHttpContext.Response.AddHeader(GX_SPA_MASTERPAGE_HEADER, MasterPageObj.GetPgmname()); - } - } - protected override void ValidateSpaRequest() { string sourceMasterPage = localHttpContext.Request.Headers[GX_SPA_MASTERPAGE_HEADER]; @@ -3021,6 +3012,10 @@ protected override void ValidateSpaRequest() context.DisableSpaRequest(); sendSpaHeaders(); } + if (MasterPageObj != null) + { + localHttpContext.Response.Headers[GX_SPA_MASTERPAGE_HEADER] = MasterPageObj.GetPgmname(); + } } } public class GXDataAreaControl From ab2cc191bde5222899004c05596919b4c677a7a7 Mon Sep 17 00:00:00 2001 From: cmurialdo Date: Fri, 16 Jun 2023 15:29:35 -0300 Subject: [PATCH 06/14] Define method ChunkedStreaming to set chunkedResponse at procedure level --- .../GxClasses/Core/Web/GxHttpServer.cs | 30 ++----------------- .../GxClasses/Model/GXWebProcedure.cs | 23 +++++++++++++- 2 files changed, 25 insertions(+), 28 deletions(-) diff --git a/dotnet/src/dotnetframework/GxClasses/Core/Web/GxHttpServer.cs b/dotnet/src/dotnetframework/GxClasses/Core/Web/GxHttpServer.cs index b0b96dd00..1e6e32449 100644 --- a/dotnet/src/dotnetframework/GxClasses/Core/Web/GxHttpServer.cs +++ b/dotnet/src/dotnetframework/GxClasses/Core/Web/GxHttpServer.cs @@ -12,12 +12,6 @@ namespace GeneXus.Http.Server using System.Linq; using Microsoft.AspNetCore.Http.Features; using System.Text; - using System.Threading.Tasks; - using Microsoft.AspNetCore.Mvc.Formatters; - using System.Net.Http; - using Stubble.Core.Contexts; - using System.Net.Mime; - #endif public class GxHttpCookie @@ -96,9 +90,6 @@ public class GxHttpResponse { HttpResponse _httpRes; IGxContext _context; -#if !NETCORE - bool _chunkedResponse; -#endif public GxHttpResponse(IGxContext context) { _context = context; @@ -139,12 +130,6 @@ public void AddString( string s) { if (Response != null) { -#if !NETCORE - if (_chunkedResponse) - { - Response.Buffer = false; - } -#endif Response.Write(s); } } @@ -156,23 +141,14 @@ public void AddFile( string fileName) Response.WriteFile(fileName.Trim()); } } - public void AppendHeader( string name, string value) + public void AppendHeader(string name, string value) { - bool transferEncodingHeader = (name.Equals(HttpHeader.TRANSFER_ENCODING, StringComparison.OrdinalIgnoreCase) && value.Equals(HttpHeaderValue.TRANSFER_ENCODING_CHUNKED, StringComparison.OrdinalIgnoreCase)); -#if !NETCORE - if (transferEncodingHeader) - _chunkedResponse = true; -#endif - if (string.Compare(name, "Content-Disposition", true) == 0) { value = GXUtil.EncodeContentDispositionHeader(value, _context.GetBrowserType()); } - if (!transferEncodingHeader) - { - if (_context != null) - _context.SetHeader(name, value); - } + if (_context != null) + _context.SetHeader(name, value); } } diff --git a/dotnet/src/dotnetframework/GxClasses/Model/GXWebProcedure.cs b/dotnet/src/dotnetframework/GxClasses/Model/GXWebProcedure.cs index ad7871941..4a174fb6f 100644 --- a/dotnet/src/dotnetframework/GxClasses/Model/GXWebProcedure.cs +++ b/dotnet/src/dotnetframework/GxClasses/Model/GXWebProcedure.cs @@ -8,6 +8,11 @@ namespace GeneXus.Procedure using System.Threading; using GeneXus.Mime; using GeneXus.Utils; +#if NETCORE + using Microsoft.AspNetCore.Http; +#else + using System.Web; +#endif public class GXWebProcedure : GXHttpHandler { @@ -35,6 +40,7 @@ public override void webExecute() { } public override void initialize() { } protected override void createObjects() { } public override void skipLines(long nToSkip) { } + protected virtual bool ChunkedStreaming() { return false; } public override void cleanup() { @@ -43,7 +49,22 @@ public override void cleanup() context.DeleteReferer(); } } - + public GXWebProcedure() + { +#if !NETCORE + if (ChunkedStreaming()) + { + context.HttpContext.Response.Buffer = false; + } +#endif + } + protected override void SetCompression(HttpContext httpContext) + { + if (!ChunkedStreaming()) + { + base.SetCompression(httpContext); + } + } public void setContextReportHandler() { From dc171a5f093f6cf4741db9f460b0c324b0163df8 Mon Sep 17 00:00:00 2001 From: cmurialdo Date: Fri, 16 Jun 2023 16:58:18 -0300 Subject: [PATCH 07/14] Setup Streaming after httpcontext is initialized --- .../dotnetframework/GxClasses/Middleware/GXHttp.cs | 14 ++++++++++++-- .../GxClasses/Model/GXWebProcedure.cs | 10 ---------- 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/dotnet/src/dotnetframework/GxClasses/Middleware/GXHttp.cs b/dotnet/src/dotnetframework/GxClasses/Middleware/GXHttp.cs index 821a231c5..32696e1b0 100644 --- a/dotnet/src/dotnetframework/GxClasses/Middleware/GXHttp.cs +++ b/dotnet/src/dotnetframework/GxClasses/Middleware/GXHttp.cs @@ -1851,8 +1851,6 @@ public bool IsMain get { return _isMain; } } #endif - - public void ProcessRequest(HttpContext httpContext) { localHttpContext = httpContext; @@ -1877,6 +1875,7 @@ public void ProcessRequest(HttpContext httpContext) InitPrivates(); try { + SetStreaming(); SendHeaders(); string clientid = context.ClientID; //Send clientid cookie (before response HasStarted) if necessary, since UseResponseBuffering is not in .netcore3.0 @@ -1928,6 +1927,17 @@ public void ProcessRequest(HttpContext httpContext) throw new Exception("GXApplication exception", e); } } + protected virtual bool ChunkedStreaming() { return false; } + + private void SetStreaming() + { +#if !NETCORE + if (ChunkedStreaming()) + { + context.HttpContext.Response.Buffer = false; + } +#endif + } internal string DumpHeaders(HttpContext httpContext) { StringBuilder str = new StringBuilder(); diff --git a/dotnet/src/dotnetframework/GxClasses/Model/GXWebProcedure.cs b/dotnet/src/dotnetframework/GxClasses/Model/GXWebProcedure.cs index 4a174fb6f..e82d8eb18 100644 --- a/dotnet/src/dotnetframework/GxClasses/Model/GXWebProcedure.cs +++ b/dotnet/src/dotnetframework/GxClasses/Model/GXWebProcedure.cs @@ -40,7 +40,6 @@ public override void webExecute() { } public override void initialize() { } protected override void createObjects() { } public override void skipLines(long nToSkip) { } - protected virtual bool ChunkedStreaming() { return false; } public override void cleanup() { @@ -49,15 +48,6 @@ public override void cleanup() context.DeleteReferer(); } } - public GXWebProcedure() - { -#if !NETCORE - if (ChunkedStreaming()) - { - context.HttpContext.Response.Buffer = false; - } -#endif - } protected override void SetCompression(HttpContext httpContext) { if (!ChunkedStreaming()) From 211451d0f56701faa0f658c3d4728f404bba708e Mon Sep 17 00:00:00 2001 From: cmurialdo Date: Tue, 20 Jun 2023 11:38:19 -0300 Subject: [PATCH 08/14] Remove extra changes in spacing and indentation. --- .../GxClasses/Core/Web/GxHttpServer.cs | 13 +++++++------ .../GxClasses/Domain/GxHttpClient.cs | 1 - .../dotnetframework/GxClasses/Middleware/GXHttp.cs | 7 +++++-- .../GxClasses/Model/GXWebProcedure.cs | 1 + 4 files changed, 13 insertions(+), 9 deletions(-) diff --git a/dotnet/src/dotnetframework/GxClasses/Core/Web/GxHttpServer.cs b/dotnet/src/dotnetframework/GxClasses/Core/Web/GxHttpServer.cs index 1e6e32449..dbb4e1092 100644 --- a/dotnet/src/dotnetframework/GxClasses/Core/Web/GxHttpServer.cs +++ b/dotnet/src/dotnetframework/GxClasses/Core/Web/GxHttpServer.cs @@ -90,6 +90,7 @@ public class GxHttpResponse { HttpResponse _httpRes; IGxContext _context; + public GxHttpResponse(IGxContext context) { _context = context; @@ -131,7 +132,7 @@ public void AddString( string s) if (Response != null) { Response.Write(s); - } + } } public void AddFile( string fileName) @@ -141,16 +142,16 @@ public void AddFile( string fileName) Response.WriteFile(fileName.Trim()); } } - public void AppendHeader(string name, string value) + public void AppendHeader( string name, string value) { - if (string.Compare(name, "Content-Disposition", true) == 0) + if(string.Compare(name, "Content-Disposition", true) == 0) { value = GXUtil.EncodeContentDispositionHeader(value, _context.GetBrowserType()); } - if (_context != null) - _context.SetHeader(name, value); + if (_context!=null) + _context.SetHeader(name, value); } - + } public class GxSoapRequest : GxHttpRequest diff --git a/dotnet/src/dotnetframework/GxClasses/Domain/GxHttpClient.cs b/dotnet/src/dotnetframework/GxClasses/Domain/GxHttpClient.cs index e24736698..732c6a6cb 100644 --- a/dotnet/src/dotnetframework/GxClasses/Domain/GxHttpClient.cs +++ b/dotnet/src/dotnetframework/GxClasses/Domain/GxHttpClient.cs @@ -679,7 +679,6 @@ HttpResponseMessage ExecuteRequest(string method, string requestUrl, CookieConta handler.ReceiveDataTimeout = milliseconds; handler.ReceiveHeadersTimeout = milliseconds; #endif - using (client = new HttpClient(handler)) { client.Timeout = milliseconds; diff --git a/dotnet/src/dotnetframework/GxClasses/Middleware/GXHttp.cs b/dotnet/src/dotnetframework/GxClasses/Middleware/GXHttp.cs index 32696e1b0..9a6f7c778 100644 --- a/dotnet/src/dotnetframework/GxClasses/Middleware/GXHttp.cs +++ b/dotnet/src/dotnetframework/GxClasses/Middleware/GXHttp.cs @@ -45,6 +45,9 @@ namespace GeneXus.Http using Web.Security; using System.Web.SessionState; #endif + + + #if NETCORE public abstract class GXHttpHandler : GXBaseObject, IHttpHandler #else @@ -1545,8 +1548,6 @@ protected void SendResponseStatus(HttpStatusCode statusCode) { SendResponseStatus((int)statusCode, string.Empty); } - - #if !NETCORE protected void SendResponseStatus(int statusCode, string statusDescription) { @@ -1851,6 +1852,8 @@ public bool IsMain get { return _isMain; } } #endif + + public void ProcessRequest(HttpContext httpContext) { localHttpContext = httpContext; diff --git a/dotnet/src/dotnetframework/GxClasses/Model/GXWebProcedure.cs b/dotnet/src/dotnetframework/GxClasses/Model/GXWebProcedure.cs index e82d8eb18..d2c799c20 100644 --- a/dotnet/src/dotnetframework/GxClasses/Model/GXWebProcedure.cs +++ b/dotnet/src/dotnetframework/GxClasses/Model/GXWebProcedure.cs @@ -136,6 +136,7 @@ private void setOuputFileName() { fileType = outputType.ToLower(); } + context.HttpContext.Response.AddHeader(HttpHeader.CONTENT_DISPOSITION, $"inline; filename={fileName}.{fileType}"); } From 7fa5359a03ba15080160184ca5213ff328ab2681 Mon Sep 17 00:00:00 2001 From: cmurialdo Date: Wed, 21 Jun 2023 14:50:30 -0300 Subject: [PATCH 09/14] _chunkedResponse condition depends only on TransferEncodingChunked header in the response. --- .../GxClasses/Domain/GxHttpClient.cs | 22 ++++++++----------- 1 file changed, 9 insertions(+), 13 deletions(-) diff --git a/dotnet/src/dotnetframework/GxClasses/Domain/GxHttpClient.cs b/dotnet/src/dotnetframework/GxClasses/Domain/GxHttpClient.cs index 732c6a6cb..36dd09b91 100644 --- a/dotnet/src/dotnetframework/GxClasses/Domain/GxHttpClient.cs +++ b/dotnet/src/dotnetframework/GxClasses/Domain/GxHttpClient.cs @@ -415,9 +415,7 @@ public void AddHeader(string name, string value) GXLogging.Error(log, String.Format("Error parsing charset ", value, ex)); } } - if (name.Equals( HttpHeader.ACCEPT, StringComparison.OrdinalIgnoreCase) && value.Equals(HttpHeaderValue.ACCEPT_SERVER_SENT_EVENT, StringComparison.OrdinalIgnoreCase)) - _chunkedResponse = true; - + _headers.Set(name, value); } public void ClearVariables() @@ -701,10 +699,7 @@ HttpResponseMessage ExecuteRequest(string method, string requestUrl, CookieConta reqStream.Seek(0, SeekOrigin.Begin); request.Content = new ByteArrayContent(reqStream.ToArray()); setHeaders(request, handler.CookieContainer); - if (_chunkedResponse) - response = client.SendAsync(request, HttpCompletionOption.ResponseHeadersRead).GetAwaiter().GetResult(); - else - response = client.SendAsync(request).GetAwaiter().GetResult(); + response = client.SendAsync(request, HttpCompletionOption.ResponseHeadersRead).GetAwaiter().GetResult(); } } return response; @@ -715,6 +710,7 @@ void ReadReponseContent(HttpResponseMessage response) try { Stream stream = response.Content.ReadAsStreamAsync().GetAwaiter().GetResult(); + _chunkedResponse = response.Headers.TransferEncodingChunked.HasValue && response.Headers.TransferEncodingChunked.Value; string charset; if (response.Content.Headers.ContentType == null) charset = null; @@ -884,10 +880,6 @@ public void HttpClientExecute(string method, string name) _errDescription = "The remote server returned an error: (" + _statusCode + ") " + _statusDescription + "."; } ClearSendStream(); - if (!_chunkedResponse) - { - GXLogging.DebugSanitized(log, "_responseString " + ToString()); - } } NameValueCollection _respHeaders; private bool disposedValue; @@ -1419,13 +1411,14 @@ public string ReadChunk() } public override string ToString() { + string responseString; if (_chunkedResponse) { StringBuilder sb = new StringBuilder(); while (!Eof){ sb.Append(ReadChunk()); } - return sb.ToString(); + responseString = sb.ToString(); } else { @@ -1433,8 +1426,11 @@ public override string ToString() _encoding = Encoding.UTF8; if (_receiveData == null) return string.Empty; - return _encoding.GetString(_receiveData); + + responseString = _encoding.GetString(_receiveData); } + GXLogging.DebugSanitized(log, "_responseString " + responseString); + return responseString; } public void ToFile(string fileName) { From 5dda8b73f27032cff0bc44c382b8f77d28b386fc Mon Sep 17 00:00:00 2001 From: cmurialdo Date: Wed, 21 Jun 2023 15:09:05 -0300 Subject: [PATCH 10/14] Use BufferOutput instead of the deprecated Buffer property in HttpResponse. --- dotnet/src/dotnetframework/GxClasses/Middleware/GXHttp.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dotnet/src/dotnetframework/GxClasses/Middleware/GXHttp.cs b/dotnet/src/dotnetframework/GxClasses/Middleware/GXHttp.cs index 9a6f7c778..2f7f87fc1 100644 --- a/dotnet/src/dotnetframework/GxClasses/Middleware/GXHttp.cs +++ b/dotnet/src/dotnetframework/GxClasses/Middleware/GXHttp.cs @@ -1937,7 +1937,7 @@ private void SetStreaming() #if !NETCORE if (ChunkedStreaming()) { - context.HttpContext.Response.Buffer = false; + context.HttpContext.Response.BufferOutput = false; } #endif } From 21752baffde8ddfeaefcc20bb05da0dee621a561 Mon Sep 17 00:00:00 2001 From: cmurialdo Date: Wed, 21 Jun 2023 15:10:38 -0300 Subject: [PATCH 11/14] Remove extra blank lines. --- dotnet/src/dotnetframework/GxClasses/Middleware/GXHttp.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/dotnet/src/dotnetframework/GxClasses/Middleware/GXHttp.cs b/dotnet/src/dotnetframework/GxClasses/Middleware/GXHttp.cs index 7f00123aa..8d6a806a9 100644 --- a/dotnet/src/dotnetframework/GxClasses/Middleware/GXHttp.cs +++ b/dotnet/src/dotnetframework/GxClasses/Middleware/GXHttp.cs @@ -2041,7 +2041,6 @@ private bool ValidWebSession() #else bool isExpired = IsFullAjaxRequest(HttpContext.Current) && this.AjaxOnSessionTimeout() == "Warn" && GxWebSession.IsSessionExpired(localHttpContext); #endif - if (isExpired) { GXLogging.Info(log, "440 Session timeout. Web Session has expired and GX' OnSessionTimeout' Pty is set to 'WARN'"); From 14c0a529cb7b71993d15519d7976287afa072a35 Mon Sep 17 00:00:00 2001 From: cmurialdo Date: Mon, 26 Jun 2023 11:49:07 -0300 Subject: [PATCH 12/14] ReceiveData was null when httpclient response is chunked. Issue:103474 --- .../dotnetframework/GxClasses/Domain/GxHttpClient.cs | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/dotnet/src/dotnetframework/GxClasses/Domain/GxHttpClient.cs b/dotnet/src/dotnetframework/GxClasses/Domain/GxHttpClient.cs index 36dd09b91..67d28923e 100644 --- a/dotnet/src/dotnetframework/GxClasses/Domain/GxHttpClient.cs +++ b/dotnet/src/dotnetframework/GxClasses/Domain/GxHttpClient.cs @@ -134,6 +134,14 @@ internal byte[] ReceiveData { get { + if (_chunkedResponse && _receiveData == null && _receiveStream!=null) + { + using (MemoryStream memstream = new MemoryStream()) + { + _receiveStream.BaseStream.CopyTo(memstream); + _receiveData = memstream.ToArray(); + } + } return _receiveData; } } @@ -739,6 +747,7 @@ void ReadReponseContent(HttpResponseMessage response) _encoding = Encoding.UTF8; _receiveStream = new StreamReader(stream, _encoding); + _receiveData = null; } else { @@ -1520,6 +1529,7 @@ protected virtual void Dispose(bool disposing) { _receiveData = null; _sendStream?.Dispose(); + _receiveStream?.Dispose(); } disposedValue = true; } From b96630a33e12321e79e9fc709fa0ffe66fe060a5 Mon Sep 17 00:00:00 2001 From: cmurialdo Date: Wed, 28 Jun 2023 15:29:39 -0300 Subject: [PATCH 13/14] 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. --- dotnet/src/dotnetframework/GxClasses/Middleware/GXHttp.cs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/dotnet/src/dotnetframework/GxClasses/Middleware/GXHttp.cs b/dotnet/src/dotnetframework/GxClasses/Middleware/GXHttp.cs index 8d6a806a9..f22c2162b 100644 --- a/dotnet/src/dotnetframework/GxClasses/Middleware/GXHttp.cs +++ b/dotnet/src/dotnetframework/GxClasses/Middleware/GXHttp.cs @@ -1992,10 +1992,16 @@ private void SetStreaming() #if !NETCORE if (ChunkedStreaming()) { + BringSessionStateToLife(); context.HttpContext.Response.BufferOutput = false; } #endif } + private void BringSessionStateToLife() + { + string sessionId = context.HttpContext.Session.SessionID; + GXLogging.Debug(log, "Session is alive: " + sessionId); + } internal string DumpHeaders(HttpContext httpContext) { StringBuilder str = new StringBuilder(); From 40d2c5913e51c7b0eed3c7899b4cc7e3b3792e30 Mon Sep 17 00:00:00 2001 From: cmurialdo Date: Wed, 28 Jun 2023 16:52:32 -0300 Subject: [PATCH 14/14] Limit BringSessionStateToLife to .NET Framework. --- dotnet/src/dotnetframework/GxClasses/Middleware/GXHttp.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/dotnet/src/dotnetframework/GxClasses/Middleware/GXHttp.cs b/dotnet/src/dotnetframework/GxClasses/Middleware/GXHttp.cs index f22c2162b..8877eb7e2 100644 --- a/dotnet/src/dotnetframework/GxClasses/Middleware/GXHttp.cs +++ b/dotnet/src/dotnetframework/GxClasses/Middleware/GXHttp.cs @@ -1997,11 +1997,13 @@ private void SetStreaming() } #endif } +#if !NETCORE private void BringSessionStateToLife() { string sessionId = context.HttpContext.Session.SessionID; GXLogging.Debug(log, "Session is alive: " + sessionId); } +#endif internal string DumpHeaders(HttpContext httpContext) { StringBuilder str = new StringBuilder();