Skip to content

Commit eae2ec6

Browse files
committed
Provide a fix for tyrus client buffer overflow
1 parent 576b35c commit eae2ec6

File tree

7 files changed

+334
-32
lines changed

7 files changed

+334
-32
lines changed
Lines changed: 122 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,122 @@
1+
package com.github.kklisura.cdt.examples;
2+
3+
import com.github.kklisura.cdt.launch.ChromeLauncher;
4+
import com.github.kklisura.cdt.protocol.commands.Page;
5+
import com.github.kklisura.cdt.protocol.types.page.PrintToPDF;
6+
import com.github.kklisura.cdt.protocol.types.page.PrintToPDFTransferMode;
7+
import com.github.kklisura.cdt.services.ChromeDevToolsService;
8+
import com.github.kklisura.cdt.services.ChromeService;
9+
import com.github.kklisura.cdt.services.factory.impl.ConfigurableTyrusClientFactory;
10+
import com.github.kklisura.cdt.services.types.ChromeTab;
11+
import java.io.File;
12+
import java.io.FileOutputStream;
13+
import java.io.IOException;
14+
import java.util.Base64;
15+
16+
/**
17+
* This example demonstrates how to increase incoming buffer size in Tyrus client. Use the following
18+
* example to fix the issues with Tyrus Buffer overflow. This issue occurs when the incoming message
19+
* size is larger than the incoming buffer in Tyrus client at which the client disconnects.
20+
*
21+
* @author Kenan Klisura
22+
*/
23+
public class IncreasedIncomingBufferInTyrusExample {
24+
static {
25+
System.setProperty(
26+
"com.github.kklisura.cdt.services.config.webSocketContainerFactory",
27+
ConfigurableTyrusClientFactory.class.getName());
28+
}
29+
30+
public static void main(String[] args) {
31+
// Create chrome launcher.
32+
final ChromeLauncher launcher = new ChromeLauncher();
33+
34+
// Launch chrome either as headless (true) - PDF printing is only supported on Chrome headless
35+
// at the moment
36+
final ChromeService chromeService = launcher.launch(true);
37+
38+
// Create empty tab ie about:blank.
39+
final ChromeTab tab = chromeService.createTab();
40+
41+
// Get DevTools service to this tab
42+
final ChromeDevToolsService devToolsService = chromeService.createDevToolsService(tab);
43+
44+
// Get individual commands
45+
final Page page = devToolsService.getPage();
46+
page.enable();
47+
48+
// Navigate to github.com.
49+
page.navigate("https://github.com");
50+
51+
page.onLoadEventFired(
52+
loadEventFired -> {
53+
System.out.println("Printing to PDF...");
54+
55+
final String outputFilename = "test.pdf";
56+
57+
Boolean landscape = false;
58+
Boolean displayHeaderFooter = false;
59+
Boolean printBackground = false;
60+
Double scale = 1d;
61+
Double paperWidth = 8.27d; // A4 paper format
62+
Double paperHeight = 11.7d; // A4 paper format
63+
Double marginTop = 0d;
64+
Double marginBottom = 0d;
65+
Double marginLeft = 0d;
66+
Double marginRight = 0d;
67+
String pageRanges = "";
68+
Boolean ignoreInvalidPageRanges = false;
69+
String headerTemplate = "";
70+
String footerTemplate = "";
71+
Boolean preferCSSPageSize = false;
72+
PrintToPDFTransferMode mode = PrintToPDFTransferMode.RETURN_AS_BASE_64;
73+
74+
dump(
75+
outputFilename,
76+
devToolsService
77+
.getPage()
78+
.printToPDF(
79+
landscape,
80+
displayHeaderFooter,
81+
printBackground,
82+
scale,
83+
paperWidth,
84+
paperHeight,
85+
marginTop,
86+
marginBottom,
87+
marginLeft,
88+
marginRight,
89+
pageRanges,
90+
ignoreInvalidPageRanges,
91+
headerTemplate,
92+
footerTemplate,
93+
preferCSSPageSize,
94+
mode));
95+
96+
System.out.println("Done!");
97+
devToolsService.close();
98+
});
99+
100+
devToolsService.waitUntilClosed();
101+
}
102+
103+
private static void dump(String fileName, PrintToPDF data) {
104+
FileOutputStream fileOutputStream = null;
105+
try {
106+
File file = new File(fileName);
107+
fileOutputStream = new FileOutputStream(file);
108+
fileOutputStream.write(Base64.getDecoder().decode(data.getData()));
109+
} catch (IOException e) {
110+
e.printStackTrace();
111+
} finally {
112+
if (fileOutputStream != null) {
113+
try {
114+
fileOutputStream.flush();
115+
fileOutputStream.close();
116+
} catch (IOException e) {
117+
e.printStackTrace();
118+
}
119+
}
120+
}
121+
}
122+
}

cdt-java-client/src/main/java/com/github/kklisura/cdt/services/WebSocketService.java

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,4 +53,11 @@ public interface WebSocketService {
5353

5454
/** Closes the service. */
5555
void close();
56+
57+
/**
58+
* Checks if connection is closed.
59+
*
60+
* @return True if connection is closed.
61+
*/
62+
boolean closed();
5663
}
Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
package com.github.kklisura.cdt.services.factory.impl;
2+
3+
/*-
4+
* #%L
5+
* cdt-java-client
6+
* %%
7+
* Copyright (C) 2018 - 2019 Kenan Klisura
8+
* %%
9+
* Licensed under the Apache License, Version 2.0 (the "License");
10+
* you may not use this file except in compliance with the License.
11+
* You may obtain a copy of the License at
12+
*
13+
* http://www.apache.org/licenses/LICENSE-2.0
14+
*
15+
* Unless required by applicable law or agreed to in writing, software
16+
* distributed under the License is distributed on an "AS IS" BASIS,
17+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
18+
* See the License for the specific language governing permissions and
19+
* limitations under the License.
20+
* #L%
21+
*/
22+
23+
import com.github.kklisura.cdt.services.factory.WebSocketContainerFactory;
24+
import javax.websocket.WebSocketContainer;
25+
import org.glassfish.tyrus.client.ClientManager;
26+
import org.glassfish.tyrus.container.grizzly.client.GrizzlyClientContainer;
27+
28+
/**
29+
* Configurable tyrus client WebSocketContainer factory. This demonstrates how to increase incoming
30+
* buffer size in tyrus client. It increases the incoming buffer to 120MB instead of 4MB which is
31+
* used as default in Tyrus.
32+
*
33+
* <p>To install this factory add the following to your main application class:
34+
*
35+
* <pre>
36+
* static {
37+
* System.setProperty("com.github.kklisura.cdt.services.config.webSocketContainerFactory",
38+
* ConfigurableTyrusClientFactory.class.getName());
39+
* }
40+
* </pre>
41+
*
42+
* <p>This sets system property to a ConfigurableTyrusClientFactory class.
43+
*
44+
* <p>Use this class as template for any configuration changes to underlying web-socket client.
45+
*
46+
* <p><a href="https://tyrus-project.github.io/documentation/1.13.1/user-guide.html#d0e1197">8.4.
47+
* Incoming buffer size on tyrus</a>
48+
*
49+
* @author Kenan Klisura
50+
*/
51+
public class ConfigurableTyrusClientFactory implements WebSocketContainerFactory {
52+
public static final String INCOMING_BUFFER_SIZE_PROPERTY =
53+
"org.glassfish.tyrus.incomingBufferSize";
54+
55+
public static final int KB = 1024;
56+
public static final int MB = 1024 * KB;
57+
58+
private static final int INCOMING_BUFFER_SIZE = 120 * MB;
59+
60+
@Override
61+
public WebSocketContainer getWebSocketContainer() {
62+
final ClientManager client = ClientManager.createClient(GrizzlyClientContainer.class.getName());
63+
// Tyrus configuration goes here.
64+
client.getProperties().put(INCOMING_BUFFER_SIZE_PROPERTY, INCOMING_BUFFER_SIZE);
65+
return client;
66+
}
67+
}

cdt-java-client/src/main/java/com/github/kklisura/cdt/services/factory/impl/DefaultWebSocketContainerFactory.java

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,9 @@
2020
* #L%
2121
*/
2222

23+
import static com.github.kklisura.cdt.services.factory.impl.ConfigurableTyrusClientFactory.INCOMING_BUFFER_SIZE_PROPERTY;
24+
import static com.github.kklisura.cdt.services.factory.impl.ConfigurableTyrusClientFactory.MB;
25+
2326
import com.github.kklisura.cdt.services.factory.WebSocketContainerFactory;
2427
import javax.websocket.WebSocketContainer;
2528
import org.glassfish.tyrus.client.ClientManager;
@@ -31,8 +34,12 @@
3134
* @author Kenan Klisura
3235
*/
3336
public class DefaultWebSocketContainerFactory implements WebSocketContainerFactory {
37+
private static final int INCOMING_BUFFER_SIZE = 8 * MB;
38+
3439
@Override
3540
public WebSocketContainer getWebSocketContainer() {
36-
return ClientManager.createClient(GrizzlyClientContainer.class.getName());
41+
final ClientManager client = ClientManager.createClient(GrizzlyClientContainer.class.getName());
42+
client.getProperties().put(INCOMING_BUFFER_SIZE_PROPERTY, INCOMING_BUFFER_SIZE);
43+
return client;
3744
}
3845
}

cdt-java-client/src/main/java/com/github/kklisura/cdt/services/impl/WebSocketServiceImpl.java

Lines changed: 44 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
* #L%
2121
*/
2222

23+
import static com.github.kklisura.cdt.services.impl.utils.WebSocketUtils.isTyrusBufferOverflowCloseReason;
2324
import static com.github.kklisura.cdt.services.utils.ConfigurationUtils.systemProperty;
2425

2526
import com.github.kklisura.cdt.services.WebSocketService;
@@ -29,12 +30,7 @@
2930
import java.io.IOException;
3031
import java.net.URI;
3132
import java.util.function.Consumer;
32-
import javax.websocket.DeploymentException;
33-
import javax.websocket.Endpoint;
34-
import javax.websocket.EndpointConfig;
35-
import javax.websocket.MessageHandler;
36-
import javax.websocket.Session;
37-
import javax.websocket.WebSocketContainer;
33+
import javax.websocket.*;
3834
import org.slf4j.Logger;
3935
import org.slf4j.LoggerFactory;
4036

@@ -89,16 +85,28 @@ public static WebSocketService create(URI uri) throws WebSocketServiceException
8985
public void connect(URI uri) throws WebSocketServiceException {
9086
LOGGER.debug("Connecting to ws server {}", uri);
9187

88+
final WebSocketServiceImpl webSocketService = this;
89+
9290
try {
9391
session =
9492
WEB_SOCKET_CONTAINER.connectToServer(
9593
new Endpoint() {
9694
@Override
9795
public void onOpen(Session session, EndpointConfig config) {
98-
LOGGER.info("Connected to ws server {}", uri);
96+
webSocketService.onOpen(session, config);
97+
}
98+
99+
@Override
100+
public void onClose(Session session, CloseReason closeReason) {
101+
super.onClose(session, closeReason);
102+
webSocketService.onClose(session, closeReason);
99103
}
100104

101-
// TODO(kklisura): Add close handler.
105+
@Override
106+
public void onError(Session session, Throwable thr) {
107+
super.onError(session, thr);
108+
webSocketService.onError(session, thr);
109+
}
102110
},
103111
uri);
104112
} catch (DeploymentException | IOException e) {
@@ -143,11 +151,39 @@ public void onMessage(String message) {
143151
public void close() {
144152
try {
145153
session.close();
154+
session = null;
146155
} catch (IOException e) {
147156
LOGGER.error("Failed closing ws session on {}...", session.getRequestURI(), e);
148157
}
149158
}
150159

160+
@Override
161+
public boolean closed() {
162+
return session == null || !session.isOpen();
163+
}
164+
165+
private void onOpen(Session session, EndpointConfig config) {
166+
LOGGER.info("Connected to ws {}", session.getRequestURI());
167+
}
168+
169+
private void onClose(Session session, CloseReason closeReason) {
170+
LOGGER.info(
171+
"Web socket connection closed {}, {}",
172+
closeReason.getCloseCode(),
173+
closeReason.getReasonPhrase());
174+
175+
if (isTyrusBufferOverflowCloseReason(closeReason)) {
176+
LOGGER.info(
177+
"Web socket connection closed due to BufferOverflow raised by Tyrus client. This indicates the message "
178+
+ "about to be received is larger than the incoming buffer in Tyrus client. "
179+
+ "See ConfigurableTyrusClientFactory class source on how to increase the incoming buffer size in Tyrus or visit https://github.com/kklisura/chrome-devtools-java-client/blob/master/cdt-examples/src/main/java/com/github/kklisura/cdt/examples/IncreasedIncomingBufferInTyrusExample.java");
180+
}
181+
}
182+
183+
private void onError(Session session, Throwable thr) {
184+
LOGGER.error("Error in web socket session.", thr);
185+
}
186+
151187
/**
152188
* Returns a WebSocketContainer retrieved from class defined in system property
153189
* com.github.kklisura.cdt.services.config.webSocketContainerProvider. The default value for this
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
package com.github.kklisura.cdt.services.impl.utils;
2+
3+
/*-
4+
* #%L
5+
* cdt-java-client
6+
* %%
7+
* Copyright (C) 2018 - 2019 Kenan Klisura
8+
* %%
9+
* Licensed under the Apache License, Version 2.0 (the "License");
10+
* you may not use this file except in compliance with the License.
11+
* You may obtain a copy of the License at
12+
*
13+
* http://www.apache.org/licenses/LICENSE-2.0
14+
*
15+
* Unless required by applicable law or agreed to in writing, software
16+
* distributed under the License is distributed on an "AS IS" BASIS,
17+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
18+
* See the License for the specific language governing permissions and
19+
* limitations under the License.
20+
* #L%
21+
*/
22+
23+
import javax.websocket.CloseReason;
24+
25+
/**
26+
* Web socket related utils.
27+
*
28+
* @author Kenan Klisura
29+
*/
30+
public final class WebSocketUtils {
31+
private static final String TYRUS_BUFFER_OVERFLOW = "Buffer overflow.";
32+
33+
private WebSocketUtils() {
34+
// Empty ctor.
35+
}
36+
37+
/**
38+
* Is the reason for closing tyrus buffer overflow.
39+
*
40+
* @param closeReason Close reason.
41+
* @return True if this is unexpected close due to buffer overflow.
42+
*/
43+
public static boolean isTyrusBufferOverflowCloseReason(CloseReason closeReason) {
44+
return CloseReason.CloseCodes.UNEXPECTED_CONDITION.equals(closeReason.getCloseCode())
45+
&& TYRUS_BUFFER_OVERFLOW.equals(closeReason.getReasonPhrase());
46+
}
47+
}

0 commit comments

Comments
 (0)