Author: markt
Date: Sun Jun 3 18:50:48 2012
New Revision: 1345739
URL: http://svn.apache.org/viewvc?rev=1345739&view=rev
Log:
Fix https://issues.apache.org/bugzilla/show_bug.cgi?id=53339
Ensure WebSocket event calls are made using the web application's class loader.
Includes a test case.
Modified:
tomcat/tc7.0.x/trunk/ (props changed)
tomcat/tc7.0.x/trunk/java/org/apache/catalina/websocket/StreamInbound.java
tomcat/tc7.0.x/trunk/test/org/apache/catalina/websocket/TestWebSocket.java
tomcat/tc7.0.x/trunk/webapps/docs/changelog.xml
Propchange: tomcat/tc7.0.x/trunk/
------------------------------------------------------------------------------
Merged /tomcat/trunk:r1345737
Modified:
tomcat/tc7.0.x/trunk/java/org/apache/catalina/websocket/StreamInbound.java
URL:
http://svn.apache.org/viewvc/tomcat/tc7.0.x/trunk/java/org/apache/catalina/websocket/StreamInbound.java?rev=1345739&r1=1345738&r2=1345739&view=diff
==============================================================================
--- tomcat/tc7.0.x/trunk/java/org/apache/catalina/websocket/StreamInbound.java
(original)
+++ tomcat/tc7.0.x/trunk/java/org/apache/catalina/websocket/StreamInbound.java
Sun Jun 3 18:50:48 2012
@@ -37,12 +37,17 @@ import org.apache.tomcat.util.net.Abstra
*/
public abstract class StreamInbound implements UpgradeInbound {
+ private final ClassLoader applicationClassLoader;
private UpgradeProcessor<?> processor = null;
private WsOutbound outbound;
private int outboundByteBufferSize = WsOutbound.DEFAULT_BUFFER_SIZE;
private int outboundCharBufferSize = WsOutbound.DEFAULT_BUFFER_SIZE;
+ public StreamInbound() {
+ applicationClassLoader =
Thread.currentThread().getContextClassLoader();
+ }
+
public int getOutboundByteBufferSize() {
return outboundByteBufferSize;
@@ -122,11 +127,11 @@ public abstract class StreamInbound impl
byte opCode = frame.getOpCode();
if (opCode == Constants.OPCODE_BINARY) {
- onBinaryData(wsIs);
+ doOnBinaryData(wsIs);
} else if (opCode == Constants.OPCODE_TEXT) {
InputStreamReader r =
new InputStreamReader(wsIs, new Utf8Decoder());
- onTextData(r);
+ doOnTextData(r);
} else if (opCode == Constants.OPCODE_CLOSE){
closeOutboundConnection(frame);
return SocketState.CLOSED;
@@ -159,11 +164,37 @@ public abstract class StreamInbound impl
return SocketState.UPGRADED;
}
+ private void doOnBinaryData(InputStream is) throws IOException {
+ // Need to call onClose using the web application's class loader
+ Thread t = Thread.currentThread();
+ ClassLoader cl = t.getContextClassLoader();
+ t.setContextClassLoader(applicationClassLoader);
+ try {
+ onBinaryData(is);
+ } finally {
+ t.setContextClassLoader(cl);
+ }
+ }
+
+
+ private void doOnTextData(Reader r) throws IOException {
+ // Need to call onClose using the web application's class loader
+ Thread t = Thread.currentThread();
+ ClassLoader cl = t.getContextClassLoader();
+ t.setContextClassLoader(applicationClassLoader);
+ try {
+ onTextData(r);
+ } finally {
+ t.setContextClassLoader(cl);
+ }
+ }
+
+
private void closeOutboundConnection(int status, ByteBuffer data) throws
IOException {
try {
getWsOutbound().close(status, data);
} finally {
- onClose(status);
+ doOnClose(status);
}
}
@@ -171,13 +202,33 @@ public abstract class StreamInbound impl
try {
getWsOutbound().close(frame);
} finally {
- onClose(Constants.OPCODE_CLOSE);
+ doOnClose(Constants.OPCODE_CLOSE);
+ }
+ }
+
+ private void doOnClose(int status) {
+ // Need to call onClose using the web application's class loader
+ Thread t = Thread.currentThread();
+ ClassLoader cl = t.getContextClassLoader();
+ t.setContextClassLoader(applicationClassLoader);
+ try {
+ onClose(status);
+ } finally {
+ t.setContextClassLoader(cl);
}
}
@Override
- public void onUpgradeComplete() {
- onOpen(outbound);
+ public final void onUpgradeComplete() {
+ // Need to call onOpen using the web application's class loader
+ Thread t = Thread.currentThread();
+ ClassLoader cl = t.getContextClassLoader();
+ t.setContextClassLoader(applicationClassLoader);
+ try {
+ onOpen(outbound);
+ } finally {
+ t.setContextClassLoader(cl);
+ }
}
/**
Modified:
tomcat/tc7.0.x/trunk/test/org/apache/catalina/websocket/TestWebSocket.java
URL:
http://svn.apache.org/viewvc/tomcat/tc7.0.x/trunk/test/org/apache/catalina/websocket/TestWebSocket.java?rev=1345739&r1=1345738&r2=1345739&view=diff
==============================================================================
--- tomcat/tc7.0.x/trunk/test/org/apache/catalina/websocket/TestWebSocket.java
(original)
+++ tomcat/tc7.0.x/trunk/test/org/apache/catalina/websocket/TestWebSocket.java
Sun Jun 3 18:50:48 2012
@@ -28,16 +28,23 @@ import java.io.Writer;
import java.net.InetSocketAddress;
import java.net.Socket;
import java.net.SocketAddress;
+import java.nio.ByteBuffer;
+import java.nio.CharBuffer;
import java.security.MessageDigest;
import java.util.ArrayList;
import java.util.List;
+import javax.naming.InitialContext;
+import javax.naming.NamingException;
+
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import org.junit.Test;
+import org.apache.catalina.Context;
+import org.apache.catalina.deploy.ContextEnvironment;
import org.apache.catalina.startup.Tomcat;
import org.apache.catalina.startup.TomcatBaseTest;
import org.apache.catalina.util.Base64;
@@ -117,7 +124,6 @@ public class TestWebSocket extends Tomca
String responseLine = client.reader.readLine();
assertTrue(responseLine.startsWith("HTTP/1.1 426"));
- // Swallow the headers
List<String> headerlines = new ArrayList<String>();
String responseHeaderLine = client.reader.readLine();
@@ -208,9 +214,7 @@ public class TestWebSocket extends Tomca
String responseLine = client.reader.readLine();
assertTrue(responseLine.startsWith("HTTP/1.1 101"));
- // Swallow the headers
String accept = null;
-
String responseHeaderLine = client.reader.readLine();
while (!responseHeaderLine.equals("")) {
if(responseHeaderLine.startsWith("Sec-WebSocket-Accept: ")) {
@@ -235,6 +239,105 @@ public class TestWebSocket extends Tomca
}
+ @Test
+ public void testBug53339() throws Exception {
+ Tomcat tomcat = getTomcatInstance();
+ tomcat.enableNaming();
+
+ // Must have a real docBase - just use temp
+ Context ctx =
+ tomcat.addContext("", System.getProperty("java.io.tmpdir"));
+
+ Tomcat.addServlet(ctx, "Bug53339", new Bug53339Servlet());
+ ctx.addServletMapping("/*", "Bug53339");
+
+ // Create the resource
+ ContextEnvironment env = new ContextEnvironment();
+ env.setName(Bug53339WsInbound.JNDI_NAME);
+ env.setType(String.class.getName());
+ env.setValue(Bug53339WsInbound.TEST_MESSAGE);
+ ctx.getNamingResources().addEnvironment(env);
+
+ tomcat.start();
+
+ WebSocketClient client= new WebSocketClient(getPort());
+
+ // Send the WebSocket handshake
+ client.writer.write("GET / HTTP/1.1" + CRLF);
+ client.writer.write("Host: foo" + CRLF);
+ client.writer.write("Upgrade: websocket" + CRLF);
+ client.writer.write("Connection: upgrade" + CRLF);
+ client.writer.write("Sec-WebSocket-Version: 13" + CRLF);
+ client.writer.write("Sec-WebSocket-Key: TODO" + CRLF);
+ client.writer.write(CRLF);
+ client.writer.flush();
+
+ // Make sure we got an upgrade response
+ String responseLine = client.reader.readLine();
+ assertTrue(responseLine.startsWith("HTTP/1.1 101"));
+
+ // Swallow the headers
+ String responseHeaderLine = client.reader.readLine();
+ while (!responseHeaderLine.equals("")) {
+ responseHeaderLine = client.reader.readLine();
+ }
+
+ // Now we can do WebSocket
+ String msg = client.readMessage();
+ assertEquals(Bug53339WsInbound.TEST_MESSAGE, msg);
+
+ // Finished with the socket
+ client.close();
+ }
+
+
+ private static class Bug53339Servlet extends WebSocketServlet {
+
+ private static final long serialVersionUID = 1L;
+
+ @Override
+ protected StreamInbound createWebSocketInbound(String subProtocol) {
+ return new Bug53339WsInbound();
+ }
+ }
+
+
+ private static class Bug53339WsInbound extends MessageInbound {
+
+ public static final String TEST_MESSAGE = "Test Message";
+ public static final String JNDI_NAME = "Bug53339Message";
+
+ @Override
+ protected void onOpen(WsOutbound outbound) {
+ String msg = "Error";
+ try {
+ javax.naming.Context initCtx = new InitialContext();
+ msg = (String) initCtx.lookup(
+ "java:comp/env/" + JNDI_NAME);
+ } catch (NamingException e) {
+ // Ignore - the test checks if the message is sent
+ e.printStackTrace(); // for debug purposes if the test fails
+ }
+ CharBuffer cb = CharBuffer.wrap("" + msg);
+ try {
+ outbound.writeTextMessage(cb);
+ } catch (IOException e) {
+ // Ignore - the test checks if the message is sent
+ }
+
+ }
+
+ @Override
+ protected void onBinaryMessage(ByteBuffer message) throws IOException {
+ // Ignore
+ }
+
+ @Override
+ protected void onTextMessage(CharBuffer message) throws IOException {
+ // Ignore
+ }
+ }
+
private static class WebSocketClient {
private OutputStream os;
private InputStream is;
Modified: tomcat/tc7.0.x/trunk/webapps/docs/changelog.xml
URL:
http://svn.apache.org/viewvc/tomcat/tc7.0.x/trunk/webapps/docs/changelog.xml?rev=1345739&r1=1345738&r2=1345739&view=diff
==============================================================================
--- tomcat/tc7.0.x/trunk/webapps/docs/changelog.xml (original)
+++ tomcat/tc7.0.x/trunk/webapps/docs/changelog.xml Sun Jun 3 18:50:48 2012
@@ -190,6 +190,10 @@
by Rossen Stoyanchev. (markt)
</fix>
<fix>
+ <bug>53339</bug>: Ensure WebSocket call backs (<code>onOpen</code>
etc.)
+ are called using the web application's class loader. (markt)
+ </fix>
+ <fix>
<bug>53342</bug>: To avoid BindException, make startStopThreads into a
demon thread. (kfujino)
</fix>
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]