417134 - WebSocket / Jsr ServerEndpointConfig.Configurator.getNegotiatedExtensions() is never used
+ Extensions pre-negotiated via ServerEndpointConfig.Configurator.getNegotiatedExtensions are not properly handled. + Added JsrBrowserDebugTool (Jsr version of BrowserDebugTool)
This commit is contained in:
parent
5445c42ffe
commit
7f85842666
|
@ -24,7 +24,6 @@ import javax.websocket.server.ServerEndpoint;
|
||||||
|
|
||||||
import org.eclipse.jetty.server.Server;
|
import org.eclipse.jetty.server.Server;
|
||||||
import org.eclipse.jetty.servlet.ServletContextHandler;
|
import org.eclipse.jetty.servlet.ServletContextHandler;
|
||||||
import org.eclipse.jetty.servlet.ServletHolder;
|
|
||||||
import org.eclipse.jetty.websocket.jsr356.server.ServerContainer;
|
import org.eclipse.jetty.websocket.jsr356.server.ServerContainer;
|
||||||
import org.eclipse.jetty.websocket.jsr356.server.WebSocketConfiguration;
|
import org.eclipse.jetty.websocket.jsr356.server.WebSocketConfiguration;
|
||||||
|
|
||||||
|
|
|
@ -25,6 +25,7 @@ import java.util.Map;
|
||||||
import javax.websocket.Extension;
|
import javax.websocket.Extension;
|
||||||
|
|
||||||
import org.eclipse.jetty.websocket.api.extensions.ExtensionConfig;
|
import org.eclipse.jetty.websocket.api.extensions.ExtensionConfig;
|
||||||
|
import org.eclipse.jetty.websocket.api.util.QuoteUtil;
|
||||||
|
|
||||||
public class JsrExtension implements Extension
|
public class JsrExtension implements Extension
|
||||||
{
|
{
|
||||||
|
@ -89,4 +90,23 @@ public class JsrExtension implements Extension
|
||||||
{
|
{
|
||||||
return parameters;
|
return parameters;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString()
|
||||||
|
{
|
||||||
|
StringBuilder str = new StringBuilder();
|
||||||
|
str.append(name);
|
||||||
|
for (Parameter param : parameters)
|
||||||
|
{
|
||||||
|
str.append(';');
|
||||||
|
str.append(param.getName());
|
||||||
|
String value = param.getValue();
|
||||||
|
if (value != null)
|
||||||
|
{
|
||||||
|
str.append('=');
|
||||||
|
QuoteUtil.quoteIfNeeded(str,value,";=");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return str.toString();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -57,8 +57,7 @@ public class BasicServerEndpointConfigurator extends Configurator
|
||||||
@Override
|
@Override
|
||||||
public List<Extension> getNegotiatedExtensions(List<Extension> installed, List<Extension> requested)
|
public List<Extension> getNegotiatedExtensions(List<Extension> installed, List<Extension> requested)
|
||||||
{
|
{
|
||||||
/* do nothing */
|
return requested;
|
||||||
return null;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
|
@ -19,12 +19,18 @@
|
||||||
package org.eclipse.jetty.websocket.jsr356.server;
|
package org.eclipse.jetty.websocket.jsr356.server;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
|
import javax.websocket.Extension;
|
||||||
|
import javax.websocket.Extension.Parameter;
|
||||||
import javax.websocket.server.ServerEndpointConfig;
|
import javax.websocket.server.ServerEndpointConfig;
|
||||||
|
|
||||||
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.extensions.ExtensionConfig;
|
||||||
|
import org.eclipse.jetty.websocket.api.extensions.ExtensionFactory;
|
||||||
|
import org.eclipse.jetty.websocket.jsr356.JsrExtension;
|
||||||
import org.eclipse.jetty.websocket.jsr356.endpoints.EndpointInstance;
|
import org.eclipse.jetty.websocket.jsr356.endpoints.EndpointInstance;
|
||||||
import org.eclipse.jetty.websocket.jsr356.server.pathmap.WebSocketPathSpec;
|
import org.eclipse.jetty.websocket.jsr356.server.pathmap.WebSocketPathSpec;
|
||||||
import org.eclipse.jetty.websocket.server.pathmap.PathSpec;
|
import org.eclipse.jetty.websocket.server.pathmap.PathSpec;
|
||||||
|
@ -36,10 +42,12 @@ public class JsrCreator implements WebSocketCreator
|
||||||
{
|
{
|
||||||
private static final Logger LOG = Log.getLogger(JsrCreator.class);
|
private static final Logger LOG = Log.getLogger(JsrCreator.class);
|
||||||
private final ServerEndpointMetadata metadata;
|
private final ServerEndpointMetadata metadata;
|
||||||
|
private final ExtensionFactory extensionFactory;
|
||||||
|
|
||||||
public JsrCreator(ServerEndpointMetadata metadata)
|
public JsrCreator(ServerEndpointMetadata metadata, ExtensionFactory extensionFactory)
|
||||||
{
|
{
|
||||||
this.metadata = metadata;
|
this.metadata = metadata;
|
||||||
|
this.extensionFactory = extensionFactory;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -78,6 +86,33 @@ public class JsrCreator implements WebSocketCreator
|
||||||
resp.setAcceptedSubProtocol(subprotocol);
|
resp.setAcceptedSubProtocol(subprotocol);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// deal with extensions
|
||||||
|
List<Extension> installedExts = new ArrayList<>();
|
||||||
|
for (String extName : extensionFactory.getAvailableExtensions().keySet())
|
||||||
|
{
|
||||||
|
installedExts.add(new JsrExtension(extName));
|
||||||
|
}
|
||||||
|
List<Extension> requestedExts = new ArrayList<>();
|
||||||
|
for (ExtensionConfig reqCfg : req.getExtensions())
|
||||||
|
{
|
||||||
|
requestedExts.add(new JsrExtension(reqCfg));
|
||||||
|
}
|
||||||
|
List<Extension> usedExts = configurator.getNegotiatedExtensions(installedExts,requestedExts);
|
||||||
|
List<ExtensionConfig> configs = new ArrayList<>();
|
||||||
|
if (usedExts != null)
|
||||||
|
{
|
||||||
|
for (Extension used : usedExts)
|
||||||
|
{
|
||||||
|
ExtensionConfig ecfg = new ExtensionConfig(used.getName());
|
||||||
|
for (Parameter param : used.getParameters())
|
||||||
|
{
|
||||||
|
ecfg.setParameter(param.getName(),param.getValue());
|
||||||
|
}
|
||||||
|
configs.add(ecfg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
resp.setExtensions(configs);
|
||||||
|
|
||||||
// create endpoint class
|
// create endpoint class
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
|
|
|
@ -80,7 +80,7 @@ public class ServerContainer extends ClientContainer implements javax.websocket.
|
||||||
|
|
||||||
public void addEndpoint(ServerEndpointMetadata metadata) throws DeploymentException
|
public void addEndpoint(ServerEndpointMetadata metadata) throws DeploymentException
|
||||||
{
|
{
|
||||||
JsrCreator creator = new JsrCreator(metadata);
|
JsrCreator creator = new JsrCreator(metadata,webSocketServerFactory.getExtensionFactory());
|
||||||
mappedCreator.addMapping(new WebSocketPathSpec(metadata.getPath()),creator);
|
mappedCreator.addMapping(new WebSocketPathSpec(metadata.getPath()),creator);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,217 @@
|
||||||
|
//
|
||||||
|
// ========================================================================
|
||||||
|
// Copyright (c) 1995-2013 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.server;
|
||||||
|
|
||||||
|
import static org.hamcrest.Matchers.*;
|
||||||
|
|
||||||
|
import java.net.URI;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.concurrent.TimeUnit;
|
||||||
|
|
||||||
|
import javax.websocket.Extension;
|
||||||
|
import javax.websocket.HandshakeResponse;
|
||||||
|
import javax.websocket.OnMessage;
|
||||||
|
import javax.websocket.Session;
|
||||||
|
import javax.websocket.server.HandshakeRequest;
|
||||||
|
import javax.websocket.server.ServerEndpoint;
|
||||||
|
import javax.websocket.server.ServerEndpointConfig;
|
||||||
|
|
||||||
|
import org.eclipse.jetty.server.Server;
|
||||||
|
import org.eclipse.jetty.server.ServerConnector;
|
||||||
|
import org.eclipse.jetty.servlet.ServletContextHandler;
|
||||||
|
import org.eclipse.jetty.util.log.Log;
|
||||||
|
import org.eclipse.jetty.util.log.Logger;
|
||||||
|
import org.eclipse.jetty.websocket.api.util.QuoteUtil;
|
||||||
|
import org.eclipse.jetty.websocket.common.WebSocketFrame;
|
||||||
|
import org.eclipse.jetty.websocket.common.frames.TextFrame;
|
||||||
|
import org.eclipse.jetty.websocket.jsr356.server.blockhead.BlockheadClient;
|
||||||
|
import org.eclipse.jetty.websocket.jsr356.server.blockhead.HttpResponse;
|
||||||
|
import org.eclipse.jetty.websocket.jsr356.server.blockhead.IncomingFramesCapture;
|
||||||
|
import org.junit.AfterClass;
|
||||||
|
import org.junit.Assert;
|
||||||
|
import org.junit.BeforeClass;
|
||||||
|
import org.junit.Test;
|
||||||
|
|
||||||
|
public class ConfiguratorTest
|
||||||
|
{
|
||||||
|
private static final Logger LOG = Log.getLogger(ConfiguratorTest.class);
|
||||||
|
|
||||||
|
public static class EmptyConfigurator extends ServerEndpointConfig.Configurator
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
@ServerEndpoint(value = "/empty", configurator = EmptyConfigurator.class)
|
||||||
|
public static class EmptySocket
|
||||||
|
{
|
||||||
|
@OnMessage
|
||||||
|
public String echo(String message)
|
||||||
|
{
|
||||||
|
return message;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class NoExtensionsConfigurator extends ServerEndpointConfig.Configurator
|
||||||
|
{
|
||||||
|
@Override
|
||||||
|
public List<Extension> getNegotiatedExtensions(List<Extension> installed, List<Extension> requested)
|
||||||
|
{
|
||||||
|
return Collections.emptyList();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@ServerEndpoint(value = "/no-extensions", configurator = NoExtensionsConfigurator.class)
|
||||||
|
public static class NoExtensionsSocket
|
||||||
|
{
|
||||||
|
@OnMessage
|
||||||
|
public String echo(String message)
|
||||||
|
{
|
||||||
|
return message;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class CaptureHeadersConfigurator extends ServerEndpointConfig.Configurator
|
||||||
|
{
|
||||||
|
@Override
|
||||||
|
public void modifyHandshake(ServerEndpointConfig sec, HandshakeRequest request, HandshakeResponse response)
|
||||||
|
{
|
||||||
|
super.modifyHandshake(sec,request,response);
|
||||||
|
sec.getUserProperties().put("request-headers",request.getHeaders());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@ServerEndpoint(value = "/capture-request-headers", configurator = CaptureHeadersConfigurator.class)
|
||||||
|
public static class CaptureHeadersSocket
|
||||||
|
{
|
||||||
|
@OnMessage
|
||||||
|
public String getHeaders(Session session, String headerKey)
|
||||||
|
{
|
||||||
|
StringBuilder response = new StringBuilder();
|
||||||
|
|
||||||
|
response.append("Request Header [").append(headerKey).append("]: ");
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
Map<String, List<String>> headers = (Map<String, List<String>>)session.getUserProperties().get("request-headers");
|
||||||
|
if (headers == null)
|
||||||
|
{
|
||||||
|
response.append("<no headers found in session.getUserProperties()>");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
List<String> values = headers.get(headerKey);
|
||||||
|
if (values == null)
|
||||||
|
{
|
||||||
|
response.append("<header not found>");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
response.append(QuoteUtil.join(values,","));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return response.toString();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Server server;
|
||||||
|
private static URI baseServerUri;
|
||||||
|
|
||||||
|
@BeforeClass
|
||||||
|
public static void startServer() throws Exception
|
||||||
|
{
|
||||||
|
server = new Server();
|
||||||
|
ServerConnector connector = new ServerConnector(server);
|
||||||
|
connector.setPort(0);
|
||||||
|
server.addConnector(connector);
|
||||||
|
|
||||||
|
ServletContextHandler context = new ServletContextHandler(ServletContextHandler.SESSIONS);
|
||||||
|
context.setContextPath("/");
|
||||||
|
server.setHandler(context);
|
||||||
|
|
||||||
|
ServerContainer container = WebSocketConfiguration.configureContext(context);
|
||||||
|
container.addEndpoint(CaptureHeadersSocket.class);
|
||||||
|
container.addEndpoint(EmptySocket.class);
|
||||||
|
container.addEndpoint(NoExtensionsSocket.class);
|
||||||
|
|
||||||
|
server.start();
|
||||||
|
String host = connector.getHost();
|
||||||
|
if (host == null)
|
||||||
|
{
|
||||||
|
host = "localhost";
|
||||||
|
}
|
||||||
|
int port = connector.getLocalPort();
|
||||||
|
baseServerUri = new URI(String.format("ws://%s:%d/",host,port));
|
||||||
|
LOG.debug("Server started on {}",baseServerUri);
|
||||||
|
}
|
||||||
|
|
||||||
|
@AfterClass
|
||||||
|
public static void stopServer() throws Exception
|
||||||
|
{
|
||||||
|
server.stop();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testEmptyConfigurator() throws Exception
|
||||||
|
{
|
||||||
|
URI uri = baseServerUri.resolve("/empty");
|
||||||
|
|
||||||
|
try (BlockheadClient client = new BlockheadClient(uri))
|
||||||
|
{
|
||||||
|
client.addExtensions("identity");
|
||||||
|
client.connect();
|
||||||
|
client.sendStandardRequest();
|
||||||
|
HttpResponse response = client.readResponseHeader();
|
||||||
|
Assert.assertThat("response.extensions",response.getExtensionsHeader(),is("identity"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testNoExtensionsConfigurator() throws Exception
|
||||||
|
{
|
||||||
|
URI uri = baseServerUri.resolve("/no-extensions");
|
||||||
|
|
||||||
|
try (BlockheadClient client = new BlockheadClient(uri))
|
||||||
|
{
|
||||||
|
client.addExtensions("identity");
|
||||||
|
client.connect();
|
||||||
|
client.sendStandardRequest();
|
||||||
|
HttpResponse response = client.readResponseHeader();
|
||||||
|
Assert.assertThat("response.extensions",response.getExtensionsHeader(),nullValue());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testCaptureRequestHeadersConfigurator() throws Exception
|
||||||
|
{
|
||||||
|
URI uri = baseServerUri.resolve("/capture-request-headers");
|
||||||
|
|
||||||
|
try (BlockheadClient client = new BlockheadClient(uri))
|
||||||
|
{
|
||||||
|
client.addHeader("X-Dummy: Bogus\r\n");
|
||||||
|
client.connect();
|
||||||
|
client.sendStandardRequest();
|
||||||
|
client.expectUpgradeResponse();
|
||||||
|
|
||||||
|
client.write(new TextFrame().setPayload("X-Dummy"));
|
||||||
|
IncomingFramesCapture capture = client.readFrames(1,TimeUnit.SECONDS,1);
|
||||||
|
WebSocketFrame frame = capture.getFrames().poll();
|
||||||
|
Assert.assertThat("Frame Response", frame.getPayloadAsUTF8(), is("Request Header [X-Dummy]: \"Bogus\""));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -160,6 +160,7 @@ public class WSServer
|
||||||
{
|
{
|
||||||
server = new Server();
|
server = new Server();
|
||||||
ServerConnector connector = new ServerConnector(server);
|
ServerConnector connector = new ServerConnector(server);
|
||||||
|
connector.setPort(0);
|
||||||
server.addConnector(connector);
|
server.addConnector(connector);
|
||||||
|
|
||||||
HandlerCollection handlers = new HandlerCollection();
|
HandlerCollection handlers = new HandlerCollection();
|
||||||
|
|
|
@ -0,0 +1,744 @@
|
||||||
|
//
|
||||||
|
// ========================================================================
|
||||||
|
// Copyright (c) 1995-2013 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.server.blockhead;
|
||||||
|
|
||||||
|
import static org.hamcrest.Matchers.*;
|
||||||
|
|
||||||
|
import java.io.Closeable;
|
||||||
|
import java.io.EOFException;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.io.OutputStream;
|
||||||
|
import java.net.HttpURLConnection;
|
||||||
|
import java.net.InetAddress;
|
||||||
|
import java.net.InetSocketAddress;
|
||||||
|
import java.net.Socket;
|
||||||
|
import java.net.SocketAddress;
|
||||||
|
import java.net.SocketTimeoutException;
|
||||||
|
import java.net.URI;
|
||||||
|
import java.net.URISyntaxException;
|
||||||
|
import java.nio.ByteBuffer;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.concurrent.CountDownLatch;
|
||||||
|
import java.util.concurrent.TimeUnit;
|
||||||
|
import java.util.concurrent.TimeoutException;
|
||||||
|
import java.util.concurrent.atomic.AtomicInteger;
|
||||||
|
|
||||||
|
import javax.net.ssl.HttpsURLConnection;
|
||||||
|
|
||||||
|
import org.eclipse.jetty.io.ByteBufferPool;
|
||||||
|
import org.eclipse.jetty.io.MappedByteBufferPool;
|
||||||
|
import org.eclipse.jetty.util.BufferUtil;
|
||||||
|
import org.eclipse.jetty.util.IO;
|
||||||
|
import org.eclipse.jetty.util.StringUtil;
|
||||||
|
import org.eclipse.jetty.util.log.Log;
|
||||||
|
import org.eclipse.jetty.util.log.Logger;
|
||||||
|
import org.eclipse.jetty.websocket.api.WebSocketPolicy;
|
||||||
|
import org.eclipse.jetty.websocket.api.WriteCallback;
|
||||||
|
import org.eclipse.jetty.websocket.api.extensions.ExtensionConfig;
|
||||||
|
import org.eclipse.jetty.websocket.api.extensions.Frame;
|
||||||
|
import org.eclipse.jetty.websocket.api.extensions.IncomingFrames;
|
||||||
|
import org.eclipse.jetty.websocket.api.extensions.OutgoingFrames;
|
||||||
|
import org.eclipse.jetty.websocket.api.util.WSURI;
|
||||||
|
import org.eclipse.jetty.websocket.common.AcceptHash;
|
||||||
|
import org.eclipse.jetty.websocket.common.CloseInfo;
|
||||||
|
import org.eclipse.jetty.websocket.common.ConnectionState;
|
||||||
|
import org.eclipse.jetty.websocket.common.Generator;
|
||||||
|
import org.eclipse.jetty.websocket.common.OpCode;
|
||||||
|
import org.eclipse.jetty.websocket.common.Parser;
|
||||||
|
import org.eclipse.jetty.websocket.common.WebSocketFrame;
|
||||||
|
import org.eclipse.jetty.websocket.common.extensions.ExtensionStack;
|
||||||
|
import org.eclipse.jetty.websocket.common.extensions.WebSocketExtensionFactory;
|
||||||
|
import org.eclipse.jetty.websocket.common.io.IOState;
|
||||||
|
import org.eclipse.jetty.websocket.common.io.IOState.ConnectionStateListener;
|
||||||
|
import org.eclipse.jetty.websocket.common.io.http.HttpResponseHeaderParser;
|
||||||
|
import org.junit.Assert;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A simple websocket client for performing unit tests with.
|
||||||
|
* <p>
|
||||||
|
* This client will use {@link HttpURLConnection} and {@link HttpsURLConnection} with standard blocking calls to perform websocket requests.
|
||||||
|
* <p>
|
||||||
|
* This client is <u>NOT</u> intended to be performant or follow the websocket spec religiously. In fact, being able to deviate from the websocket spec at will
|
||||||
|
* is desired for this client to operate properly for the unit testing within this module.
|
||||||
|
* <p>
|
||||||
|
* The BlockheadClient should never validate frames or bytes being sent for validity, against any sort of spec, or even sanity. It should, however be honest
|
||||||
|
* with regards to basic IO behavior, a write should work as expected, a read should work as expected, but <u>what</u> byte it sends or reads is not within its
|
||||||
|
* scope.
|
||||||
|
*/
|
||||||
|
public class BlockheadClient implements IncomingFrames, OutgoingFrames, ConnectionStateListener, Closeable
|
||||||
|
{
|
||||||
|
private static final String REQUEST_HASH_KEY = "dGhlIHNhbXBsZSBub25jZQ==";
|
||||||
|
private static final int BUFFER_SIZE = 8192;
|
||||||
|
private static final Logger LOG = Log.getLogger(BlockheadClient.class);
|
||||||
|
/** Set to true to disable timeouts (for debugging reasons) */
|
||||||
|
private boolean debug = false;
|
||||||
|
private final URI destHttpURI;
|
||||||
|
private final URI destWebsocketURI;
|
||||||
|
private final ByteBufferPool bufferPool;
|
||||||
|
private final Generator generator;
|
||||||
|
private final Parser parser;
|
||||||
|
private final IncomingFramesCapture incomingFrames;
|
||||||
|
private final WebSocketExtensionFactory extensionFactory;
|
||||||
|
|
||||||
|
private Socket socket;
|
||||||
|
private OutputStream out;
|
||||||
|
private InputStream in;
|
||||||
|
private int version = 13; // default to RFC-6455
|
||||||
|
private String protocols;
|
||||||
|
private List<String> extensions = new ArrayList<>();
|
||||||
|
private List<String> headers = new ArrayList<>();
|
||||||
|
private byte[] clientmask = new byte[]
|
||||||
|
{ (byte)0xFF, (byte)0xFF, (byte)0xFF, (byte)0xFF };
|
||||||
|
private int timeout = 1000;
|
||||||
|
private AtomicInteger parseCount;
|
||||||
|
private OutgoingFrames outgoing = this;
|
||||||
|
private boolean eof = false;
|
||||||
|
private ExtensionStack extensionStack;
|
||||||
|
private IOState ioState;
|
||||||
|
private CountDownLatch disconnectedLatch = new CountDownLatch(1);
|
||||||
|
private ByteBuffer remainingBuffer;
|
||||||
|
private String connectionValue = "Upgrade";
|
||||||
|
|
||||||
|
public BlockheadClient(URI destWebsocketURI) throws URISyntaxException
|
||||||
|
{
|
||||||
|
this(WebSocketPolicy.newClientPolicy(),destWebsocketURI);
|
||||||
|
}
|
||||||
|
|
||||||
|
public BlockheadClient(WebSocketPolicy policy, URI destWebsocketURI) throws URISyntaxException
|
||||||
|
{
|
||||||
|
Assert.assertThat("Websocket URI scheme",destWebsocketURI.getScheme(),anyOf(is("ws"),is("wss")));
|
||||||
|
this.destWebsocketURI = destWebsocketURI;
|
||||||
|
if (destWebsocketURI.getScheme().equals("wss"))
|
||||||
|
{
|
||||||
|
throw new RuntimeException("Sorry, BlockheadClient does not support SSL");
|
||||||
|
}
|
||||||
|
this.destHttpURI = WSURI.toHttp(destWebsocketURI);
|
||||||
|
|
||||||
|
LOG.debug("WebSocket URI: {}",destWebsocketURI);
|
||||||
|
LOG.debug(" HTTP URI: {}",destHttpURI);
|
||||||
|
|
||||||
|
this.bufferPool = new MappedByteBufferPool(8192);
|
||||||
|
this.generator = new Generator(policy,bufferPool);
|
||||||
|
this.parser = new Parser(policy,bufferPool);
|
||||||
|
this.parseCount = new AtomicInteger(0);
|
||||||
|
|
||||||
|
this.incomingFrames = new IncomingFramesCapture();
|
||||||
|
|
||||||
|
this.extensionFactory = new WebSocketExtensionFactory(policy,bufferPool);
|
||||||
|
this.ioState = new IOState();
|
||||||
|
this.ioState.addListener(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void addExtensions(String xtension)
|
||||||
|
{
|
||||||
|
this.extensions.add(xtension);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void addHeader(String header)
|
||||||
|
{
|
||||||
|
this.headers.add(header);
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean awaitDisconnect(long timeout, TimeUnit unit) throws InterruptedException
|
||||||
|
{
|
||||||
|
return disconnectedLatch.await(timeout,unit);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void clearCaptured()
|
||||||
|
{
|
||||||
|
this.incomingFrames.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void clearExtensions()
|
||||||
|
{
|
||||||
|
extensions.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void close()
|
||||||
|
{
|
||||||
|
LOG.debug("close()");
|
||||||
|
close(-1,null);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void close(int statusCode, String message)
|
||||||
|
{
|
||||||
|
CloseInfo close = new CloseInfo(statusCode,message);
|
||||||
|
|
||||||
|
ioState.onCloseLocal(close);
|
||||||
|
|
||||||
|
if (!ioState.isClosed())
|
||||||
|
{
|
||||||
|
WebSocketFrame frame = close.asFrame();
|
||||||
|
LOG.debug("Issuing: {}",frame);
|
||||||
|
try
|
||||||
|
{
|
||||||
|
write(frame);
|
||||||
|
}
|
||||||
|
catch (IOException e)
|
||||||
|
{
|
||||||
|
LOG.debug(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void connect() throws IOException
|
||||||
|
{
|
||||||
|
InetAddress destAddr = InetAddress.getByName(destHttpURI.getHost());
|
||||||
|
int port = destHttpURI.getPort();
|
||||||
|
|
||||||
|
SocketAddress endpoint = new InetSocketAddress(destAddr,port);
|
||||||
|
|
||||||
|
socket = new Socket();
|
||||||
|
socket.setSoTimeout(timeout);
|
||||||
|
socket.connect(endpoint);
|
||||||
|
|
||||||
|
out = socket.getOutputStream();
|
||||||
|
in = socket.getInputStream();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void disconnect()
|
||||||
|
{
|
||||||
|
LOG.debug("disconnect");
|
||||||
|
IO.close(in);
|
||||||
|
IO.close(out);
|
||||||
|
disconnectedLatch.countDown();
|
||||||
|
if (socket != null)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
socket.close();
|
||||||
|
}
|
||||||
|
catch (IOException ignore)
|
||||||
|
{
|
||||||
|
/* ignore */
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void expectServerDisconnect()
|
||||||
|
{
|
||||||
|
if (eof)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
int len = in.read();
|
||||||
|
if (len == (-1))
|
||||||
|
{
|
||||||
|
// we are disconnected
|
||||||
|
eof = true;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Assert.assertThat("Expecting no data and proper socket disconnect (issued from server)",len,is(-1));
|
||||||
|
}
|
||||||
|
catch (SocketTimeoutException e)
|
||||||
|
{
|
||||||
|
LOG.warn(e);
|
||||||
|
Assert.fail("Expected a server initiated disconnect, instead the read timed out");
|
||||||
|
}
|
||||||
|
catch (IOException e)
|
||||||
|
{
|
||||||
|
// acceptable path
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public HttpResponse expectUpgradeResponse() throws IOException
|
||||||
|
{
|
||||||
|
HttpResponse response = readResponseHeader();
|
||||||
|
|
||||||
|
if (LOG.isDebugEnabled())
|
||||||
|
{
|
||||||
|
LOG.debug("Response Header: {}{}",'\n',response);
|
||||||
|
}
|
||||||
|
|
||||||
|
Assert.assertThat("Response Status Code",response.getStatusCode(),is(101));
|
||||||
|
Assert.assertThat("Response Status Reason",response.getStatusReason(),is("Switching Protocols"));
|
||||||
|
Assert.assertThat("Response Header[Upgrade]",response.getHeader("Upgrade"),is("WebSocket"));
|
||||||
|
Assert.assertThat("Response Header[Connection]",response.getHeader("Connection"),is("Upgrade"));
|
||||||
|
|
||||||
|
// Validate the Sec-WebSocket-Accept
|
||||||
|
String acceptKey = response.getHeader("Sec-WebSocket-Accept");
|
||||||
|
Assert.assertThat("Response Header[Sec-WebSocket-Accept Exists]",acceptKey,notNullValue());
|
||||||
|
|
||||||
|
String reqKey = REQUEST_HASH_KEY;
|
||||||
|
String expectedHash = AcceptHash.hashKey(reqKey);
|
||||||
|
|
||||||
|
Assert.assertThat("Valid Sec-WebSocket-Accept Hash?",acceptKey,is(expectedHash));
|
||||||
|
|
||||||
|
// collect extensions configured in response header
|
||||||
|
List<ExtensionConfig> configs = getExtensionConfigs(response);
|
||||||
|
extensionStack = new ExtensionStack(this.extensionFactory);
|
||||||
|
extensionStack.negotiate(configs);
|
||||||
|
|
||||||
|
// Start with default routing
|
||||||
|
extensionStack.setNextIncoming(this); // the websocket layer
|
||||||
|
extensionStack.setNextOutgoing(outgoing); // the network layer
|
||||||
|
|
||||||
|
// Configure Parser / Generator
|
||||||
|
extensionStack.configure(parser);
|
||||||
|
extensionStack.configure(generator);
|
||||||
|
|
||||||
|
// Start Stack
|
||||||
|
try
|
||||||
|
{
|
||||||
|
extensionStack.start();
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
throw new IOException("Unable to start Extension Stack");
|
||||||
|
}
|
||||||
|
|
||||||
|
// configure parser
|
||||||
|
parser.setIncomingFramesHandler(extensionStack);
|
||||||
|
ioState.onOpened();
|
||||||
|
|
||||||
|
LOG.debug("outgoing = {}",outgoing);
|
||||||
|
LOG.debug("incoming = {}",extensionStack);
|
||||||
|
|
||||||
|
return response;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void flush() throws IOException
|
||||||
|
{
|
||||||
|
out.flush();
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getConnectionValue()
|
||||||
|
{
|
||||||
|
return connectionValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
private List<ExtensionConfig> getExtensionConfigs(HttpResponse response)
|
||||||
|
{
|
||||||
|
List<ExtensionConfig> configs = new ArrayList<>();
|
||||||
|
|
||||||
|
String econf = response.getHeader("Sec-WebSocket-Extensions");
|
||||||
|
if (econf != null)
|
||||||
|
{
|
||||||
|
LOG.debug("Found Extension Response: {}",econf);
|
||||||
|
ExtensionConfig config = ExtensionConfig.parse(econf);
|
||||||
|
configs.add(config);
|
||||||
|
}
|
||||||
|
return configs;
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<String> getExtensions()
|
||||||
|
{
|
||||||
|
return extensions;
|
||||||
|
}
|
||||||
|
|
||||||
|
public URI getHttpURI()
|
||||||
|
{
|
||||||
|
return destHttpURI;
|
||||||
|
}
|
||||||
|
|
||||||
|
public IOState getIOState()
|
||||||
|
{
|
||||||
|
return ioState;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getProtocols()
|
||||||
|
{
|
||||||
|
return protocols;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getRequestHost()
|
||||||
|
{
|
||||||
|
if (destHttpURI.getPort() > 0)
|
||||||
|
{
|
||||||
|
return String.format("%s:%d",destHttpURI.getHost(),destHttpURI.getPort());
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return destHttpURI.getHost();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getRequestPath()
|
||||||
|
{
|
||||||
|
StringBuilder path = new StringBuilder();
|
||||||
|
path.append(destHttpURI.getPath());
|
||||||
|
if (StringUtil.isNotBlank(destHttpURI.getQuery()))
|
||||||
|
{
|
||||||
|
path.append('?').append(destHttpURI.getQuery());
|
||||||
|
}
|
||||||
|
return path.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getRequestWebSocketKey()
|
||||||
|
{
|
||||||
|
return REQUEST_HASH_KEY;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getRequestWebSocketOrigin()
|
||||||
|
{
|
||||||
|
return destWebsocketURI.toASCIIString();
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getVersion()
|
||||||
|
{
|
||||||
|
return version;
|
||||||
|
}
|
||||||
|
|
||||||
|
public URI getWebsocketURI()
|
||||||
|
{
|
||||||
|
return destWebsocketURI;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Errors received (after extensions)
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public void incomingError(Throwable e)
|
||||||
|
{
|
||||||
|
incomingFrames.incomingError(e);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Frames received (after extensions)
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public void incomingFrame(Frame frame)
|
||||||
|
{
|
||||||
|
LOG.debug("incoming({})",frame);
|
||||||
|
int count = parseCount.incrementAndGet();
|
||||||
|
if ((count % 10) == 0)
|
||||||
|
{
|
||||||
|
LOG.info("Client parsed {} frames",count);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (frame.getOpCode() == OpCode.CLOSE)
|
||||||
|
{
|
||||||
|
CloseInfo close = new CloseInfo(frame);
|
||||||
|
ioState.onCloseRemote(close);
|
||||||
|
}
|
||||||
|
|
||||||
|
WebSocketFrame copy = WebSocketFrame.copy(frame);
|
||||||
|
incomingFrames.incomingFrame(copy);
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isConnected()
|
||||||
|
{
|
||||||
|
return (socket != null) && (socket.isConnected());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onConnectionStateChange(ConnectionState state)
|
||||||
|
{
|
||||||
|
switch (state)
|
||||||
|
{
|
||||||
|
case CLOSED:
|
||||||
|
// Per Spec, client should not initiate disconnect on its own
|
||||||
|
// this.disconnect();
|
||||||
|
break;
|
||||||
|
case CLOSING:
|
||||||
|
if (ioState.wasRemoteCloseInitiated())
|
||||||
|
{
|
||||||
|
CloseInfo close = ioState.getCloseInfo();
|
||||||
|
close(close.getStatusCode(),close.getReason());
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
/* do nothing */
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void outgoingFrame(Frame frame, WriteCallback callback)
|
||||||
|
{
|
||||||
|
ByteBuffer headerBuf = generator.generateHeaderBytes(frame);
|
||||||
|
if (LOG.isDebugEnabled())
|
||||||
|
{
|
||||||
|
LOG.debug("writing out: {}",BufferUtil.toDetailString(headerBuf));
|
||||||
|
}
|
||||||
|
try
|
||||||
|
{
|
||||||
|
BufferUtil.writeTo(headerBuf,out);
|
||||||
|
BufferUtil.writeTo(frame.getPayload(),out);
|
||||||
|
out.flush();
|
||||||
|
if (callback != null)
|
||||||
|
{
|
||||||
|
callback.writeSuccess();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (IOException e)
|
||||||
|
{
|
||||||
|
if (callback != null)
|
||||||
|
{
|
||||||
|
callback.writeFailed(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
bufferPool.release(headerBuf);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (frame.getOpCode() == OpCode.CLOSE)
|
||||||
|
{
|
||||||
|
disconnect();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public int read(ByteBuffer buf) throws IOException
|
||||||
|
{
|
||||||
|
if (eof)
|
||||||
|
{
|
||||||
|
throw new EOFException("Hit EOF");
|
||||||
|
}
|
||||||
|
|
||||||
|
if ((remainingBuffer != null) && (remainingBuffer.remaining() > 0))
|
||||||
|
{
|
||||||
|
return BufferUtil.put(remainingBuffer,buf);
|
||||||
|
}
|
||||||
|
|
||||||
|
int len = -1;
|
||||||
|
int b;
|
||||||
|
while ((in.available() > 0) && (buf.remaining() > 0))
|
||||||
|
{
|
||||||
|
b = in.read();
|
||||||
|
if (b == (-1))
|
||||||
|
{
|
||||||
|
eof = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
buf.put((byte)b);
|
||||||
|
len++;
|
||||||
|
}
|
||||||
|
|
||||||
|
return len;
|
||||||
|
}
|
||||||
|
|
||||||
|
public IncomingFramesCapture readFrames(int expectedCount, TimeUnit timeoutUnit, int timeoutDuration) throws IOException, TimeoutException
|
||||||
|
{
|
||||||
|
LOG.debug("Read: waiting for {} frame(s) from server",expectedCount);
|
||||||
|
|
||||||
|
ByteBuffer buf = bufferPool.acquire(BUFFER_SIZE,false);
|
||||||
|
BufferUtil.clearToFill(buf);
|
||||||
|
try
|
||||||
|
{
|
||||||
|
long msDur = TimeUnit.MILLISECONDS.convert(timeoutDuration,timeoutUnit);
|
||||||
|
long now = System.currentTimeMillis();
|
||||||
|
long expireOn = now + msDur;
|
||||||
|
LOG.debug("Now: {} - expireOn: {} ({} ms)",now,expireOn,msDur);
|
||||||
|
|
||||||
|
long iter = 0;
|
||||||
|
|
||||||
|
int len = 0;
|
||||||
|
while (incomingFrames.size() < expectedCount)
|
||||||
|
{
|
||||||
|
BufferUtil.clearToFill(buf);
|
||||||
|
len = read(buf);
|
||||||
|
if (len > 0)
|
||||||
|
{
|
||||||
|
BufferUtil.flipToFlush(buf,0);
|
||||||
|
if (LOG.isDebugEnabled())
|
||||||
|
{
|
||||||
|
LOG.debug("Read {} bytes: {}",len,BufferUtil.toDetailString(buf));
|
||||||
|
}
|
||||||
|
parser.parse(buf);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if (LOG.isDebugEnabled())
|
||||||
|
{
|
||||||
|
iter++;
|
||||||
|
if ((iter % 10000000) == 0)
|
||||||
|
{
|
||||||
|
LOG.debug("10,000,000 reads of zero length");
|
||||||
|
iter = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!debug && (System.currentTimeMillis() > expireOn))
|
||||||
|
{
|
||||||
|
incomingFrames.dump();
|
||||||
|
throw new TimeoutException(String.format("Timeout reading all %d expected frames. (managed to only read %d frame(s))",expectedCount,
|
||||||
|
incomingFrames.size()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
bufferPool.release(buf);
|
||||||
|
}
|
||||||
|
|
||||||
|
return incomingFrames;
|
||||||
|
}
|
||||||
|
|
||||||
|
public HttpResponse readResponseHeader() throws IOException
|
||||||
|
{
|
||||||
|
HttpResponse response = new HttpResponse();
|
||||||
|
HttpResponseHeaderParser parser = new HttpResponseHeaderParser(response);
|
||||||
|
|
||||||
|
ByteBuffer buf = BufferUtil.allocate(512);
|
||||||
|
|
||||||
|
do
|
||||||
|
{
|
||||||
|
BufferUtil.flipToFill(buf);
|
||||||
|
read(buf);
|
||||||
|
BufferUtil.flipToFlush(buf,0);
|
||||||
|
}
|
||||||
|
while (parser.parse(buf) == null);
|
||||||
|
|
||||||
|
remainingBuffer = response.getRemainingBuffer();
|
||||||
|
|
||||||
|
return response;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void sendStandardRequest() throws IOException
|
||||||
|
{
|
||||||
|
StringBuilder req = new StringBuilder();
|
||||||
|
req.append("GET ").append(getRequestPath()).append(" HTTP/1.1\r\n");
|
||||||
|
req.append("Host: ").append(getRequestHost()).append("\r\n");
|
||||||
|
req.append("Upgrade: websocket\r\n");
|
||||||
|
req.append("Connection: ").append(connectionValue).append("\r\n");
|
||||||
|
for (String header : headers)
|
||||||
|
{
|
||||||
|
req.append(header);
|
||||||
|
}
|
||||||
|
req.append("Sec-WebSocket-Key: ").append(getRequestWebSocketKey()).append("\r\n");
|
||||||
|
req.append("Sec-WebSocket-Origin: ").append(getRequestWebSocketOrigin()).append("\r\n");
|
||||||
|
if (StringUtil.isNotBlank(protocols))
|
||||||
|
{
|
||||||
|
req.append("Sec-WebSocket-Protocol: ").append(protocols).append("\r\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
for (String xtension : extensions)
|
||||||
|
{
|
||||||
|
req.append("Sec-WebSocket-Extensions: ").append(xtension).append("\r\n");
|
||||||
|
}
|
||||||
|
req.append("Sec-WebSocket-Version: ").append(version).append("\r\n");
|
||||||
|
req.append("\r\n");
|
||||||
|
writeRaw(req.toString());
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setConnectionValue(String connectionValue)
|
||||||
|
{
|
||||||
|
this.connectionValue = connectionValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setDebug(boolean flag)
|
||||||
|
{
|
||||||
|
this.debug = flag;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setProtocols(String protocols)
|
||||||
|
{
|
||||||
|
this.protocols = protocols;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setTimeout(TimeUnit unit, int duration)
|
||||||
|
{
|
||||||
|
this.timeout = (int)TimeUnit.MILLISECONDS.convert(duration,unit);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setVersion(int version)
|
||||||
|
{
|
||||||
|
this.version = version;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void skipTo(String string) throws IOException
|
||||||
|
{
|
||||||
|
int state = 0;
|
||||||
|
|
||||||
|
while (true)
|
||||||
|
{
|
||||||
|
int b = in.read();
|
||||||
|
if (b < 0)
|
||||||
|
{
|
||||||
|
throw new EOFException();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (b == string.charAt(state))
|
||||||
|
{
|
||||||
|
state++;
|
||||||
|
if (state == string.length())
|
||||||
|
{
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
state = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void sleep(TimeUnit unit, int duration) throws InterruptedException
|
||||||
|
{
|
||||||
|
LOG.info("Sleeping for {} {}",duration,unit);
|
||||||
|
unit.sleep(duration);
|
||||||
|
LOG.info("Waking up from sleep");
|
||||||
|
}
|
||||||
|
|
||||||
|
public void write(WebSocketFrame frame) throws IOException
|
||||||
|
{
|
||||||
|
if (!ioState.isOpen())
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
LOG.debug("write(Frame->{}) to {}",frame,outgoing);
|
||||||
|
if (LOG.isDebugEnabled())
|
||||||
|
{
|
||||||
|
frame.setMask(new byte[]
|
||||||
|
{ 0x00, 0x00, 0x00, 0x00 });
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
frame.setMask(clientmask);
|
||||||
|
}
|
||||||
|
extensionStack.outgoingFrame(frame,null);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void writeRaw(ByteBuffer buf) throws IOException
|
||||||
|
{
|
||||||
|
LOG.debug("write(ByteBuffer) {}",BufferUtil.toDetailString(buf));
|
||||||
|
BufferUtil.writeTo(buf,out);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void writeRaw(ByteBuffer buf, int numBytes) throws IOException
|
||||||
|
{
|
||||||
|
int len = Math.min(numBytes,buf.remaining());
|
||||||
|
byte arr[] = new byte[len];
|
||||||
|
buf.get(arr,0,len);
|
||||||
|
out.write(arr);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void writeRaw(String str) throws IOException
|
||||||
|
{
|
||||||
|
LOG.debug("write((String)[{}]){}{})",str.length(),'\n',str);
|
||||||
|
out.write(StringUtil.getBytes(str,StringUtil.__ISO_8859_1));
|
||||||
|
}
|
||||||
|
|
||||||
|
public void writeRawSlowly(ByteBuffer buf, int segmentSize) throws IOException
|
||||||
|
{
|
||||||
|
while (buf.remaining() > 0)
|
||||||
|
{
|
||||||
|
writeRaw(buf,segmentSize);
|
||||||
|
flush();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,71 @@
|
||||||
|
//
|
||||||
|
// ========================================================================
|
||||||
|
// Copyright (c) 1995-2013 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.server.blockhead;
|
||||||
|
|
||||||
|
import static org.hamcrest.Matchers.*;
|
||||||
|
|
||||||
|
import java.net.URI;
|
||||||
|
import java.net.URISyntaxException;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Collection;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import org.junit.Assert;
|
||||||
|
import org.junit.Test;
|
||||||
|
import org.junit.runner.RunWith;
|
||||||
|
import org.junit.runners.Parameterized;
|
||||||
|
import org.junit.runners.Parameterized.Parameters;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gotta test some basic constructors of the BlockheadClient.
|
||||||
|
*/
|
||||||
|
@RunWith(value = Parameterized.class)
|
||||||
|
public class BlockheadClientConstructionTest
|
||||||
|
{
|
||||||
|
@Parameters
|
||||||
|
public static Collection<Object[]> data()
|
||||||
|
{
|
||||||
|
List<Object[]> data = new ArrayList<>();
|
||||||
|
// @formatter:off
|
||||||
|
data.add(new Object[] { "ws://localhost/", "http://localhost/" });
|
||||||
|
data.add(new Object[] { "ws://localhost:8080/", "http://localhost:8080/" });
|
||||||
|
data.add(new Object[] { "ws://webtide.com/", "http://webtide.com/" });
|
||||||
|
data.add(new Object[] { "ws://www.webtide.com/sockets/chat", "http://www.webtide.com/sockets/chat" });
|
||||||
|
// @formatter:on
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
|
||||||
|
private URI expectedWsUri;
|
||||||
|
private URI expectedHttpUri;
|
||||||
|
|
||||||
|
public BlockheadClientConstructionTest(String wsuri, String httpuri)
|
||||||
|
{
|
||||||
|
this.expectedWsUri = URI.create(wsuri);
|
||||||
|
this.expectedHttpUri = URI.create(httpuri);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testURIs() throws URISyntaxException
|
||||||
|
{
|
||||||
|
BlockheadClient client = new BlockheadClient(expectedWsUri);
|
||||||
|
Assert.assertThat("Websocket URI",client.getWebsocketURI(),is(expectedWsUri));
|
||||||
|
Assert.assertThat("Websocket URI",client.getHttpURI(),is(expectedHttpUri));
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,94 @@
|
||||||
|
//
|
||||||
|
// ========================================================================
|
||||||
|
// Copyright (c) 1995-2013 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.server.blockhead;
|
||||||
|
|
||||||
|
import java.nio.ByteBuffer;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.TreeMap;
|
||||||
|
|
||||||
|
import org.eclipse.jetty.websocket.common.io.http.HttpResponseHeaderParseListener;
|
||||||
|
|
||||||
|
public class HttpResponse implements HttpResponseHeaderParseListener
|
||||||
|
{
|
||||||
|
private int statusCode;
|
||||||
|
private String statusReason;
|
||||||
|
private Map<String, String> headers = new TreeMap<>(String.CASE_INSENSITIVE_ORDER);
|
||||||
|
private ByteBuffer remainingBuffer;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void addHeader(String name, String value)
|
||||||
|
{
|
||||||
|
headers.put(name,value);
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getExtensionsHeader()
|
||||||
|
{
|
||||||
|
return getHeader("Sec-WebSocket-Extensions");
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getHeader(String name)
|
||||||
|
{
|
||||||
|
return headers.get(name);
|
||||||
|
}
|
||||||
|
|
||||||
|
public ByteBuffer getRemainingBuffer()
|
||||||
|
{
|
||||||
|
return remainingBuffer;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getStatusCode()
|
||||||
|
{
|
||||||
|
return statusCode;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getStatusReason()
|
||||||
|
{
|
||||||
|
return statusReason;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setRemainingBuffer(ByteBuffer copy)
|
||||||
|
{
|
||||||
|
this.remainingBuffer = copy;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setStatusCode(int code)
|
||||||
|
{
|
||||||
|
this.statusCode = code;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setStatusReason(String reason)
|
||||||
|
{
|
||||||
|
this.statusReason = reason;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString()
|
||||||
|
{
|
||||||
|
StringBuilder str = new StringBuilder();
|
||||||
|
str.append("HTTP/1.1 ").append(statusCode).append(' ').append(statusReason);
|
||||||
|
for (Map.Entry<String, String> entry : headers.entrySet())
|
||||||
|
{
|
||||||
|
str.append('\n').append(entry.getKey()).append(": ").append(entry.getValue());
|
||||||
|
}
|
||||||
|
return str.toString();
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,147 @@
|
||||||
|
//
|
||||||
|
// ========================================================================
|
||||||
|
// Copyright (c) 1995-2013 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.server.blockhead;
|
||||||
|
|
||||||
|
import static org.hamcrest.Matchers.*;
|
||||||
|
|
||||||
|
import java.util.Queue;
|
||||||
|
|
||||||
|
import org.eclipse.jetty.toolchain.test.EventQueue;
|
||||||
|
import org.eclipse.jetty.util.BufferUtil;
|
||||||
|
import org.eclipse.jetty.util.log.Log;
|
||||||
|
import org.eclipse.jetty.util.log.Logger;
|
||||||
|
import org.eclipse.jetty.websocket.api.WebSocketException;
|
||||||
|
import org.eclipse.jetty.websocket.api.extensions.Frame;
|
||||||
|
import org.eclipse.jetty.websocket.api.extensions.IncomingFrames;
|
||||||
|
import org.eclipse.jetty.websocket.common.OpCode;
|
||||||
|
import org.eclipse.jetty.websocket.common.WebSocketFrame;
|
||||||
|
import org.junit.Assert;
|
||||||
|
|
||||||
|
public class IncomingFramesCapture implements IncomingFrames
|
||||||
|
{
|
||||||
|
private static final Logger LOG = Log.getLogger(IncomingFramesCapture.class);
|
||||||
|
private EventQueue<WebSocketFrame> frames = new EventQueue<>();
|
||||||
|
private EventQueue<Throwable> errors = new EventQueue<>();
|
||||||
|
|
||||||
|
public void assertErrorCount(int expectedCount)
|
||||||
|
{
|
||||||
|
Assert.assertThat("Captured error count",errors.size(),is(expectedCount));
|
||||||
|
}
|
||||||
|
|
||||||
|
public void assertFrameCount(int expectedCount)
|
||||||
|
{
|
||||||
|
Assert.assertThat("Captured frame count",frames.size(),is(expectedCount));
|
||||||
|
}
|
||||||
|
|
||||||
|
public void assertHasErrors(Class<? extends WebSocketException> errorType, int expectedCount)
|
||||||
|
{
|
||||||
|
Assert.assertThat(errorType.getSimpleName(),getErrorCount(errorType),is(expectedCount));
|
||||||
|
}
|
||||||
|
|
||||||
|
public void assertHasFrame(byte op)
|
||||||
|
{
|
||||||
|
Assert.assertThat(OpCode.name(op),getFrameCount(op),greaterThanOrEqualTo(1));
|
||||||
|
}
|
||||||
|
|
||||||
|
public void assertHasFrame(byte op, int expectedCount)
|
||||||
|
{
|
||||||
|
Assert.assertThat(OpCode.name(op),getFrameCount(op),is(expectedCount));
|
||||||
|
}
|
||||||
|
|
||||||
|
public void assertHasNoFrames()
|
||||||
|
{
|
||||||
|
Assert.assertThat("Has no frames",frames.size(),is(0));
|
||||||
|
}
|
||||||
|
|
||||||
|
public void assertNoErrors()
|
||||||
|
{
|
||||||
|
Assert.assertThat("Has no errors",errors.size(),is(0));
|
||||||
|
}
|
||||||
|
|
||||||
|
public void clear()
|
||||||
|
{
|
||||||
|
frames.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void dump()
|
||||||
|
{
|
||||||
|
System.err.printf("Captured %d incoming frames%n",frames.size());
|
||||||
|
int i = 0;
|
||||||
|
for (Frame frame : frames)
|
||||||
|
{
|
||||||
|
System.err.printf("[%3d] %s%n",i++,frame);
|
||||||
|
System.err.printf(" payload: %s%n",BufferUtil.toDetailString(frame.getPayload()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getErrorCount(Class<? extends Throwable> errorType)
|
||||||
|
{
|
||||||
|
int count = 0;
|
||||||
|
for (Throwable error : errors)
|
||||||
|
{
|
||||||
|
if (errorType.isInstance(error))
|
||||||
|
{
|
||||||
|
count++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return count;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Queue<Throwable> getErrors()
|
||||||
|
{
|
||||||
|
return errors;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getFrameCount(byte op)
|
||||||
|
{
|
||||||
|
int count = 0;
|
||||||
|
for (WebSocketFrame frame : frames)
|
||||||
|
{
|
||||||
|
if (frame.getOpCode() == op)
|
||||||
|
{
|
||||||
|
count++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return count;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Queue<WebSocketFrame> getFrames()
|
||||||
|
{
|
||||||
|
return frames;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void incomingError(Throwable e)
|
||||||
|
{
|
||||||
|
LOG.debug(e);
|
||||||
|
errors.add(e);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void incomingFrame(Frame frame)
|
||||||
|
{
|
||||||
|
WebSocketFrame copy = WebSocketFrame.copy(frame);
|
||||||
|
frames.add(copy);
|
||||||
|
}
|
||||||
|
|
||||||
|
public int size()
|
||||||
|
{
|
||||||
|
return frames.size();
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,52 @@
|
||||||
|
//
|
||||||
|
// ========================================================================
|
||||||
|
// Copyright (c) 1995-2013 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.server.browser;
|
||||||
|
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import javax.websocket.Extension;
|
||||||
|
import javax.websocket.HandshakeResponse;
|
||||||
|
import javax.websocket.server.HandshakeRequest;
|
||||||
|
import javax.websocket.server.ServerEndpointConfig;
|
||||||
|
|
||||||
|
import org.eclipse.jetty.websocket.api.util.QuoteUtil;
|
||||||
|
|
||||||
|
public class JsrBrowserConfigurator extends ServerEndpointConfig.Configurator
|
||||||
|
{
|
||||||
|
@Override
|
||||||
|
public void modifyHandshake(ServerEndpointConfig sec, HandshakeRequest request, HandshakeResponse response)
|
||||||
|
{
|
||||||
|
super.modifyHandshake(sec,request,response);
|
||||||
|
sec.getUserProperties().put("userAgent",getHeaderValue(request,"User-Agent"));
|
||||||
|
sec.getUserProperties().put("requestedExtensions",getHeaderValue(request,"Sec-WebSocket-Extensions"));
|
||||||
|
}
|
||||||
|
|
||||||
|
private String getHeaderValue(HandshakeRequest request, String key)
|
||||||
|
{
|
||||||
|
List<String> value = request.getHeaders().get("User-Agent");
|
||||||
|
return QuoteUtil.join(value,",");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<Extension> getNegotiatedExtensions(List<Extension> installed, List<Extension> requested)
|
||||||
|
{
|
||||||
|
return Collections.emptyList();
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,97 @@
|
||||||
|
//
|
||||||
|
// ========================================================================
|
||||||
|
// Copyright (c) 1995-2013 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.server.browser;
|
||||||
|
|
||||||
|
import javax.websocket.DeploymentException;
|
||||||
|
|
||||||
|
import org.eclipse.jetty.server.Server;
|
||||||
|
import org.eclipse.jetty.server.ServerConnector;
|
||||||
|
import org.eclipse.jetty.servlet.DefaultServlet;
|
||||||
|
import org.eclipse.jetty.servlet.ServletContextHandler;
|
||||||
|
import org.eclipse.jetty.servlet.ServletHolder;
|
||||||
|
import org.eclipse.jetty.util.log.Log;
|
||||||
|
import org.eclipse.jetty.util.log.Logger;
|
||||||
|
import org.eclipse.jetty.websocket.jsr356.server.ServerContainer;
|
||||||
|
import org.eclipse.jetty.websocket.jsr356.server.WebSocketConfiguration;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tool to help debug JSR based websocket circumstances reported around browsers.
|
||||||
|
* <p>
|
||||||
|
* Provides a server, with a few simple websocket's that can be twiddled from a browser. This helps with setting up breakpoints and whatnot to help debug our
|
||||||
|
* websocket implementation from the context of a browser client.
|
||||||
|
*/
|
||||||
|
public class JsrBrowserDebugTool
|
||||||
|
{
|
||||||
|
private static final Logger LOG = Log.getLogger(JsrBrowserDebugTool.class);
|
||||||
|
|
||||||
|
public static void main(String[] args)
|
||||||
|
{
|
||||||
|
int port = 8080;
|
||||||
|
|
||||||
|
for (int i = 0; i < args.length; i++)
|
||||||
|
{
|
||||||
|
String a = args[i];
|
||||||
|
if ("-p".equals(a) || "--port".equals(a))
|
||||||
|
{
|
||||||
|
port = Integer.parseInt(args[++i]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
JsrBrowserDebugTool tool = new JsrBrowserDebugTool();
|
||||||
|
tool.setupServer(port);
|
||||||
|
tool.runForever();
|
||||||
|
}
|
||||||
|
catch (Throwable t)
|
||||||
|
{
|
||||||
|
LOG.warn(t);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private Server server;
|
||||||
|
|
||||||
|
private void runForever() throws Exception
|
||||||
|
{
|
||||||
|
server.start();
|
||||||
|
server.dumpStdErr();
|
||||||
|
LOG.info("Server available.");
|
||||||
|
server.join();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void setupServer(int port) throws DeploymentException
|
||||||
|
{
|
||||||
|
server = new Server();
|
||||||
|
ServerConnector connector = new ServerConnector(server);
|
||||||
|
connector.setPort(port);
|
||||||
|
server.addConnector(connector);
|
||||||
|
|
||||||
|
ServletContextHandler context = new ServletContextHandler(ServletContextHandler.SESSIONS);
|
||||||
|
context.setContextPath("/");
|
||||||
|
ServletHolder holder = context.addServlet(DefaultServlet.class,"/");
|
||||||
|
holder.setInitParameter("resourceBase","src/test/resources/jsr-browser-debug-tool");
|
||||||
|
holder.setInitParameter("dirAllowed","true");
|
||||||
|
server.setHandler(context);
|
||||||
|
|
||||||
|
ServerContainer container = WebSocketConfiguration.configureContext(context);
|
||||||
|
container.addEndpoint(JsrBrowserSocket.class);
|
||||||
|
|
||||||
|
LOG.info("{} setup on port {}",this.getClass().getName(),port);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,227 @@
|
||||||
|
//
|
||||||
|
// ========================================================================
|
||||||
|
// Copyright (c) 1995-2013 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.server.browser;
|
||||||
|
|
||||||
|
import java.text.DateFormat;
|
||||||
|
import java.text.SimpleDateFormat;
|
||||||
|
import java.util.Calendar;
|
||||||
|
import java.util.Locale;
|
||||||
|
import java.util.Random;
|
||||||
|
|
||||||
|
import javax.websocket.CloseReason;
|
||||||
|
import javax.websocket.OnClose;
|
||||||
|
import javax.websocket.OnMessage;
|
||||||
|
import javax.websocket.OnOpen;
|
||||||
|
import javax.websocket.RemoteEndpoint.Async;
|
||||||
|
import javax.websocket.Session;
|
||||||
|
import javax.websocket.server.ServerEndpoint;
|
||||||
|
|
||||||
|
import org.eclipse.jetty.util.StringUtil;
|
||||||
|
import org.eclipse.jetty.util.log.Log;
|
||||||
|
import org.eclipse.jetty.util.log.Logger;
|
||||||
|
|
||||||
|
@ServerEndpoint(value = "/", configurator = JsrBrowserConfigurator.class)
|
||||||
|
public class JsrBrowserSocket
|
||||||
|
{
|
||||||
|
private static class WriteMany implements Runnable
|
||||||
|
{
|
||||||
|
private Async remote;
|
||||||
|
private int size;
|
||||||
|
private int count;
|
||||||
|
|
||||||
|
public WriteMany(Async remote, int size, int count)
|
||||||
|
{
|
||||||
|
this.remote = remote;
|
||||||
|
this.size = size;
|
||||||
|
this.count = count;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void run()
|
||||||
|
{
|
||||||
|
char letters[] = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ-|{}[]():".toCharArray();
|
||||||
|
int lettersLen = letters.length;
|
||||||
|
char randomText[] = new char[size];
|
||||||
|
Random rand = new Random(42);
|
||||||
|
String msg;
|
||||||
|
|
||||||
|
for (int n = 0; n < count; n++)
|
||||||
|
{
|
||||||
|
// create random text
|
||||||
|
for (int i = 0; i < size; i++)
|
||||||
|
{
|
||||||
|
randomText[i] = letters[rand.nextInt(lettersLen)];
|
||||||
|
}
|
||||||
|
msg = String.format("ManyThreads [%s]",String.valueOf(randomText));
|
||||||
|
remote.sendText(msg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static final Logger LOG = Log.getLogger(JsrBrowserSocket.class);
|
||||||
|
private Session session;
|
||||||
|
private Async remote;
|
||||||
|
private String userAgent;
|
||||||
|
private String requestedExtensions;
|
||||||
|
|
||||||
|
@OnOpen
|
||||||
|
public void onOpen(Session session)
|
||||||
|
{
|
||||||
|
LOG.info("Open: {}",session);
|
||||||
|
this.session = session;
|
||||||
|
this.remote = session.getAsyncRemote();
|
||||||
|
this.userAgent = (String)session.getUserProperties().get("userAgent");
|
||||||
|
this.requestedExtensions = (String)session.getUserProperties().get("requestedExtensions");
|
||||||
|
}
|
||||||
|
|
||||||
|
@OnClose
|
||||||
|
public void onClose(CloseReason close)
|
||||||
|
{
|
||||||
|
LOG.info("Close: {}: {}",close.getCloseCode(),close.getReasonPhrase());
|
||||||
|
this.session = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@OnMessage
|
||||||
|
public void onMessage(String message)
|
||||||
|
{
|
||||||
|
LOG.info("onTextMessage({})",message);
|
||||||
|
|
||||||
|
int idx = message.indexOf(':');
|
||||||
|
if (idx > 0)
|
||||||
|
{
|
||||||
|
String key = message.substring(0,idx).toLowerCase(Locale.ENGLISH);
|
||||||
|
String val = message.substring(idx + 1);
|
||||||
|
switch (key)
|
||||||
|
{
|
||||||
|
case "info":
|
||||||
|
{
|
||||||
|
writeMessage("Using javax.websocket");
|
||||||
|
if (StringUtil.isBlank(userAgent))
|
||||||
|
{
|
||||||
|
writeMessage("Client has no User-Agent");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
writeMessage("Client User-Agent: " + this.userAgent);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (StringUtil.isBlank(requestedExtensions))
|
||||||
|
{
|
||||||
|
writeMessage("Client requested no Sec-WebSocket-Extensions");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
writeMessage("Client Sec-WebSocket-Extensions: " + this.requestedExtensions);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case "many":
|
||||||
|
{
|
||||||
|
String parts[] = val.split(",");
|
||||||
|
int size = Integer.parseInt(parts[0]);
|
||||||
|
int count = Integer.parseInt(parts[1]);
|
||||||
|
|
||||||
|
writeManyAsync(size,count);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case "manythreads":
|
||||||
|
{
|
||||||
|
String parts[] = val.split(",");
|
||||||
|
int threadCount = Integer.parseInt(parts[0]);
|
||||||
|
int size = Integer.parseInt(parts[1]);
|
||||||
|
int count = Integer.parseInt(parts[2]);
|
||||||
|
|
||||||
|
Thread threads[] = new Thread[threadCount];
|
||||||
|
|
||||||
|
// Setup threads
|
||||||
|
for (int n = 0; n < threadCount; n++)
|
||||||
|
{
|
||||||
|
threads[n] = new Thread(new WriteMany(remote,size,count),"WriteMany[" + n + "]");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Execute threads
|
||||||
|
for (Thread thread : threads)
|
||||||
|
{
|
||||||
|
thread.start();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Drop out of this thread
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case "time":
|
||||||
|
{
|
||||||
|
Calendar now = Calendar.getInstance();
|
||||||
|
DateFormat sdf = SimpleDateFormat.getDateTimeInstance(SimpleDateFormat.FULL,SimpleDateFormat.FULL);
|
||||||
|
writeMessage("Server time: %s",sdf.format(now.getTime()));
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
{
|
||||||
|
writeMessage("key[%s] val[%s]",key,val);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// Not parameterized, echo it back
|
||||||
|
writeMessage(message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void writeManyAsync(int size, int count)
|
||||||
|
{
|
||||||
|
char letters[] = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ-|{}[]():".toCharArray();
|
||||||
|
int lettersLen = letters.length;
|
||||||
|
char randomText[] = new char[size];
|
||||||
|
Random rand = new Random(42);
|
||||||
|
|
||||||
|
for (int n = 0; n < count; n++)
|
||||||
|
{
|
||||||
|
// create random text
|
||||||
|
for (int i = 0; i < size; i++)
|
||||||
|
{
|
||||||
|
randomText[i] = letters[rand.nextInt(lettersLen)];
|
||||||
|
}
|
||||||
|
writeMessage("Many [%s]",String.valueOf(randomText));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void writeMessage(String message)
|
||||||
|
{
|
||||||
|
if (this.session == null)
|
||||||
|
{
|
||||||
|
LOG.debug("Not connected");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (session.isOpen() == false)
|
||||||
|
{
|
||||||
|
LOG.debug("Not open");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Async write
|
||||||
|
remote.sendText(message);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void writeMessage(String format, Object... args)
|
||||||
|
{
|
||||||
|
writeMessage(String.format(format,args));
|
||||||
|
}
|
||||||
|
}
|
|
@ -6,3 +6,6 @@ org.eclipse.jetty.LEVEL=WARN
|
||||||
# org.eclipse.jetty.websocket.LEVEL=WARN
|
# org.eclipse.jetty.websocket.LEVEL=WARN
|
||||||
# org.eclipse.jetty.websocket.common.io.LEVEL=DEBUG
|
# org.eclipse.jetty.websocket.common.io.LEVEL=DEBUG
|
||||||
|
|
||||||
|
### Show state changes on BrowserDebugTool
|
||||||
|
# -- LEAVE THIS AT DEBUG LEVEL --
|
||||||
|
org.eclipse.jetty.websocket.jsr356.server.browser.LEVEL=DEBUG
|
||||||
|
|
|
@ -0,0 +1,37 @@
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<title>Jetty WebSocket Browser -> Server Debug Tool</title>
|
||||||
|
<script type="text/javascript" src="websocket.js"></script>
|
||||||
|
<link rel="stylesheet" type="text/css" href="main.css" media="all" >
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
jetty websocket/browser/javascript -> server debug tool #console
|
||||||
|
<div id="console"></div>
|
||||||
|
<div id="buttons">
|
||||||
|
<input id="connect" class="button" type="submit" name="connect" value="connect"/>
|
||||||
|
<input id="close" class="button" type="submit" name="close" value="close" disabled="disabled"/>
|
||||||
|
<input id="info" class="button" type="submit" name="info" value="info" disabled="disabled"/>
|
||||||
|
<input id="time" class="button" type="submit" name="time" value="time" disabled="disabled"/>
|
||||||
|
<input id="many" class="button" type="submit" name="many" value="many" disabled="disabled"/>
|
||||||
|
<input id="manythreads" class="button" type="submit" name="many" value="manythreads" disabled="disabled"/>
|
||||||
|
<input id="hello" class="button" type="submit" name="hello" value="hello" disabled="disabled"/>
|
||||||
|
<input id="there" class="button" type="submit" name="there" value="there" disabled="disabled"/>
|
||||||
|
<input id="json" class="button" type="submit" name="json" value="json" disabled="disabled"/>
|
||||||
|
</div>
|
||||||
|
<script type="text/javascript">
|
||||||
|
$("connect").onclick = function(event) { wstool.connect(); return false; }
|
||||||
|
$("close").onclick = function(event) {wstool.close(); return false; }
|
||||||
|
$("info").onclick = function(event) {wstool.write("info:"); return false; }
|
||||||
|
$("time").onclick = function(event) {wstool.write("time:"); return false; }
|
||||||
|
$("many").onclick = function(event) {wstool.write("many:15,300"); return false; }
|
||||||
|
$("manythreads").onclick = function(event) {wstool.write("manythreads:20,25,60"); return false; }
|
||||||
|
$("hello").onclick = function(event) {wstool.write("Hello"); return false; }
|
||||||
|
$("there").onclick = function(event) {wstool.write("There"); return false; }
|
||||||
|
$("json").onclick = function(event) {wstool.write("[{\"channel\":\"/meta/subscribe\",\"subscription\":\"/chat/demo\",\"id\":\"2\",\"clientId\":\"81dwnxwbgs0h0bq8968b0a0gyl\",\"timestamp\":\"Thu,"
|
||||||
|
+ " 12 Sep 2013 19:42:30 GMT\"},{\"channel\":\"/meta/subscribe\",\"subscription\":\"/members/demo\",\"id\":\"3\",\"clientId\":\"81dwnxwbgs0h0bq8968b0a0gyl\",\"timestamp\":\"Thu,"
|
||||||
|
+ " 12 Sep 2013 19:42:30 GMT\"},{\"channel\":\"/chat/demo\",\"data\":{\"user\":\"ch\",\"membership\":\"join\",\"chat\":\"ch"
|
||||||
|
+ " has joined\"},\"id\":\"4\",\"clientId\":\"81dwnxwbgs0h0bq8968b0a0gyl\",\"timestamp\":\"Thu,"
|
||||||
|
+ " 12 Sep 2013 19:42:30 GMT\"}]"); return false; }
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
</html>
|
|
@ -0,0 +1,29 @@
|
||||||
|
body {
|
||||||
|
font-family: sans-serif;
|
||||||
|
}
|
||||||
|
|
||||||
|
div {
|
||||||
|
border: 0px solid black;
|
||||||
|
}
|
||||||
|
|
||||||
|
div#console {
|
||||||
|
clear: both;
|
||||||
|
width: 40em;
|
||||||
|
height: 20em;
|
||||||
|
overflow: auto;
|
||||||
|
background-color: #f0f0f0;
|
||||||
|
padding: 4px;
|
||||||
|
border: 1px solid black;
|
||||||
|
}
|
||||||
|
|
||||||
|
div#console .info {
|
||||||
|
color: black;
|
||||||
|
}
|
||||||
|
|
||||||
|
div#console .client {
|
||||||
|
color: blue;
|
||||||
|
}
|
||||||
|
|
||||||
|
div#console .server {
|
||||||
|
color: magenta;
|
||||||
|
}
|
|
@ -0,0 +1,128 @@
|
||||||
|
if (!window.WebSocket && window.MozWebSocket) {
|
||||||
|
window.WebSocket = window.MozWebSocket;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!window.WebSocket) {
|
||||||
|
alert("WebSocket not supported by this browser");
|
||||||
|
}
|
||||||
|
|
||||||
|
function $() {
|
||||||
|
return document.getElementById(arguments[0]);
|
||||||
|
}
|
||||||
|
function $F() {
|
||||||
|
return document.getElementById(arguments[0]).value;
|
||||||
|
}
|
||||||
|
|
||||||
|
function getKeyCode(ev) {
|
||||||
|
if (window.event)
|
||||||
|
return window.event.keyCode;
|
||||||
|
return ev.keyCode;
|
||||||
|
}
|
||||||
|
|
||||||
|
var wstool = {
|
||||||
|
connect : function() {
|
||||||
|
var location = document.location.toString().replace('http://', 'ws://')
|
||||||
|
.replace('https://', 'wss://');
|
||||||
|
|
||||||
|
wstool.info("Document URI: " + document.location);
|
||||||
|
wstool.info("WS URI: " + location);
|
||||||
|
|
||||||
|
this._scount = 0;
|
||||||
|
|
||||||
|
try {
|
||||||
|
this._ws = new WebSocket(location, "tool");
|
||||||
|
this._ws.onopen = this._onopen;
|
||||||
|
this._ws.onmessage = this._onmessage;
|
||||||
|
this._ws.onclose = this._onclose;
|
||||||
|
} catch (exception) {
|
||||||
|
wstool.info("Connect Error: " + exception);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
close : function() {
|
||||||
|
this._ws.close();
|
||||||
|
},
|
||||||
|
|
||||||
|
_out : function(css, message) {
|
||||||
|
var console = $('console');
|
||||||
|
var spanText = document.createElement('span');
|
||||||
|
spanText.className = 'text ' + css;
|
||||||
|
spanText.innerHTML = message;
|
||||||
|
var lineBreak = document.createElement('br');
|
||||||
|
console.appendChild(spanText);
|
||||||
|
console.appendChild(lineBreak);
|
||||||
|
console.scrollTop = console.scrollHeight - console.clientHeight;
|
||||||
|
},
|
||||||
|
|
||||||
|
info : function(message) {
|
||||||
|
wstool._out("info", message);
|
||||||
|
},
|
||||||
|
|
||||||
|
infoc : function(message) {
|
||||||
|
wstool._out("client", "[c] " + message);
|
||||||
|
},
|
||||||
|
|
||||||
|
infos : function(message) {
|
||||||
|
this._scount++;
|
||||||
|
wstool._out("server", "[s" + this._scount + "] " + message);
|
||||||
|
},
|
||||||
|
|
||||||
|
setState : function(enabled) {
|
||||||
|
$('connect').disabled = enabled;
|
||||||
|
$('close').disabled = !enabled;
|
||||||
|
$('info').disabled = !enabled;
|
||||||
|
$('time').disabled = !enabled;
|
||||||
|
$('many').disabled = !enabled;
|
||||||
|
$('manythreads').disabled = !enabled;
|
||||||
|
$('hello').disabled = !enabled;
|
||||||
|
$('there').disabled = !enabled;
|
||||||
|
$('json').disabled = !enabled;
|
||||||
|
},
|
||||||
|
|
||||||
|
_onopen : function() {
|
||||||
|
wstool.setState(true);
|
||||||
|
wstool.info("Websocket Connected");
|
||||||
|
},
|
||||||
|
|
||||||
|
_send : function(message) {
|
||||||
|
if (this._ws) {
|
||||||
|
this._ws.send(message);
|
||||||
|
wstool.infoc(message);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
write : function(text) {
|
||||||
|
wstool._send(text);
|
||||||
|
},
|
||||||
|
|
||||||
|
_onmessage : function(m) {
|
||||||
|
if (m.data) {
|
||||||
|
wstool.infos(m.data);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
_onclose : function(closeEvent) {
|
||||||
|
this._ws = null;
|
||||||
|
wstool.setState(false);
|
||||||
|
wstool.info("Websocket Closed");
|
||||||
|
wstool.info(" .wasClean = " + closeEvent.wasClean);
|
||||||
|
|
||||||
|
var codeMap = {};
|
||||||
|
codeMap[1000] = "(NORMAL)";
|
||||||
|
codeMap[1001] = "(ENDPOINT_GOING_AWAY)";
|
||||||
|
codeMap[1002] = "(PROTOCOL_ERROR)";
|
||||||
|
codeMap[1003] = "(UNSUPPORTED_DATA)";
|
||||||
|
codeMap[1004] = "(UNUSED/RESERVED)";
|
||||||
|
codeMap[1005] = "(INTERNAL/NO_CODE_PRESENT)";
|
||||||
|
codeMap[1006] = "(INTERNAL/ABNORMAL_CLOSE)";
|
||||||
|
codeMap[1007] = "(BAD_DATA)";
|
||||||
|
codeMap[1008] = "(POLICY_VIOLATION)";
|
||||||
|
codeMap[1009] = "(MESSAGE_TOO_BIG)";
|
||||||
|
codeMap[1010] = "(HANDSHAKE/EXT_FAILURE)";
|
||||||
|
codeMap[1011] = "(SERVER/UNEXPECTED_CONDITION)";
|
||||||
|
codeMap[1015] = "(INTERNAL/TLS_ERROR)";
|
||||||
|
var codeStr = codeMap[closeEvent.code];
|
||||||
|
wstool.info(" .code = " + closeEvent.code + " " + codeStr);
|
||||||
|
wstool.info(" .reason = " + closeEvent.reason);
|
||||||
|
}
|
||||||
|
};
|
|
@ -18,8 +18,8 @@
|
||||||
|
|
||||||
package org.eclipse.jetty.websocket.api.util;
|
package org.eclipse.jetty.websocket.api.util;
|
||||||
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
|
import java.util.Collection;
|
||||||
import java.util.Iterator;
|
import java.util.Iterator;
|
||||||
import java.util.NoSuchElementException;
|
import java.util.NoSuchElementException;
|
||||||
|
|
||||||
|
@ -473,4 +473,31 @@ public class QuoteUtil
|
||||||
}
|
}
|
||||||
return ret.toString();
|
return ret.toString();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static String join(Collection<?> objs, String delim)
|
||||||
|
{
|
||||||
|
if (objs == null)
|
||||||
|
{
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
StringBuilder ret = new StringBuilder();
|
||||||
|
boolean needDelim = false;
|
||||||
|
for (Object obj : objs)
|
||||||
|
{
|
||||||
|
if (needDelim)
|
||||||
|
{
|
||||||
|
ret.append(delim);
|
||||||
|
}
|
||||||
|
if (obj instanceof String)
|
||||||
|
{
|
||||||
|
ret.append('"').append(obj).append('"');
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
ret.append(obj);
|
||||||
|
}
|
||||||
|
needDelim = true;
|
||||||
|
}
|
||||||
|
return ret.toString();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -468,7 +468,18 @@ public class WebSocketServerFactory extends ContainerLifeCycle implements WebSoc
|
||||||
|
|
||||||
// Initialize / Negotiate Extensions
|
// Initialize / Negotiate Extensions
|
||||||
ExtensionStack extensionStack = new ExtensionStack(getExtensionFactory());
|
ExtensionStack extensionStack = new ExtensionStack(getExtensionFactory());
|
||||||
extensionStack.negotiate(request.getExtensions());
|
// The JSR allows for the extensions to be pre-negotiated, filtered, etc...
|
||||||
|
// Usually from a Configurator.
|
||||||
|
if (response.isExtensionsNegotiated())
|
||||||
|
{
|
||||||
|
// Use pre-negotiated extension list from response
|
||||||
|
extensionStack.negotiate(response.getExtensions());
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// Use raw extension list from request
|
||||||
|
extensionStack.negotiate(request.getExtensions());
|
||||||
|
}
|
||||||
|
|
||||||
// Create connection
|
// Create connection
|
||||||
UpgradeContext context = getActiveUpgradeContext();
|
UpgradeContext context = getActiveUpgradeContext();
|
||||||
|
@ -494,6 +505,7 @@ public class WebSocketServerFactory extends ContainerLifeCycle implements WebSoc
|
||||||
WebSocketSession session = createSession(request.getRequestURI(),driver,connection);
|
WebSocketSession session = createSession(request.getRequestURI(),driver,connection);
|
||||||
session.setPolicy(driver.getPolicy());
|
session.setPolicy(driver.getPolicy());
|
||||||
session.setUpgradeRequest(request);
|
session.setUpgradeRequest(request);
|
||||||
|
// set true negotiated extension list back to response
|
||||||
response.setExtensions(extensionStack.getNegotiatedExtensions());
|
response.setExtensions(extensionStack.getNegotiatedExtensions());
|
||||||
session.setUpgradeResponse(response);
|
session.setUpgradeResponse(response);
|
||||||
connection.setSession(session);
|
connection.setSession(session);
|
||||||
|
|
|
@ -20,6 +20,7 @@ package org.eclipse.jetty.websocket.server.blockhead;
|
||||||
|
|
||||||
import static org.hamcrest.Matchers.*;
|
import static org.hamcrest.Matchers.*;
|
||||||
|
|
||||||
|
import java.io.Closeable;
|
||||||
import java.io.EOFException;
|
import java.io.EOFException;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.InputStream;
|
import java.io.InputStream;
|
||||||
|
@ -83,7 +84,7 @@ import org.junit.Assert;
|
||||||
* with regards to basic IO behavior, a write should work as expected, a read should work as expected, but <u>what</u> byte it sends or reads is not within its
|
* with regards to basic IO behavior, a write should work as expected, a read should work as expected, but <u>what</u> byte it sends or reads is not within its
|
||||||
* scope.
|
* scope.
|
||||||
*/
|
*/
|
||||||
public class BlockheadClient implements IncomingFrames, OutgoingFrames, ConnectionStateListener
|
public class BlockheadClient implements IncomingFrames, OutgoingFrames, ConnectionStateListener, Closeable
|
||||||
{
|
{
|
||||||
private static final String REQUEST_HASH_KEY = "dGhlIHNhbXBsZSBub25jZQ==";
|
private static final String REQUEST_HASH_KEY = "dGhlIHNhbXBsZSBub25jZQ==";
|
||||||
private static final int BUFFER_SIZE = 8192;
|
private static final int BUFFER_SIZE = 8192;
|
||||||
|
|
|
@ -19,10 +19,12 @@
|
||||||
package org.eclipse.jetty.websocket.servlet;
|
package org.eclipse.jetty.websocket.servlet;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
import javax.servlet.http.HttpServletResponse;
|
import javax.servlet.http.HttpServletResponse;
|
||||||
|
|
||||||
import org.eclipse.jetty.websocket.api.UpgradeResponse;
|
import org.eclipse.jetty.websocket.api.UpgradeResponse;
|
||||||
|
import org.eclipse.jetty.websocket.api.extensions.ExtensionConfig;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Servlet Specific UpgradeResponse implementation.
|
* Servlet Specific UpgradeResponse implementation.
|
||||||
|
@ -30,6 +32,8 @@ import org.eclipse.jetty.websocket.api.UpgradeResponse;
|
||||||
public class ServletUpgradeResponse extends UpgradeResponse
|
public class ServletUpgradeResponse extends UpgradeResponse
|
||||||
{
|
{
|
||||||
private HttpServletResponse resp;
|
private HttpServletResponse resp;
|
||||||
|
private boolean extensionsNegotiated = false;
|
||||||
|
private boolean subprotocolNegotiated = false;
|
||||||
|
|
||||||
public ServletUpgradeResponse(HttpServletResponse resp)
|
public ServletUpgradeResponse(HttpServletResponse resp)
|
||||||
{
|
{
|
||||||
|
@ -60,6 +64,16 @@ public class ServletUpgradeResponse extends UpgradeResponse
|
||||||
return this.resp.isCommitted();
|
return this.resp.isCommitted();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public boolean isExtensionsNegotiated()
|
||||||
|
{
|
||||||
|
return extensionsNegotiated;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isSubprotocolNegotiated()
|
||||||
|
{
|
||||||
|
return subprotocolNegotiated;
|
||||||
|
}
|
||||||
|
|
||||||
public void sendError(int statusCode, String message) throws IOException
|
public void sendError(int statusCode, String message) throws IOException
|
||||||
{
|
{
|
||||||
setSuccess(false);
|
setSuccess(false);
|
||||||
|
@ -73,6 +87,20 @@ public class ServletUpgradeResponse extends UpgradeResponse
|
||||||
resp.sendError(HttpServletResponse.SC_FORBIDDEN,message);
|
resp.sendError(HttpServletResponse.SC_FORBIDDEN,message);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setAcceptedSubProtocol(String protocol)
|
||||||
|
{
|
||||||
|
super.setAcceptedSubProtocol(protocol);
|
||||||
|
subprotocolNegotiated = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setExtensions(List<ExtensionConfig> extensions)
|
||||||
|
{
|
||||||
|
super.setExtensions(extensions);
|
||||||
|
extensionsNegotiated = true;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void setHeader(String name, String value)
|
public void setHeader(String name, String value)
|
||||||
{
|
{
|
||||||
|
|
Loading…
Reference in New Issue