Skip to content

Commit 867d148

Browse files
ggallottiGonzalo Gallotti Vazquez
authored andcommitted
Support for disabling Buffered output response for realtime data streaming (SSE) (#779)
* Add support for disabled buffered output * Remove unused code * Always flush stream in GZIP responses whn flush() is invocated * Fix compilation error
1 parent 828ac94 commit 867d148

File tree

6 files changed

+72
-55
lines changed

6 files changed

+72
-55
lines changed

gxweb/src/main/java/com/genexus/webpanels/WebWrapper.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@ public void setSource(GXWebPanel panel)
4040
((HttpContext) context.getHttpContext()).setContext(context);
4141
panel.httpContext = (HttpAjaxContext)context.getHttpContext();
4242
panel.httpContext.setCompression(false);
43-
panel.httpContext.setBuffered(false);
43+
panel.httpContext.setResponseBufferMode(HttpContext.ResponseBufferMode.SERVER_DEFAULT);
4444
panel.httpContext.useUtf8 = true;
4545
panel.httpContext.setOutputStream(new java.io.ByteArrayOutputStream());
4646
}

java/src/main/java/com/genexus/GXWebReport.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ protected void initState(ModelContext context, UserInformation ui)
3333
{
3434
super.initState(context, ui);
3535

36-
httpContext.setBuffered(true);
36+
httpContext.setResponseBufferMode(HttpContext.ResponseBufferMode.ENABLED);
3737
httpContext.setBinary(true);
3838
String implementation = com.genexus.Application.getClientContext().getClientPreferences().getPDF_RPT_LIBRARY();
3939
if (implementation.equals("ITEXT"))

java/src/main/java/com/genexus/internet/HttpContext.java

Lines changed: 37 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,22 @@
11
package com.genexus.internet;
22

3-
import java.io.*;
4-
import java.util.Date;
5-
import java.util.HashMap;
6-
import java.util.Hashtable;
7-
3+
import com.genexus.*;
4+
import com.genexus.servlet.http.ICookie;
5+
import com.genexus.servlet.http.IHttpServletRequest;
6+
import com.genexus.servlet.http.IHttpServletResponse;
7+
import com.genexus.webpanels.WebSession;
88
import json.org.json.IJsonFormattable;
99
import json.org.json.JSONArray;
1010
import json.org.json.JSONException;
1111
import json.org.json.JSONObject;
1212
import org.apache.logging.log4j.Logger;
1313

14-
import com.genexus.*;
15-
import com.genexus.servlet.http.ICookie;
16-
import com.genexus.servlet.http.IHttpServletRequest;
17-
import com.genexus.servlet.http.IHttpServletResponse;
18-
import com.genexus.webpanels.WebSession;
14+
import java.io.IOException;
15+
import java.io.OutputStream;
16+
import java.io.PrintWriter;
17+
import java.util.Date;
18+
import java.util.HashMap;
19+
import java.util.Hashtable;
1920

2021
public abstract class HttpContext implements IHttpContext
2122
{
@@ -89,11 +90,6 @@ public void disableSpaRequest()
8990
{
9091
ignoreSpa = true;
9192
}
92-
93-
public void setChunked()
94-
{
95-
isChunked = true;
96-
}
9793

9894
public boolean isSoapRequest()
9995
{
@@ -114,7 +110,7 @@ public boolean isSoapRequest()
114110
public com.genexus.xml.XMLWriter GX_xmlwrt = new com.genexus.xml.XMLWriter();
115111

116112
protected com.genexus.util.FastByteArrayOutputStream buffer;
117-
protected boolean buffered;
113+
protected ResponseBufferMode bufferMode = ResponseBufferMode.SERVER_DEFAULT;
118114
protected boolean compressed;
119115
protected boolean doNotCompress;
120116
protected ModelContext context;
@@ -131,7 +127,6 @@ public boolean isSoapRequest()
131127
protected boolean wrapped = false;
132128
protected int drawGridsAtServer = -1;
133129
private boolean ignoreSpa = false;
134-
private boolean isChunked = false;
135130

136131
private static HashMap<String, Messages> cachedMessages = new HashMap<String, Messages>();
137132
protected String currentLanguage = null;
@@ -180,7 +175,7 @@ protected void copyCommon(HttpContext ctx)
180175
ctx.GX_msglist = GX_msglist;
181176
ctx.GX_xmlwrt = GX_xmlwrt;
182177
ctx.buffer = buffer;
183-
ctx.buffered = buffered;
178+
ctx.bufferMode = bufferMode;
184179
ctx.compressed = compressed;
185180
ctx.out = out;
186181
ctx.writer = writer;
@@ -567,11 +562,22 @@ public boolean mustUseWriter()
567562
}
568563

569564

570-
public void writeBytes(byte[] bytes) throws IOException
571-
{
572-
out.write(bytes);
573-
if (isChunked || getHttpResponse().getHeader("Transfer-Encoding").equalsIgnoreCase("chunked"))
574-
out.flush();
565+
public void writeBytes(byte[] bytes) throws IOException {
566+
out.write(bytes);
567+
568+
if (bufferMode == ResponseBufferMode.DISABLED) {
569+
tryFlushStream();
570+
}
571+
}
572+
573+
private void tryFlushStream() {
574+
try {
575+
out.flush();
576+
}
577+
catch (IOException e)
578+
{
579+
logger.debug("Failed to flush Http stream", e);
580+
}
575581
}
576582

577583
public OutputStream getOutputStream()
@@ -605,9 +611,9 @@ public void closeOutputStream()
605611
}
606612
}
607613

608-
public void setBuffered(boolean buffered)
614+
public void setResponseBufferMode(ResponseBufferMode bufferMode)
609615
{
610-
this.buffered = buffered;
616+
this.bufferMode = bufferMode;
611617
}
612618

613619
public void setCompression(boolean compressed)
@@ -962,4 +968,10 @@ public void readJsonSdtValue(String jsonStr, Object SdtObj)
962968
}
963969
catch(JSONException exc) {}
964970
}
971+
972+
public enum ResponseBufferMode {
973+
DISABLED, // The response buffer is disabled and the server does not use any buffering.
974+
SERVER_DEFAULT, // The server uses its default buffering behavior, which includes a buffer size. When the buffer is full, it flushes the response.
975+
ENABLED // Not recommended: The response buffer is enabled and actively used by the server with a Custom GeneXus implementation.
976+
}
965977
}

java/src/main/java/com/genexus/internet/HttpResponse.java

Lines changed: 25 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,18 @@
11

22
package com.genexus.internet;
33

4-
import java.io.*;
5-
import java.util.Hashtable;
6-
7-
import com.genexus.CommonUtil;
8-
import com.genexus.ModelContext;
94
import com.genexus.IHttpContext;
5+
import com.genexus.ModelContext;
106
import com.genexus.PrivateUtilities;
117
import com.genexus.com.IHttpResponse;
128
import com.genexus.webpanels.FileItemCollection;
139
import com.genexus.webpanels.HttpContextWeb;
14-
import com.genexus.webpanels.HttpUtils;
1510
import com.genexus.webpanels.WebUtils;
16-
1711
import org.apache.logging.log4j.Logger;
1812

13+
import java.io.*;
14+
import java.util.Hashtable;
15+
1916
/**
2017
* Esta clase esta disponible en los webprocs para grabar informacion en el response
2118
*/
@@ -43,23 +40,32 @@ public FileItemCollection getPostedparts()
4340
return httpContext.getPostedparts();
4441
}
4542

46-
public void addHeader(String name, String value)
47-
{
48-
if(name.equalsIgnoreCase("Content-Disposition"))
49-
{
50-
value = WebUtils.getEncodedContentDisposition(value, httpContext.getBrowserType());
43+
public void addHeader(String name, String value) {
44+
final String normalizedName = name.trim().toLowerCase();
45+
46+
if (normalizedName.equals("content-disposition")) {
47+
value = WebUtils.getEncodedContentDisposition(value, httpContext.getBrowserType());
5148
}
5249

5350
httpContext.setHeader(name, value);
5451
headers.put(name.toUpperCase(), value);
5552

56-
if (name.equalsIgnoreCase("Content-type"))
57-
{
58-
httpContext.setContentType(value);
59-
}
60-
else if (name.equalsIgnoreCase("Content-length"))
61-
{
62-
httpContext.getResponse().setContentLength((int) CommonUtil.val(value));
53+
switch (normalizedName) {
54+
case "content-type":
55+
httpContext.setContentType(value);
56+
57+
if (value.equalsIgnoreCase("text/event-stream")) {
58+
httpContext.setResponseBufferMode(HttpContext.ResponseBufferMode.DISABLED);
59+
}
60+
break;
61+
case "content-length":
62+
try {
63+
int length = Integer.parseInt(value);
64+
httpContext.getResponse().setContentLength(length);
65+
} catch (NumberFormatException ex) {
66+
log.warn("Content-Length header could not be set to HttpResponse", ex);
67+
}
68+
break;
6369
}
6470
}
6571

java/src/main/java/com/genexus/webpanels/GXWebProcedure.java

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -69,16 +69,15 @@ protected void initState(ModelContext context, UserInformation ui)
6969
super.initState(context, ui);
7070

7171
if(httpContext.getHttpSecure() == 0)httpContext.setHeader("pragma", "no-cache");
72-
if (isChunked()) {
73-
httpContext.setChunked();
74-
httpContext.setCompression(false);
75-
}
7672

73+
if (!isBufferedResponse()) {
74+
httpContext.setResponseBufferMode(HttpContext.ResponseBufferMode.DISABLED);
75+
}
7776
initialize();
7877
}
7978

80-
protected boolean isChunked() {
81-
return false;
79+
protected boolean isBufferedResponse() {
80+
return true;
8281
}
8382

8483
protected void preExecute()

java/src/main/java/com/genexus/webpanels/HttpContextWeb.java

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1375,7 +1375,7 @@ public void setStream() {
13751375
if (mustUseWriter()) {
13761376
setWriter(getResponse().getWriter());
13771377
} else {
1378-
if (buffered) {
1378+
if (bufferMode == ResponseBufferMode.ENABLED) {
13791379
buffer = new com.genexus.util.FastByteArrayOutputStream();
13801380
setOutputStream(buffer);
13811381
} else {
@@ -1386,7 +1386,7 @@ public void setStream() {
13861386
String accepts = getHeader("Accept-Encoding");
13871387
if (accepts != null && accepts.indexOf("gzip") >= 0) {
13881388
setHeader("Content-Encoding", "gzip");
1389-
setOutputStream(new GZIPOutputStream(getOutputStream()));
1389+
setOutputStream(new GZIPOutputStream(getOutputStream(), true));
13901390
}
13911391
}
13921392
}
@@ -1399,7 +1399,7 @@ public void flushStream() {
13991399
proxyCookieValues();
14001400

14011401
try {
1402-
if (buffered) {
1402+
if (bufferMode == ResponseBufferMode.ENABLED) {
14031403
// Esto en realidad cierra el ZipOutputStream, o el ByteOutputStream, no cierra
14041404
// el del
14051405
// servlet... Es necesario hacerlo, dado que sino el GZip no hace el flush de

0 commit comments

Comments
 (0)