Merged branch 'jetty-9.2.x' into 'master'.

This commit is contained in:
Simone Bordet 2015-01-09 12:51:13 +01:00
commit 8a27385bc3
24 changed files with 785 additions and 120 deletions

24
.gitignore vendored
View File

@ -2,23 +2,30 @@
.classpath .classpath
.project .project
.settings .settings
.gitignore
# maven # maven
target/ target/
*/src/main/java/META-INF/ */src/main/java/META-INF/
*.versionsBackup *.versionsBackup
*.releaseBackup
bin/ bin/
# common junk # common junk
*.log *.log
*.swp
*.diff *.diff
*.patch *.patch
~* *.sw[a-p]
*~ *.bak
*.backup
*.debug
*.dump
# intellij # vim
.*.sw[a-p]
*~
~*
# intellij / android studio
*.iml *.iml
*.ipr *.ipr
*.iws *.iws
@ -34,12 +41,5 @@ bin/
# netbeans # netbeans
/nbproject /nbproject
# vim
.*.sw[a-p]
# merge tooling # merge tooling
*.orig *.orig
#maven
*.versionsBackup
*.releaseBackup

View File

@ -134,10 +134,14 @@ public abstract class HttpConnection implements Connection
CookieStore cookieStore = getHttpClient().getCookieStore(); CookieStore cookieStore = getHttpClient().getCookieStore();
if (cookieStore != null) if (cookieStore != null)
{ {
StringBuilder cookies = convertCookies(cookieStore.get(request.getURI()), null); URI uri = request.getURI();
cookies = convertCookies(request.getCookies(), cookies); if (uri != null)
if (cookies != null) {
request.header(HttpHeader.COOKIE.asString(), cookies.toString()); StringBuilder cookies = convertCookies(cookieStore.get(uri), null);
cookies = convertCookies(request.getCookies(), cookies);
if (cookies != null)
request.header(HttpHeader.COOKIE.asString(), cookies.toString());
}
} }
// Authorization // Authorization

View File

@ -22,6 +22,7 @@ import java.io.IOException;
import java.io.UnsupportedEncodingException; import java.io.UnsupportedEncodingException;
import java.net.HttpCookie; import java.net.HttpCookie;
import java.net.URI; import java.net.URI;
import java.net.URISyntaxException;
import java.net.URLDecoder; import java.net.URLDecoder;
import java.net.URLEncoder; import java.net.URLEncoder;
import java.nio.ByteBuffer; import java.nio.ByteBuffer;
@ -57,6 +58,8 @@ import org.eclipse.jetty.util.Fields;
public class HttpRequest implements Request public class HttpRequest implements Request
{ {
private static final URI NULL_URI = URI.create("null:0");
private final HttpFields headers = new HttpFields(); private final HttpFields headers = new HttpFields();
private final Fields params = new Fields(true); private final Fields params = new Fields(true);
private final List<Response.ResponseListener> responseListeners = new ArrayList<>(); private final List<Response.ResponseListener> responseListeners = new ArrayList<>();
@ -158,22 +161,30 @@ public class HttpRequest implements Request
@Override @Override
public Request path(String path) public Request path(String path)
{ {
URI uri = URI.create(path); URI uri = newURI(path);
String rawPath = uri.getRawPath(); if (uri == null)
if (uri.isOpaque())
rawPath = path;
if (rawPath == null)
rawPath = "";
this.path = rawPath;
String query = uri.getRawQuery();
if (query != null)
{ {
this.query = query; this.path = path;
params.clear(); this.query = null;
extractParams(query); }
else
{
String rawPath = uri.getRawPath();
if (uri.isOpaque())
rawPath = path;
if (rawPath == null)
rawPath = "";
this.path = rawPath;
String query = uri.getRawQuery();
if (query != null)
{
this.query = query;
params.clear();
extractParams(query);
}
if (uri.isAbsolute())
this.path = buildURI(false).toString();
} }
if (uri.isAbsolute())
this.path = buildURI(false).toString();
this.uri = null; this.uri = null;
return this; return this;
} }
@ -187,9 +198,9 @@ public class HttpRequest implements Request
@Override @Override
public URI getURI() public URI getURI()
{ {
if (uri != null) if (uri == null)
return uri; uri = buildURI(true);
return uri = buildURI(true); return uri == NULL_URI ? null : uri;
} }
@Override @Override
@ -762,12 +773,28 @@ public class HttpRequest implements Request
String query = getQuery(); String query = getQuery();
if (query != null && withQuery) if (query != null && withQuery)
path += "?" + query; path += "?" + query;
URI result = URI.create(path); URI result = newURI(path);
if (result == null)
return NULL_URI;
if (!result.isAbsolute() && !result.isOpaque()) if (!result.isAbsolute() && !result.isOpaque())
result = URI.create(new Origin(getScheme(), getHost(), getPort()).asString() + path); result = URI.create(new Origin(getScheme(), getHost(), getPort()).asString() + path);
return result; return result;
} }
private URI newURI(String uri)
{
try
{
return new URI(uri);
}
catch (URISyntaxException x)
{
// The "path" of a HTTP request may not be a URI,
// for example for CONNECT 127.0.0.1:8080 or OPTIONS *.
return null;
}
}
@Override @Override
public String toString() public String toString()
{ {

View File

@ -445,6 +445,11 @@ public class HttpClientTimeoutTest extends AbstractHttpClientServerTest
// Expected timeout during connect, continue the test. // Expected timeout during connect, continue the test.
Assume.assumeTrue(true); Assume.assumeTrue(true);
} }
catch (Throwable x)
{
// Abort if any other exception happens.
Assume.assumeTrue(false);
}
} }
private class TimeoutHandler extends AbstractHandler private class TimeoutHandler extends AbstractHandler

View File

@ -18,8 +18,6 @@
package org.eclipse.jetty.proxy; package org.eclipse.jetty.proxy;
import static org.junit.Assert.assertEquals;
import java.io.IOException; import java.io.IOException;
import java.net.ConnectException; import java.net.ConnectException;
import java.net.Socket; import java.net.Socket;
@ -28,7 +26,6 @@ import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutionException; import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicReference; import java.util.concurrent.atomic.AtomicReference;
import javax.servlet.ServletException; import javax.servlet.ServletException;
import javax.servlet.ServletOutputStream; import javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletRequest;
@ -64,6 +61,8 @@ import org.junit.Ignore;
import org.junit.Rule; import org.junit.Rule;
import org.junit.Test; import org.junit.Test;
import static org.junit.Assert.assertEquals;
public class ProxyTunnellingTest public class ProxyTunnellingTest
{ {
@Rule @Rule
@ -148,8 +147,10 @@ public class ProxyTunnellingTest
try try
{ {
// Use a numeric host to test the URI of the CONNECT request.
String host = "127.0.0.1";
String body = "BODY"; String body = "BODY";
ContentResponse response = httpClient.newRequest("localhost", serverConnector.getLocalPort()) ContentResponse response = httpClient.newRequest(host, serverConnector.getLocalPort())
.scheme(HttpScheme.HTTPS.asString()) .scheme(HttpScheme.HTTPS.asString())
.method(HttpMethod.GET) .method(HttpMethod.GET)
.path("/echo?body=" + URLEncoder.encode(body, "UTF-8")) .path("/echo?body=" + URLEncoder.encode(body, "UTF-8"))

View File

@ -162,7 +162,7 @@ public class JsrEvents<T extends Annotation, C extends EndpointConfig>
onOpen.call(websocket,config); onOpen.call(websocket,config);
} }
public void callPong(RemoteEndpoint.Async endpoint, Object websocket, ByteBuffer pong) throws DecodeException, IOException public void callPong(RemoteEndpoint.Async endpoint, Object websocket, ByteBuffer pong)
{ {
if (onPong == null) if (onPong == null)
{ {

View File

@ -21,8 +21,6 @@ package org.eclipse.jetty.websocket.jsr356.annotations;
import java.lang.reflect.Method; import java.lang.reflect.Method;
import java.nio.ByteBuffer; import java.nio.ByteBuffer;
import javax.websocket.DecodeException;
import org.eclipse.jetty.websocket.jsr356.JsrPongMessage; import org.eclipse.jetty.websocket.jsr356.JsrPongMessage;
import org.eclipse.jetty.websocket.jsr356.JsrSession; import org.eclipse.jetty.websocket.jsr356.JsrSession;
import org.eclipse.jetty.websocket.jsr356.annotations.Param.Role; import org.eclipse.jetty.websocket.jsr356.annotations.Param.Role;
@ -45,7 +43,7 @@ public class OnMessagePongCallable extends OnMessageCallable
super(copy); super(copy);
} }
public Object call(Object endpoint, ByteBuffer buf) throws DecodeException public Object call(Object endpoint, ByteBuffer buf)
{ {
super.args[idxMessageObject] = new JsrPongMessage(buf); super.args[idxMessageObject] = new JsrPongMessage(buf);
return super.call(endpoint,super.args); return super.call(endpoint,super.args);

View File

@ -23,6 +23,7 @@ import java.io.InputStream;
import java.io.Reader; import java.io.Reader;
import java.nio.ByteBuffer; import java.nio.ByteBuffer;
import java.util.Map; import java.util.Map;
import javax.websocket.CloseReason; import javax.websocket.CloseReason;
import javax.websocket.DecodeException; import javax.websocket.DecodeException;
@ -123,7 +124,7 @@ public class JsrAnnotatedEventDriver extends AbstractJsrEventDriver
{ {
events.callBinaryStream(jsrsession.getAsyncRemote(),websocket,stream); events.callBinaryStream(jsrsession.getAsyncRemote(),websocket,stream);
} }
catch (DecodeException | IOException e) catch (Throwable e)
{ {
onFatalError(e); onFatalError(e);
} }
@ -167,7 +168,7 @@ public class JsrAnnotatedEventDriver extends AbstractJsrEventDriver
// FIN is always true here // FIN is always true here
events.callBinary(jsrsession.getAsyncRemote(),websocket,buf,true); events.callBinary(jsrsession.getAsyncRemote(),websocket,buf,true);
} }
catch (DecodeException e) catch (Throwable e)
{ {
onFatalError(e); onFatalError(e);
} }
@ -188,7 +189,15 @@ public class JsrAnnotatedEventDriver extends AbstractJsrEventDriver
@Override @Override
public void onError(Throwable cause) public void onError(Throwable cause)
{ {
events.callError(websocket,cause); try
{
events.callError(websocket,cause);
}
catch (Throwable e)
{
LOG.warn("Unable to call onError with cause", cause);
LOG.warn("Call to onError resulted in exception", e);
}
} }
private void onFatalError(Throwable t) private void onFatalError(Throwable t)
@ -203,15 +212,15 @@ public class JsrAnnotatedEventDriver extends AbstractJsrEventDriver
} }
@Override @Override
public void onInputStream(InputStream stream) public void onInputStream(InputStream stream) throws IOException
{ {
try try
{ {
events.callBinaryStream(jsrsession.getAsyncRemote(),websocket,stream); events.callBinaryStream(jsrsession.getAsyncRemote(),websocket,stream);
} }
catch (DecodeException | IOException e) catch (DecodeException e)
{ {
onFatalError(e); throw new RuntimeException("Unable decode input stream", e);
} }
} }
@ -223,7 +232,7 @@ public class JsrAnnotatedEventDriver extends AbstractJsrEventDriver
} }
catch (DecodeException e) catch (DecodeException e)
{ {
onFatalError(e); throw new RuntimeException("Unable decode partial binary message", e);
} }
} }
@ -235,46 +244,33 @@ public class JsrAnnotatedEventDriver extends AbstractJsrEventDriver
} }
catch (DecodeException e) catch (DecodeException e)
{ {
onFatalError(e); throw new RuntimeException("Unable decode partial text message", e);
} }
} }
@Override @Override
public void onPing(ByteBuffer buffer) public void onPing(ByteBuffer buffer)
{ {
try // Call pong, as there is no "onPing" method in the JSR
{ events.callPong(jsrsession.getAsyncRemote(),websocket,buffer);
events.callPong(jsrsession.getAsyncRemote(),websocket,buffer);
}
catch (DecodeException | IOException e)
{
onFatalError(e);
}
} }
@Override @Override
public void onPong(ByteBuffer buffer) public void onPong(ByteBuffer buffer)
{ {
try events.callPong(jsrsession.getAsyncRemote(),websocket,buffer);
{
events.callPong(jsrsession.getAsyncRemote(),websocket,buffer);
}
catch (DecodeException | IOException e)
{
onFatalError(e);
}
} }
@Override @Override
public void onReader(Reader reader) public void onReader(Reader reader) throws IOException
{ {
try try
{ {
events.callTextStream(jsrsession.getAsyncRemote(),websocket,reader); events.callTextStream(jsrsession.getAsyncRemote(),websocket,reader);
} }
catch (DecodeException | IOException e) catch (DecodeException e)
{ {
onFatalError(e); throw new RuntimeException("Unable decode reader", e);
} }
} }
@ -343,7 +339,7 @@ public class JsrAnnotatedEventDriver extends AbstractJsrEventDriver
{ {
events.callTextStream(jsrsession.getAsyncRemote(),websocket,stream); events.callTextStream(jsrsession.getAsyncRemote(),websocket,stream);
} }
catch (DecodeException | IOException e) catch (Throwable e)
{ {
onFatalError(e); onFatalError(e);
} }
@ -380,7 +376,7 @@ public class JsrAnnotatedEventDriver extends AbstractJsrEventDriver
// FIN is always true here // FIN is always true here
events.callText(jsrsession.getAsyncRemote(),websocket,message,true); events.callText(jsrsession.getAsyncRemote(),websocket,message,true);
} }
catch (DecodeException e) catch (Throwable e)
{ {
onFatalError(e); onFatalError(e);
} }

View File

@ -23,6 +23,7 @@ import java.io.InputStream;
import java.io.Reader; import java.io.Reader;
import java.nio.ByteBuffer; import java.nio.ByteBuffer;
import java.util.Map; import java.util.Map;
import javax.websocket.CloseReason; import javax.websocket.CloseReason;
import javax.websocket.Endpoint; import javax.websocket.Endpoint;
import javax.websocket.MessageHandler; import javax.websocket.MessageHandler;
@ -134,20 +135,23 @@ public class JsrEndpointEventDriver extends AbstractJsrEventDriver
{ {
LOG.debug("onConnect({}, {})",jsrsession,config); LOG.debug("onConnect({}, {})",jsrsession,config);
} }
try
{ // Let unhandled exceptions flow out
endpoint.onOpen(jsrsession,config); endpoint.onOpen(jsrsession,config);
}
catch (Throwable t)
{
LOG.warn("Uncaught exception",t);
}
} }
@Override @Override
public void onError(Throwable cause) public void onError(Throwable cause)
{ {
endpoint.onError(jsrsession,cause); LOG.warn(cause);
try
{
endpoint.onError(jsrsession,cause);
}
catch (Throwable t)
{
LOG.warn("Unable to report to onError due to exception",t);
}
} }
@Override @Override

View File

@ -0,0 +1,65 @@
//
// ========================================================================
// Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
// ------------------------------------------------------------------------
// All rights reserved. This program and the accompanying materials
// are made available under the terms of the Eclipse Public License v1.0
// and Apache License v2.0 which accompanies this distribution.
//
// The Eclipse Public License is available at
// http://www.eclipse.org/legal/epl-v10.html
//
// The Apache License v2.0 is available at
// http://www.opensource.org/licenses/apache2.0.php
//
// You may elect to redistribute this code under either of these licenses.
// ========================================================================
//
package org.eclipse.jetty.websocket.jsr356.misbehaving;
import java.util.LinkedList;
import java.util.concurrent.CountDownLatch;
import javax.websocket.ClientEndpoint;
import javax.websocket.CloseReason;
import javax.websocket.EndpointConfig;
import javax.websocket.OnClose;
import javax.websocket.OnError;
import javax.websocket.OnOpen;
import javax.websocket.Session;
/**
* A JSR-356 Annotated that tosses a RuntimeException during its onOpen call
*/
@ClientEndpoint
public class AnnotatedRuntimeOnOpen
{
public CountDownLatch closeLatch = new CountDownLatch(1);
public CloseReason closeReason;
public LinkedList<Throwable> errors = new LinkedList<>();
@OnOpen
public void onOpen(Session session, EndpointConfig config)
{
// Intentional runtime exception.
int[] arr = new int[5];
for (int i = 0; i < 10; i++)
{
arr[i] = 222;
}
}
@OnClose
public void onClose(Session session, CloseReason closeReason)
{
this.closeReason = closeReason;
closeLatch.countDown();
}
@OnError
public void onError(Session session, Throwable thr)
{
errors.add(thr);
}
}

View File

@ -0,0 +1,63 @@
//
// ========================================================================
// Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
// ------------------------------------------------------------------------
// All rights reserved. This program and the accompanying materials
// are made available under the terms of the Eclipse Public License v1.0
// and Apache License v2.0 which accompanies this distribution.
//
// The Eclipse Public License is available at
// http://www.eclipse.org/legal/epl-v10.html
//
// The Apache License v2.0 is available at
// http://www.opensource.org/licenses/apache2.0.php
//
// You may elect to redistribute this code under either of these licenses.
// ========================================================================
//
package org.eclipse.jetty.websocket.jsr356.misbehaving;
import java.util.LinkedList;
import java.util.concurrent.CountDownLatch;
import javax.websocket.CloseReason;
import javax.websocket.Endpoint;
import javax.websocket.EndpointConfig;
import javax.websocket.Session;
/**
* A JSR-356 Endpoint that tosses a RuntimeException during its onOpen call
*/
public class EndpointRuntimeOnOpen extends Endpoint
{
public CountDownLatch closeLatch = new CountDownLatch(1);
public CloseReason closeReason;
public LinkedList<Throwable> errors = new LinkedList<>();
@Override
public void onOpen(Session session, EndpointConfig config)
{
// Intentional runtime exception.
int[] arr = new int[5];
for (int i = 0; i < 10; i++)
{
arr[i] = 222;
}
}
@Override
public void onClose(Session session, CloseReason closeReason)
{
super.onClose(session,closeReason);
this.closeReason = closeReason;
closeLatch.countDown();
}
@Override
public void onError(Session session, Throwable thr)
{
super.onError(session,thr);
errors.add(thr);
}
}

View File

@ -0,0 +1,127 @@
//
// ========================================================================
// Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
// ------------------------------------------------------------------------
// All rights reserved. This program and the accompanying materials
// are made available under the terms of the Eclipse Public License v1.0
// and Apache License v2.0 which accompanies this distribution.
//
// The Eclipse Public License is available at
// http://www.eclipse.org/legal/epl-v10.html
//
// The Apache License v2.0 is available at
// http://www.opensource.org/licenses/apache2.0.php
//
// You may elect to redistribute this code under either of these licenses.
// ========================================================================
//
package org.eclipse.jetty.websocket.jsr356.misbehaving;
import static org.hamcrest.Matchers.*;
import static org.junit.Assert.*;
import java.net.URI;
import java.util.concurrent.TimeUnit;
import javax.websocket.ContainerProvider;
import javax.websocket.Session;
import javax.websocket.WebSocketContainer;
import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.server.ServerConnector;
import org.eclipse.jetty.server.handler.ContextHandler;
import org.eclipse.jetty.util.log.StacklessLogging;
import org.eclipse.jetty.websocket.jsr356.EchoHandler;
import org.eclipse.jetty.websocket.jsr356.endpoints.JsrEndpointEventDriver;
import org.junit.AfterClass;
import org.junit.BeforeClass;
import org.junit.Test;
public class MisbehavingClassTest
{
private static Server server;
private static EchoHandler handler;
private static URI serverUri;
@BeforeClass
public static void startServer() throws Exception
{
server = new Server();
ServerConnector connector = new ServerConnector(server);
server.addConnector(connector);
handler = new EchoHandler();
ContextHandler context = new ContextHandler();
context.setContextPath("/");
context.setHandler(handler);
server.setHandler(context);
// Start Server
server.start();
String host = connector.getHost();
if (host == null)
{
host = "localhost";
}
int port = connector.getLocalPort();
serverUri = new URI(String.format("ws://%s:%d/",host,port));
}
@AfterClass
public static void stopServer()
{
try
{
server.stop();
}
catch (Exception e)
{
e.printStackTrace(System.err);
}
}
@Test
public void testEndpointRuntimeOnOpen() throws Exception
{
WebSocketContainer container = ContainerProvider.getWebSocketContainer();
EndpointRuntimeOnOpen socket = new EndpointRuntimeOnOpen();
try (StacklessLogging logging = new StacklessLogging(EndpointRuntimeOnOpen.class,JsrEndpointEventDriver.class))
{
// expecting ArrayIndexOutOfBoundsException during onOpen
Session session = container.connectToServer(socket,serverUri);
assertThat("Close should have occurred",socket.closeLatch.await(1,TimeUnit.SECONDS),is(true));
// technically, the session object isn't invalid here.
assertThat("Session.isOpen",session.isOpen(),is(false));
assertThat("Should have only had 1 error",socket.errors.size(),is(1));
Throwable cause = socket.errors.pop();
assertThat("Error",cause,instanceOf(ArrayIndexOutOfBoundsException.class));
}
}
@Test
public void testAnnotatedRuntimeOnOpen() throws Exception
{
WebSocketContainer container = ContainerProvider.getWebSocketContainer();
AnnotatedRuntimeOnOpen socket = new AnnotatedRuntimeOnOpen();
try (StacklessLogging logging = new StacklessLogging(AnnotatedRuntimeOnOpen.class))
{
// expecting ArrayIndexOutOfBoundsException during onOpen
Session session = container.connectToServer(socket,serverUri);
assertThat("Close should have occurred",socket.closeLatch.await(1,TimeUnit.SECONDS),is(true));
// technically, the session object isn't invalid here.
assertThat("Session.isOpen",session.isOpen(),is(false));
assertThat("Should have only had 1 error",socket.errors.size(),is(1));
Throwable cause = socket.errors.pop();
assertThat("Error",cause,instanceOf(ArrayIndexOutOfBoundsException.class));
}
}
}

View File

@ -26,6 +26,7 @@ import java.lang.management.MemoryUsage;
import java.net.URI; import java.net.URI;
import java.util.concurrent.CountDownLatch; import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
import javax.websocket.ContainerProvider; import javax.websocket.ContainerProvider;
import javax.websocket.Endpoint; import javax.websocket.Endpoint;
import javax.websocket.EndpointConfig; import javax.websocket.EndpointConfig;
@ -74,6 +75,7 @@ public class MemoryUsageTest
server.stop(); server.stop();
} }
@SuppressWarnings("unused")
@Test @Test
public void testMemoryUsage() throws Exception public void testMemoryUsage() throws Exception
{ {

View File

@ -32,6 +32,11 @@ public class CloseStatus
*/ */
public static String trimMaxReasonLength(String reason) public static String trimMaxReasonLength(String reason)
{ {
if (reason == null)
{
return null;
}
if (reason.length() > MAX_REASON_PHRASE) if (reason.length() > MAX_REASON_PHRASE)
{ {
return reason.substring(0,MAX_REASON_PHRASE); return reason.substring(0,MAX_REASON_PHRASE);

View File

@ -105,7 +105,7 @@ public class WebSocketSession extends ContainerLifeCycle implements Session, Inc
@Override @Override
public void close(int statusCode, String reason) public void close(int statusCode, String reason)
{ {
connection.close(statusCode,reason); connection.close(statusCode,CloseStatus.trimMaxReasonLength(reason));
} }
/** /**

View File

@ -49,13 +49,13 @@ public interface EventDriver extends IncomingFrames
public void onFrame(Frame frame); public void onFrame(Frame frame);
public void onInputStream(InputStream stream); public void onInputStream(InputStream stream) throws IOException;
public void onPing(ByteBuffer buffer); public void onPing(ByteBuffer buffer);
public void onPong(ByteBuffer buffer); public void onPong(ByteBuffer buffer);
public void onReader(Reader reader); public void onReader(Reader reader) throws IOException;
public void onTextFrame(ByteBuffer buffer, boolean fin) throws IOException; public void onTextFrame(ByteBuffer buffer, boolean fin) throws IOException;

View File

@ -86,7 +86,15 @@ public class JettyAnnotatedEventDriver extends AbstractEventDriver
@Override @Override
public void run() public void run()
{ {
events.onBinary.call(websocket,session,msg); try
{
events.onBinary.call(websocket,session,msg);
}
catch (Throwable t)
{
// dispatched calls need to be reported
onError(t);
}
} }
}); });
} }
@ -188,7 +196,15 @@ public class JettyAnnotatedEventDriver extends AbstractEventDriver
@Override @Override
public void run() public void run()
{ {
events.onText.call(websocket,session,msg); try
{
events.onText.call(websocket,session,msg);
}
catch (Throwable t)
{
// dispatched calls need to be reported
onError(t);
}
} }
}); });
} }

View File

@ -22,7 +22,6 @@ import java.lang.annotation.Annotation;
import java.lang.reflect.Method; import java.lang.reflect.Method;
import java.lang.reflect.Modifier; import java.lang.reflect.Modifier;
import org.eclipse.jetty.util.StringUtil;
import org.eclipse.jetty.websocket.api.InvalidWebSocketException; import org.eclipse.jetty.websocket.api.InvalidWebSocketException;
import org.eclipse.jetty.websocket.common.events.ParamList; import org.eclipse.jetty.websocket.common.events.ParamList;
@ -39,7 +38,7 @@ public abstract class AbstractMethodAnnotationScanner<T>
StringBuilder err = new StringBuilder(); StringBuilder err = new StringBuilder();
err.append("Invalid declaration of "); err.append("Invalid declaration of ");
err.append(method); err.append(method);
err.append(StringUtil.__LINE_SEPARATOR); err.append(System.lineSeparator());
err.append("Method modifier must be public"); err.append("Method modifier must be public");
@ -51,7 +50,7 @@ public abstract class AbstractMethodAnnotationScanner<T>
StringBuilder err = new StringBuilder(); StringBuilder err = new StringBuilder();
err.append("Invalid declaration of "); err.append("Invalid declaration of ");
err.append(method); err.append(method);
err.append(StringUtil.__LINE_SEPARATOR); err.append(System.lineSeparator());
err.append("Method modifier may not be static"); err.append("Method modifier may not be static");
@ -66,7 +65,7 @@ public abstract class AbstractMethodAnnotationScanner<T>
StringBuilder err = new StringBuilder(); StringBuilder err = new StringBuilder();
err.append("Invalid declaration of "); err.append("Invalid declaration of ");
err.append(method); err.append(method);
err.append(StringUtil.__LINE_SEPARATOR); err.append(System.lineSeparator());
err.append("Return type must be ").append(type); err.append("Return type must be ").append(type);
@ -87,7 +86,7 @@ public abstract class AbstractMethodAnnotationScanner<T>
StringBuilder err = new StringBuilder(); StringBuilder err = new StringBuilder();
err.append("Duplicate @").append(annoClass.getSimpleName()).append(" declaration on "); err.append("Duplicate @").append(annoClass.getSimpleName()).append(" declaration on ");
err.append(method); err.append(method);
err.append(StringUtil.__LINE_SEPARATOR); err.append(System.lineSeparator());
err.append("@").append(annoClass.getSimpleName()).append(" previously declared at "); err.append("@").append(annoClass.getSimpleName()).append(" previously declared at ");
err.append(callable.getMethod()); err.append(callable.getMethod());

View File

@ -24,7 +24,6 @@ import java.util.Objects;
import org.eclipse.jetty.util.log.Log; import org.eclipse.jetty.util.log.Log;
import org.eclipse.jetty.util.log.Logger; import org.eclipse.jetty.util.log.Logger;
import org.eclipse.jetty.websocket.api.WebSocketException;
import org.eclipse.jetty.websocket.common.util.ReflectUtils; import org.eclipse.jetty.websocket.common.util.ReflectUtils;
/** /**
@ -70,36 +69,58 @@ public class CallableMethod
{ {
return this.method.invoke(obj,args); return this.method.invoke(obj,args);
} }
catch (IllegalAccessException | IllegalArgumentException | InvocationTargetException e) catch (Throwable t)
{ {
StringBuilder err = new StringBuilder(); String err = formatMethodCallError(args);
err.append("Cannot call method "); throw unwrapRuntimeException(err,t);
err.append(ReflectUtils.toString(pojo,method));
err.append(" with args: [");
boolean delim = false;
for (Object arg : args)
{
if (delim)
{
err.append(", ");
}
if (arg == null)
{
err.append("<null>");
}
else
{
err.append(arg.getClass().getName());
}
delim = true;
}
err.append("]");
throw new WebSocketException(err.toString(),e);
} }
} }
private RuntimeException unwrapRuntimeException(String err, final Throwable t)
{
Throwable ret = t;
while (ret instanceof InvocationTargetException)
{
ret = ((InvocationTargetException)ret).getCause();
}
if (ret instanceof RuntimeException)
{
return (RuntimeException)ret;
}
return new RuntimeException(err,ret);
}
public String formatMethodCallError(Object... args)
{
StringBuilder err = new StringBuilder();
err.append("Cannot call method ");
err.append(ReflectUtils.toString(pojo,method));
err.append(" with args: [");
boolean delim = false;
for (Object arg : args)
{
if (delim)
{
err.append(", ");
}
if (arg == null)
{
err.append("<null>");
}
else
{
err.append(arg.getClass().getName());
}
delim = true;
}
err.append("]");
return err.toString();
}
public Method getMethod() public Method getMethod()
{ {
return method; return method;

View File

@ -21,7 +21,6 @@ package org.eclipse.jetty.websocket.common.events.annotated;
import java.lang.annotation.Annotation; import java.lang.annotation.Annotation;
import java.lang.reflect.Method; import java.lang.reflect.Method;
import org.eclipse.jetty.util.StringUtil;
import org.eclipse.jetty.websocket.api.InvalidWebSocketException; import org.eclipse.jetty.websocket.api.InvalidWebSocketException;
import org.eclipse.jetty.websocket.common.events.ParamList; import org.eclipse.jetty.websocket.common.events.ParamList;
@ -34,7 +33,7 @@ public class InvalidSignatureException extends InvalidWebSocketException
StringBuilder err = new StringBuilder(); StringBuilder err = new StringBuilder();
err.append("Invalid declaration of "); err.append("Invalid declaration of ");
err.append(method); err.append(method);
err.append(StringUtil.__LINE_SEPARATOR); err.append(System.lineSeparator());
err.append("Acceptable method declarations for @"); err.append("Acceptable method declarations for @");
err.append(annoClass.getSimpleName()); err.append(annoClass.getSimpleName());
@ -43,7 +42,7 @@ public class InvalidSignatureException extends InvalidWebSocketException
{ {
for (Class<?>[] params : validParams) for (Class<?>[] params : validParams)
{ {
err.append(StringUtil.__LINE_SEPARATOR); err.append(System.lineSeparator());
err.append("public void ").append(method.getName()); err.append("public void ").append(method.getName());
err.append('('); err.append('(');
boolean delim = false; boolean delim = false;

View File

@ -0,0 +1,70 @@
//
// ========================================================================
// Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
// ------------------------------------------------------------------------
// All rights reserved. This program and the accompanying materials
// are made available under the terms of the Eclipse Public License v1.0
// and Apache License v2.0 which accompanies this distribution.
//
// The Eclipse Public License is available at
// http://www.eclipse.org/legal/epl-v10.html
//
// The Apache License v2.0 is available at
// http://www.opensource.org/licenses/apache2.0.php
//
// You may elect to redistribute this code under either of these licenses.
// ========================================================================
//
package org.eclipse.jetty.websocket.server.misbehaving;
import java.util.LinkedList;
import java.util.concurrent.CountDownLatch;
import org.eclipse.jetty.websocket.api.Session;
import org.eclipse.jetty.websocket.api.annotations.OnWebSocketClose;
import org.eclipse.jetty.websocket.api.annotations.OnWebSocketConnect;
import org.eclipse.jetty.websocket.api.annotations.OnWebSocketError;
import org.eclipse.jetty.websocket.api.annotations.WebSocket;
@WebSocket
public class AnnotatedRuntimeOnConnectSocket
{
public LinkedList<Throwable> errors = new LinkedList<>();
public CountDownLatch closeLatch = new CountDownLatch(1);
public int closeStatusCode;
public String closeReason;
@OnWebSocketConnect
public void onWebSocketConnect(Session sess)
{
// Intentional runtime exception.
int[] arr = new int[5];
for (int i = 0; i < 10; i++)
{
arr[i] = 222;
}
}
@OnWebSocketClose
public void onWebSocketClose(int statusCode, String reason)
{
closeLatch.countDown();
closeStatusCode = statusCode;
closeReason = reason;
}
@OnWebSocketError
public void onWebSocketError(Throwable cause)
{
this.errors.add(cause);
}
public void reset()
{
this.closeLatch = new CountDownLatch(1);
this.closeStatusCode = -1;
this.closeReason = null;
this.errors.clear();
}
}

View File

@ -0,0 +1,56 @@
//
// ========================================================================
// Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
// ------------------------------------------------------------------------
// All rights reserved. This program and the accompanying materials
// are made available under the terms of the Eclipse Public License v1.0
// and Apache License v2.0 which accompanies this distribution.
//
// The Eclipse Public License is available at
// http://www.eclipse.org/legal/epl-v10.html
//
// The Apache License v2.0 is available at
// http://www.opensource.org/licenses/apache2.0.php
//
// You may elect to redistribute this code under either of these licenses.
// ========================================================================
//
package org.eclipse.jetty.websocket.server.misbehaving;
import org.eclipse.jetty.websocket.servlet.ServletUpgradeRequest;
import org.eclipse.jetty.websocket.servlet.ServletUpgradeResponse;
import org.eclipse.jetty.websocket.servlet.WebSocketCreator;
import org.eclipse.jetty.websocket.servlet.WebSocketServlet;
import org.eclipse.jetty.websocket.servlet.WebSocketServletFactory;
@SuppressWarnings("serial")
public class BadSocketsServlet extends WebSocketServlet implements WebSocketCreator
{
public ListenerRuntimeOnConnectSocket listenerRuntimeConnect;
public AnnotatedRuntimeOnConnectSocket annotatedRuntimeConnect;
@Override
public void configure(WebSocketServletFactory factory)
{
factory.setCreator(this);
this.listenerRuntimeConnect = new ListenerRuntimeOnConnectSocket();
this.annotatedRuntimeConnect = new AnnotatedRuntimeOnConnectSocket();
}
@Override
public Object createWebSocket(ServletUpgradeRequest req, ServletUpgradeResponse resp)
{
if (req.hasSubProtocol("listener-runtime-connect"))
{
return this.listenerRuntimeConnect;
}
else if (req.hasSubProtocol("annotated-runtime-connect"))
{
return this.annotatedRuntimeConnect;
}
return null;
}
}

View File

@ -0,0 +1,74 @@
//
// ========================================================================
// Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
// ------------------------------------------------------------------------
// All rights reserved. This program and the accompanying materials
// are made available under the terms of the Eclipse Public License v1.0
// and Apache License v2.0 which accompanies this distribution.
//
// The Eclipse Public License is available at
// http://www.eclipse.org/legal/epl-v10.html
//
// The Apache License v2.0 is available at
// http://www.opensource.org/licenses/apache2.0.php
//
// You may elect to redistribute this code under either of these licenses.
// ========================================================================
//
package org.eclipse.jetty.websocket.server.misbehaving;
import java.util.LinkedList;
import java.util.concurrent.CountDownLatch;
import org.eclipse.jetty.websocket.api.Session;
import org.eclipse.jetty.websocket.api.WebSocketAdapter;
public class ListenerRuntimeOnConnectSocket extends WebSocketAdapter
{
public LinkedList<Throwable> errors = new LinkedList<>();
public CountDownLatch closeLatch = new CountDownLatch(1);
public int closeStatusCode;
public String closeReason;
@Override
public void onWebSocketConnect(Session sess)
{
super.onWebSocketConnect(sess);
// Intentional runtime exception.
int[] arr = new int[5];
for (int i = 0; i < 10; i++)
{
arr[i] = 222;
}
}
@Override
public void onWebSocketClose(int statusCode, String reason)
{
closeLatch.countDown();
closeStatusCode = statusCode;
closeReason = reason;
}
@Override
public void onWebSocketError(Throwable cause)
{
this.errors.add(cause);
}
@Override
public void onWebSocketText(String message)
{
getRemote().sendStringByFuture(message);
}
public void reset()
{
this.closeLatch = new CountDownLatch(1);
this.closeStatusCode = -1;
this.closeReason = null;
this.errors.clear();
}
}

View File

@ -0,0 +1,133 @@
//
// ========================================================================
// Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
// ------------------------------------------------------------------------
// All rights reserved. This program and the accompanying materials
// are made available under the terms of the Eclipse Public License v1.0
// and Apache License v2.0 which accompanies this distribution.
//
// The Eclipse Public License is available at
// http://www.eclipse.org/legal/epl-v10.html
//
// The Apache License v2.0 is available at
// http://www.opensource.org/licenses/apache2.0.php
//
// You may elect to redistribute this code under either of these licenses.
// ========================================================================
//
package org.eclipse.jetty.websocket.server.misbehaving;
import static org.hamcrest.Matchers.*;
import static org.junit.Assert.*;
import java.util.concurrent.TimeUnit;
import org.eclipse.jetty.toolchain.test.EventQueue;
import org.eclipse.jetty.util.log.StacklessLogging;
import org.eclipse.jetty.websocket.api.StatusCode;
import org.eclipse.jetty.websocket.common.CloseInfo;
import org.eclipse.jetty.websocket.common.OpCode;
import org.eclipse.jetty.websocket.common.WebSocketFrame;
import org.eclipse.jetty.websocket.common.events.AbstractEventDriver;
import org.eclipse.jetty.websocket.common.test.BlockheadClient;
import org.eclipse.jetty.websocket.server.SimpleServletServer;
import org.junit.AfterClass;
import org.junit.BeforeClass;
import org.junit.Test;
/**
* Testing badly behaving Socket class implementations to get the best
* error messages and state out of the websocket implementation.
*/
public class MisbehavingClassTest
{
private static SimpleServletServer server;
private static BadSocketsServlet badSocketsServlet;
@BeforeClass
public static void startServer() throws Exception
{
badSocketsServlet = new BadSocketsServlet();
server = new SimpleServletServer(badSocketsServlet);
server.start();
}
@AfterClass
public static void stopServer()
{
server.stop();
}
@Test
public void testListenerRuntimeOnConnect() throws Exception
{
try (BlockheadClient client = new BlockheadClient(server.getServerUri()))
{
client.setProtocols("listener-runtime-connect");
client.setTimeout(1,TimeUnit.SECONDS);
try (StacklessLogging scope = new StacklessLogging(AbstractEventDriver.class))
{
ListenerRuntimeOnConnectSocket socket = badSocketsServlet.listenerRuntimeConnect;
socket.reset();
client.connect();
client.sendStandardRequest();
client.expectUpgradeResponse();
EventQueue<WebSocketFrame> frames = client.readFrames(1,1,TimeUnit.SECONDS);
WebSocketFrame frame = frames.poll();
assertThat("frames[0].opcode",frame.getOpCode(),is(OpCode.CLOSE));
CloseInfo close = new CloseInfo(frame);
assertThat("Close Status Code",close.getStatusCode(),is(StatusCode.SERVER_ERROR));
client.write(close.asFrame()); // respond with close
// ensure server socket got close event
assertThat("Close Latch",socket.closeLatch.await(1,TimeUnit.SECONDS),is(true));
assertThat("closeStatusCode",socket.closeStatusCode,is(StatusCode.SERVER_ERROR));
// Validate errors
assertThat("socket.onErrors",socket.errors.size(),is(1));
Throwable cause = socket.errors.pop();
assertThat("Error type",cause,instanceOf(ArrayIndexOutOfBoundsException.class));
}
}
}
@Test
public void testAnnotatedRuntimeOnConnect() throws Exception
{
try (BlockheadClient client = new BlockheadClient(server.getServerUri()))
{
client.setProtocols("annotated-runtime-connect");
client.setTimeout(1,TimeUnit.SECONDS);
try (StacklessLogging scope = new StacklessLogging(AbstractEventDriver.class))
{
AnnotatedRuntimeOnConnectSocket socket = badSocketsServlet.annotatedRuntimeConnect;
socket.reset();
client.connect();
client.sendStandardRequest();
client.expectUpgradeResponse();
EventQueue<WebSocketFrame> frames = client.readFrames(1,1,TimeUnit.SECONDS);
WebSocketFrame frame = frames.poll();
assertThat("frames[0].opcode",frame.getOpCode(),is(OpCode.CLOSE));
CloseInfo close = new CloseInfo(frame);
assertThat("Close Status Code",close.getStatusCode(),is(StatusCode.SERVER_ERROR));
client.write(close.asFrame()); // respond with close
// ensure server socket got close event
assertThat("Close Latch",socket.closeLatch.await(1,TimeUnit.SECONDS),is(true));
assertThat("closeStatusCode",socket.closeStatusCode,is(StatusCode.SERVER_ERROR));
// Validate errors
assertThat("socket.onErrors",socket.errors.size(),is(1));
Throwable cause = socket.errors.pop();
assertThat("Error type",cause,instanceOf(ArrayIndexOutOfBoundsException.class));
}
}
}
}