Merge branch 'issue-117' into jetty-9.4.x
This commit is contained in:
commit
709938e02b
|
@ -79,26 +79,35 @@ public class ClientContainer extends ContainerLifeCycle implements WebSocketCont
|
||||||
private final DecoderFactory decoderFactory;
|
private final DecoderFactory decoderFactory;
|
||||||
/** Tracking all primitive encoders for the container */
|
/** Tracking all primitive encoders for the container */
|
||||||
private final EncoderFactory encoderFactory;
|
private final EncoderFactory encoderFactory;
|
||||||
|
/** The jetty websocket client in use for this container */
|
||||||
|
private final WebSocketClient client;
|
||||||
/** Tracking for all declared Client endpoints */
|
/** Tracking for all declared Client endpoints */
|
||||||
private final Map<Class<?>, EndpointMetadata> endpointClientMetadataCache;
|
private final Map<Class<?>, EndpointMetadata> endpointClientMetadataCache;
|
||||||
/** The jetty websocket client in use for this container */
|
|
||||||
private WebSocketClient client;
|
/**
|
||||||
|
* This is the entry point for {@link javax.websocket.ContainerProvider#getWebSocketContainer()}
|
||||||
|
*/
|
||||||
public ClientContainer()
|
public ClientContainer()
|
||||||
{
|
{
|
||||||
// This constructor is used with Standalone JSR Client usage.
|
// This constructor is used with Standalone JSR Client usage.
|
||||||
this(new SimpleContainerScope(WebSocketPolicy.newClientPolicy()));
|
this(new SimpleContainerScope(WebSocketPolicy.newClientPolicy()));
|
||||||
client.setDaemon(true);
|
client.setDaemon(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This is the entry point for ServerContainer, via ServletContext.getAttribute(ServerContainer.class.getName())
|
||||||
|
*
|
||||||
|
* @param scope the scope of the ServerContainer
|
||||||
|
*/
|
||||||
public ClientContainer(WebSocketContainerScope scope)
|
public ClientContainer(WebSocketContainerScope scope)
|
||||||
{
|
{
|
||||||
boolean trustAll = Boolean.getBoolean("org.eclipse.jetty.websocket.jsr356.ssl-trust-all");
|
boolean trustAll = Boolean.getBoolean("org.eclipse.jetty.websocket.jsr356.ssl-trust-all");
|
||||||
|
|
||||||
this.scopeDelegate = scope;
|
this.scopeDelegate = scope;
|
||||||
client = new WebSocketClient(scope, new SslContextFactory(trustAll));
|
client = new WebSocketClient(scope,
|
||||||
client.setEventDriverFactory(new JsrEventDriverFactory(client.getPolicy()));
|
new JsrEventDriverFactory(scope.getPolicy()),
|
||||||
client.setSessionFactory(new JsrSessionFactory(this));
|
new JsrSessionFactory(this));
|
||||||
|
client.getSslContextFactory().setTrustAll(trustAll);
|
||||||
addBean(client);
|
addBean(client);
|
||||||
|
|
||||||
this.endpointClientMetadataCache = new ConcurrentHashMap<>();
|
this.endpointClientMetadataCache = new ConcurrentHashMap<>();
|
||||||
|
|
|
@ -38,6 +38,7 @@ public class AnnotatedEchoTest
|
||||||
private static EchoHandler handler;
|
private static EchoHandler handler;
|
||||||
private static URI serverUri;
|
private static URI serverUri;
|
||||||
|
|
||||||
|
@SuppressWarnings("Duplicates")
|
||||||
@BeforeClass
|
@BeforeClass
|
||||||
public static void startServer() throws Exception
|
public static void startServer() throws Exception
|
||||||
{
|
{
|
||||||
|
|
|
@ -22,8 +22,6 @@ import java.io.Closeable;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.net.InetSocketAddress;
|
import java.net.InetSocketAddress;
|
||||||
|
|
||||||
import org.eclipse.jetty.websocket.api.annotations.OnWebSocketClose;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Session represents an active link of communications with a Remote WebSocket Endpoint.
|
* Session represents an active link of communications with a Remote WebSocket Endpoint.
|
||||||
*/
|
*/
|
||||||
|
|
|
@ -21,342 +21,307 @@ package org.eclipse.jetty.websocket.api;
|
||||||
import java.net.HttpCookie;
|
import java.net.HttpCookie;
|
||||||
import java.net.URI;
|
import java.net.URI;
|
||||||
import java.security.Principal;
|
import java.security.Principal;
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.Collections;
|
|
||||||
import java.util.HashMap;
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.TreeMap;
|
|
||||||
|
|
||||||
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 UpgradeRequest
|
/**
|
||||||
|
* The HTTP Upgrade to WebSocket Request
|
||||||
|
*/
|
||||||
|
public interface UpgradeRequest
|
||||||
{
|
{
|
||||||
private URI requestURI;
|
/**
|
||||||
private List<String> subProtocols = new ArrayList<>(1);
|
* Add WebSocket Extension Configuration(s) to Upgrade Request.
|
||||||
private List<ExtensionConfig> extensions = new ArrayList<>(1);
|
* <p>
|
||||||
private List<HttpCookie> cookies = new ArrayList<>(1);
|
* This is merely the list of requested Extensions to use, see {@link UpgradeResponse#getExtensions()} for what was
|
||||||
private Map<String, List<String>> headers = new TreeMap<>(String.CASE_INSENSITIVE_ORDER);
|
* negotiated
|
||||||
private Map<String, List<String>> parameters = new HashMap<>(1);
|
*
|
||||||
private Object session;
|
* @param configs the configuration(s) to add
|
||||||
private String httpVersion;
|
*/
|
||||||
private String method;
|
void addExtensions(ExtensionConfig... configs);
|
||||||
private String host;
|
|
||||||
private boolean secure;
|
|
||||||
|
|
||||||
protected UpgradeRequest()
|
/**
|
||||||
{
|
* Add WebSocket Extension Configuration(s) to request
|
||||||
/* anonymous, no requestURI, upgrade request */
|
* <p>
|
||||||
}
|
* This is merely the list of requested Extensions to use, see {@link UpgradeResponse#getExtensions()} for what was
|
||||||
|
* negotiated
|
||||||
|
*
|
||||||
|
* @param configs the configuration(s) to add
|
||||||
|
*/
|
||||||
|
void addExtensions(String... configs);
|
||||||
|
|
||||||
public UpgradeRequest(String requestURI)
|
/**
|
||||||
{
|
* Remove all headers from request.
|
||||||
this(URI.create(requestURI));
|
* @deprecated (no longer supported, as this can undo the required upgrade request headers)
|
||||||
}
|
*/
|
||||||
|
@Deprecated
|
||||||
|
void clearHeaders();
|
||||||
|
|
||||||
public UpgradeRequest(URI requestURI)
|
/**
|
||||||
{
|
* Get the list of Cookies on the Upgrade request
|
||||||
setRequestURI(requestURI);
|
*
|
||||||
}
|
* @return the list of Cookies
|
||||||
|
*/
|
||||||
|
List<HttpCookie> getCookies();
|
||||||
|
|
||||||
public void addExtensions(ExtensionConfig... configs)
|
/**
|
||||||
{
|
* Get the list of WebSocket Extension Configurations for this Upgrade Request.
|
||||||
Collections.addAll(extensions, configs);
|
* <p>
|
||||||
}
|
* This is merely the list of requested Extensions to use, see {@link UpgradeResponse#getExtensions()} for what was
|
||||||
|
* negotiated
|
||||||
|
*
|
||||||
|
* @return the list of Extension configurations (in the order they were specified)
|
||||||
|
*/
|
||||||
|
List<ExtensionConfig> getExtensions();
|
||||||
|
|
||||||
public void addExtensions(String... configs)
|
/**
|
||||||
{
|
* Get a specific Header value from Upgrade Request
|
||||||
for (String config : configs)
|
*
|
||||||
{
|
* @param name the name of the header
|
||||||
extensions.add(ExtensionConfig.parse(config));
|
* @return the value of the header (null if header does not exist)
|
||||||
}
|
*/
|
||||||
}
|
String getHeader(String name);
|
||||||
|
|
||||||
public void clearHeaders()
|
/**
|
||||||
{
|
* Get the specific Header value, as an <code>int</code>, from the Upgrade Request.
|
||||||
headers.clear();
|
*
|
||||||
}
|
* @param name the name of the header
|
||||||
|
* @return the value of the header as an <code>int</code> (-1 if header does not exist)
|
||||||
|
* @throws NumberFormatException if unable to parse value as an int.
|
||||||
|
*/
|
||||||
|
int getHeaderInt(String name);
|
||||||
|
|
||||||
public List<HttpCookie> getCookies()
|
/**
|
||||||
{
|
* Get the headers as a Map of keys to value lists.
|
||||||
return cookies;
|
*
|
||||||
}
|
* @return the headers
|
||||||
|
*/
|
||||||
|
Map<String, List<String>> getHeaders();
|
||||||
|
|
||||||
public List<ExtensionConfig> getExtensions()
|
/**
|
||||||
{
|
* Get the specific header values (for multi-value headers)
|
||||||
return extensions;
|
*
|
||||||
}
|
* @param name the header name
|
||||||
|
* @return the value list (null if no header exists)
|
||||||
|
*/
|
||||||
|
List<String> getHeaders(String name);
|
||||||
|
|
||||||
public String getHeader(String name)
|
/**
|
||||||
{
|
* The host of the Upgrade Request URI
|
||||||
List<String> values = headers.get(name);
|
* <p>
|
||||||
// no value list
|
* Equivalent to {@link #getRequestURI()#getHost()}
|
||||||
if (values == null)
|
*
|
||||||
{
|
* @return host of the request URI
|
||||||
return null;
|
*/
|
||||||
}
|
String getHost();
|
||||||
int size = values.size();
|
|
||||||
// empty value list
|
|
||||||
if (size <= 0)
|
|
||||||
{
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
// simple return
|
|
||||||
if (size == 1)
|
|
||||||
{
|
|
||||||
return values.get(0);
|
|
||||||
}
|
|
||||||
// join it with commas
|
|
||||||
boolean needsDelim = false;
|
|
||||||
StringBuilder ret = new StringBuilder();
|
|
||||||
for (String value : values)
|
|
||||||
{
|
|
||||||
if (needsDelim)
|
|
||||||
{
|
|
||||||
ret.append(", ");
|
|
||||||
}
|
|
||||||
QuoteUtil.quoteIfNeeded(ret,value,QuoteUtil.ABNF_REQUIRED_QUOTING);
|
|
||||||
needsDelim = true;
|
|
||||||
}
|
|
||||||
return ret.toString();
|
|
||||||
}
|
|
||||||
|
|
||||||
public int getHeaderInt(String name)
|
/**
|
||||||
{
|
* The HTTP version used for this Upgrade Request
|
||||||
List<String> values = headers.get(name);
|
* <p>
|
||||||
// no value list
|
* As of <a href="http://tools.ietf.org/html/rfc6455">RFC6455 (December 2011)</a> this is always
|
||||||
if (values == null)
|
* <code>HTTP/1.1</code>
|
||||||
{
|
*
|
||||||
return -1;
|
* @return the HTTP Version used
|
||||||
}
|
*/
|
||||||
int size = values.size();
|
String getHttpVersion();
|
||||||
// empty value list
|
|
||||||
if (size <= 0)
|
|
||||||
{
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
// simple return
|
|
||||||
if (size == 1)
|
|
||||||
{
|
|
||||||
return Integer.parseInt(values.get(0));
|
|
||||||
}
|
|
||||||
throw new NumberFormatException("Cannot convert multi-value header into int");
|
|
||||||
}
|
|
||||||
|
|
||||||
public Map<String, List<String>> getHeaders()
|
/**
|
||||||
{
|
* The HTTP method for this Upgrade Request.
|
||||||
return headers;
|
* <p>
|
||||||
}
|
* As of <a href="http://tools.ietf.org/html/rfc6455">RFC6455 (December 2011)</a> this is always <code>GET</code>
|
||||||
|
*
|
||||||
|
* @return the HTTP method used
|
||||||
|
*/
|
||||||
|
String getMethod();
|
||||||
|
|
||||||
public List<String> getHeaders(String name)
|
/**
|
||||||
{
|
* The WebSocket Origin of this Upgrade Request
|
||||||
return headers.get(name);
|
* <p>
|
||||||
}
|
* See <a href="http://tools.ietf.org/html/rfc6455#section-10.2">RFC6455: Section 10.2</a> for details.
|
||||||
|
* <p>
|
||||||
public String getHost()
|
* Equivalent to {@link #getHeader("Origin")}
|
||||||
{
|
*
|
||||||
return host;
|
* @return the Origin header
|
||||||
}
|
*/
|
||||||
|
String getOrigin();
|
||||||
public String getHttpVersion()
|
|
||||||
{
|
|
||||||
return httpVersion;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getMethod()
|
|
||||||
{
|
|
||||||
return method;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getOrigin()
|
|
||||||
{
|
|
||||||
return getHeader("Origin");
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns a map of the query parameters of the request.
|
* Returns a map of the query parameters of the request.
|
||||||
*
|
*
|
||||||
* @return a unmodifiable map of query parameters of the request.
|
* @return a unmodifiable map of query parameters of the request.
|
||||||
*/
|
*/
|
||||||
public Map<String, List<String>> getParameterMap()
|
Map<String, List<String>> getParameterMap();
|
||||||
{
|
|
||||||
return Collections.unmodifiableMap(parameters);
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getProtocolVersion()
|
/**
|
||||||
{
|
* Get the WebSocket Protocol Version
|
||||||
String version = getHeader("Sec-WebSocket-Version");
|
* <p>
|
||||||
if (version == null)
|
* As of <a href="http://tools.ietf.org/html/rfc6455#section-11.6">RFC6455</a>, Jetty only supports version
|
||||||
{
|
* <code>13</code>
|
||||||
return "13"; // Default
|
*
|
||||||
}
|
* @return the WebSocket protocol version
|
||||||
return version;
|
*/
|
||||||
}
|
String getProtocolVersion();
|
||||||
|
|
||||||
public String getQueryString()
|
/**
|
||||||
{
|
* Get the Query String of the request URI.
|
||||||
return requestURI.getQuery();
|
* <p>
|
||||||
}
|
* Equivalent to {@link #getRequestURI()#getQueryString()}
|
||||||
|
*
|
||||||
|
* @return the request uri query string
|
||||||
|
*/
|
||||||
|
String getQueryString();
|
||||||
|
|
||||||
public URI getRequestURI()
|
/**
|
||||||
{
|
* Get the Request URI
|
||||||
return requestURI;
|
*
|
||||||
}
|
* @return the request URI
|
||||||
|
*/
|
||||||
|
URI getRequestURI();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Access the Servlet HTTP Session (if present)
|
* Access the Servlet HTTP Session (if present)
|
||||||
* <p>
|
* <p>
|
||||||
* Note: Never present on a Client UpgradeRequest.
|
* Note: Never present on a Client UpgradeRequest.
|
||||||
*
|
*
|
||||||
* @return the Servlet HTTPSession on server side UpgradeRequests
|
* @return the Servlet HTTPSession on server side UpgradeRequests
|
||||||
*/
|
*/
|
||||||
public Object getSession()
|
Object getSession();
|
||||||
{
|
|
||||||
return session;
|
|
||||||
}
|
|
||||||
|
|
||||||
public List<String> getSubProtocols()
|
/**
|
||||||
{
|
* Get the list of offered WebSocket sub-protocols.
|
||||||
return subProtocols;
|
*
|
||||||
}
|
* @return the list of offered sub-protocols
|
||||||
|
*/
|
||||||
|
List<String> getSubProtocols();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get the User Principal for this request.
|
* Get the User Principal for this request.
|
||||||
* <p>
|
* <p>
|
||||||
* Only applicable when using UpgradeRequest from server side.
|
* Only applicable when using UpgradeRequest from server side.
|
||||||
*
|
*
|
||||||
* @return the user principal
|
* @return the user principal
|
||||||
*/
|
*/
|
||||||
public Principal getUserPrincipal()
|
Principal getUserPrincipal();
|
||||||
{
|
|
||||||
// Server side should override to implement
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean hasSubProtocol(String test)
|
|
||||||
{
|
|
||||||
for (String protocol : subProtocols)
|
|
||||||
{
|
|
||||||
if (protocol.equalsIgnoreCase(test))
|
|
||||||
{
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean isOrigin(String test)
|
|
||||||
{
|
|
||||||
return test.equalsIgnoreCase(getOrigin());
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean isSecure()
|
|
||||||
{
|
|
||||||
return secure;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setCookies(List<HttpCookie> cookies)
|
|
||||||
{
|
|
||||||
this.cookies.clear();
|
|
||||||
if (cookies != null && !cookies.isEmpty())
|
|
||||||
{
|
|
||||||
this.cookies.addAll(cookies);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setExtensions(List<ExtensionConfig> configs)
|
|
||||||
{
|
|
||||||
this.extensions.clear();
|
|
||||||
if (configs != null)
|
|
||||||
{
|
|
||||||
this.extensions.addAll(configs);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setHeader(String name, List<String> values)
|
|
||||||
{
|
|
||||||
headers.put(name,values);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setHeader(String name, String value)
|
|
||||||
{
|
|
||||||
List<String> values = new ArrayList<>();
|
|
||||||
values.add(value);
|
|
||||||
setHeader(name,values);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setHeaders(Map<String, List<String>> headers)
|
|
||||||
{
|
|
||||||
clearHeaders();
|
|
||||||
|
|
||||||
for (Map.Entry<String, List<String>> entry : headers.entrySet())
|
|
||||||
{
|
|
||||||
String name = entry.getKey();
|
|
||||||
List<String> values = entry.getValue();
|
|
||||||
setHeader(name,values);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setHttpVersion(String httpVersion)
|
|
||||||
{
|
|
||||||
this.httpVersion = httpVersion;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setMethod(String method)
|
|
||||||
{
|
|
||||||
this.method = method;
|
|
||||||
}
|
|
||||||
|
|
||||||
protected void setParameterMap(Map<String, List<String>> parameters)
|
|
||||||
{
|
|
||||||
this.parameters.clear();
|
|
||||||
this.parameters.putAll(parameters);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setRequestURI(URI uri)
|
|
||||||
{
|
|
||||||
this.requestURI = uri;
|
|
||||||
String scheme = uri.getScheme();
|
|
||||||
if ("ws".equalsIgnoreCase(scheme))
|
|
||||||
{
|
|
||||||
secure = false;
|
|
||||||
}
|
|
||||||
else if ("wss".equalsIgnoreCase(scheme))
|
|
||||||
{
|
|
||||||
secure = true;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
throw new IllegalArgumentException("URI scheme must be 'ws' or 'wss'");
|
|
||||||
}
|
|
||||||
this.host = this.requestURI.getHost();
|
|
||||||
this.parameters.clear();
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setSession(Object session)
|
|
||||||
{
|
|
||||||
this.session = session;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setSubProtocols(List<String> subProtocols)
|
|
||||||
{
|
|
||||||
this.subProtocols.clear();
|
|
||||||
if (subProtocols != null)
|
|
||||||
{
|
|
||||||
this.subProtocols.addAll(subProtocols);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Set Sub Protocol request list.
|
* Test if a specific sub-protocol is offered
|
||||||
*
|
*
|
||||||
* @param protocols
|
* @param test the sub-protocol to test for
|
||||||
* the sub protocols desired
|
* @return true if sub-protocol exists on request
|
||||||
*/
|
*/
|
||||||
public void setSubProtocols(String... protocols)
|
boolean hasSubProtocol(String test);
|
||||||
{
|
|
||||||
subProtocols.clear();
|
/**
|
||||||
Collections.addAll(subProtocols, protocols);
|
* Test if supplied Origin is the same as the Request
|
||||||
}
|
*
|
||||||
|
* @param test the supplied origin
|
||||||
|
* @return true if the supplied origin matches the request origin
|
||||||
|
*/
|
||||||
|
boolean isOrigin(String test);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Test if connection is secure.
|
||||||
|
*
|
||||||
|
* @return true if connection is secure.
|
||||||
|
*/
|
||||||
|
boolean isSecure();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set the list of Cookies on the request
|
||||||
|
*
|
||||||
|
* @param cookies the cookies to use
|
||||||
|
*/
|
||||||
|
void setCookies(List<HttpCookie> cookies);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set the list of WebSocket Extension configurations on the request.
|
||||||
|
* @param configs the list of extension configurations
|
||||||
|
*/
|
||||||
|
void setExtensions(List<ExtensionConfig> configs);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set a specific header with multi-value field
|
||||||
|
* <p>
|
||||||
|
* Overrides any previous value for this named header
|
||||||
|
*
|
||||||
|
* @param name the name of the header
|
||||||
|
* @param values the multi-value field
|
||||||
|
*/
|
||||||
|
void setHeader(String name, List<String> values);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set a specific header value
|
||||||
|
* <p>
|
||||||
|
* Overrides any previous value for this named header
|
||||||
|
*
|
||||||
|
* @param name the header to set
|
||||||
|
* @param value the value to set it to
|
||||||
|
*/
|
||||||
|
void setHeader(String name, String value);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets multiple headers on the request.
|
||||||
|
* <p>
|
||||||
|
* Only sets those headers provided, does not remove
|
||||||
|
* headers that exist on request and are not provided in the
|
||||||
|
* parameter for this method.
|
||||||
|
* <p>
|
||||||
|
* Convenience method vs calling {@link #setHeader(String, List)} multiple times.
|
||||||
|
*
|
||||||
|
* @param headers the headers to set
|
||||||
|
*/
|
||||||
|
void setHeaders(Map<String, List<String>> headers);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set the HTTP Version to use.
|
||||||
|
* <p>
|
||||||
|
* As of <a href="http://tools.ietf.org/html/rfc6455">RFC6455 (December 2011)</a> this should always be
|
||||||
|
* <code>HTTP/1.1</code>
|
||||||
|
*
|
||||||
|
* @param httpVersion the HTTP version to use.
|
||||||
|
*/
|
||||||
|
void setHttpVersion(String httpVersion);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set the HTTP method to use.
|
||||||
|
* <p>
|
||||||
|
* As of <a href="http://tools.ietf.org/html/rfc6455">RFC6455 (December 2011)</a> this is always <code>GET</code>
|
||||||
|
*
|
||||||
|
* @param method the HTTP method to use.
|
||||||
|
*/
|
||||||
|
void setMethod(String method);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set the Request URI to use for this request.
|
||||||
|
* <p>
|
||||||
|
* Must be an absolute URI with scheme <code>'ws'</code> or <code>'wss'</code>
|
||||||
|
*
|
||||||
|
* @param uri the Request URI
|
||||||
|
*/
|
||||||
|
void setRequestURI(URI uri);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set the Session associated with this request.
|
||||||
|
* <p>
|
||||||
|
* Typically used to associate the Servlet HttpSession object.
|
||||||
|
*
|
||||||
|
* @param session the session object to associate with this request
|
||||||
|
*/
|
||||||
|
void setSession(Object session);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set the offered WebSocket Sub-Protocol list.
|
||||||
|
*
|
||||||
|
* @param protocols the offered sub-protocol list
|
||||||
|
*/
|
||||||
|
void setSubProtocols(List<String> protocols);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set the offered WebSocket Sub-Protocol list.
|
||||||
|
*
|
||||||
|
* @param protocols the offered sub-protocol list
|
||||||
|
*/
|
||||||
|
void setSubProtocols(String... protocols);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -19,119 +19,92 @@
|
||||||
package org.eclipse.jetty.websocket.api;
|
package org.eclipse.jetty.websocket.api;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
import java.util.TreeMap;
|
|
||||||
|
|
||||||
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 UpgradeResponse
|
/**
|
||||||
|
* The HTTP Upgrade to WebSocket Response
|
||||||
|
*/
|
||||||
|
public interface UpgradeResponse
|
||||||
{
|
{
|
||||||
public static final String SEC_WEBSOCKET_PROTOCOL = "Sec-WebSocket-Protocol";
|
/**
|
||||||
private int statusCode;
|
* Add a header value to the response.
|
||||||
private String statusReason;
|
*
|
||||||
private Map<String, List<String>> headers = new TreeMap<>(String.CASE_INSENSITIVE_ORDER);
|
* @param name the header name
|
||||||
private List<ExtensionConfig> extensions = new ArrayList<>();
|
* @param value the header value
|
||||||
private boolean success = false;
|
*/
|
||||||
|
void addHeader(String name, String value);
|
||||||
public void addHeader(String name, String value)
|
|
||||||
{
|
|
||||||
String key = name;
|
|
||||||
List<String> values = headers.get(key);
|
|
||||||
if (values == null)
|
|
||||||
{
|
|
||||||
values = new ArrayList<>();
|
|
||||||
}
|
|
||||||
values.add(value);
|
|
||||||
headers.put(key,values);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get the accepted WebSocket protocol.
|
* Get the accepted WebSocket protocol.
|
||||||
*
|
*
|
||||||
* @return the accepted WebSocket protocol.
|
* @return the accepted WebSocket protocol.
|
||||||
*/
|
*/
|
||||||
public String getAcceptedSubProtocol()
|
String getAcceptedSubProtocol();
|
||||||
{
|
|
||||||
return getHeader(SEC_WEBSOCKET_PROTOCOL);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get the list of extensions that should be used for the websocket.
|
* Get the list of extensions that should be used for the websocket.
|
||||||
*
|
*
|
||||||
* @return the list of negotiated extensions to use.
|
* @return the list of negotiated extensions to use.
|
||||||
*/
|
*/
|
||||||
public List<ExtensionConfig> getExtensions()
|
List<ExtensionConfig> getExtensions();
|
||||||
{
|
|
||||||
return extensions;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getHeader(String name)
|
/**
|
||||||
{
|
* Get a header value
|
||||||
List<String> values = getHeaders(name);
|
*
|
||||||
// no value list
|
* @param name the header name
|
||||||
if (values == null)
|
* @return the value (null if header doesn't exist)
|
||||||
{
|
*/
|
||||||
return null;
|
String getHeader(String name);
|
||||||
}
|
|
||||||
int size = values.size();
|
|
||||||
// empty value list
|
|
||||||
if (size <= 0)
|
|
||||||
{
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
// simple return
|
|
||||||
if (size == 1)
|
|
||||||
{
|
|
||||||
return values.get(0);
|
|
||||||
}
|
|
||||||
// join it with commas
|
|
||||||
boolean needsDelim = false;
|
|
||||||
StringBuilder ret = new StringBuilder();
|
|
||||||
for (String value : values)
|
|
||||||
{
|
|
||||||
if (needsDelim)
|
|
||||||
{
|
|
||||||
ret.append(", ");
|
|
||||||
}
|
|
||||||
QuoteUtil.quoteIfNeeded(ret,value,QuoteUtil.ABNF_REQUIRED_QUOTING);
|
|
||||||
needsDelim = true;
|
|
||||||
}
|
|
||||||
return ret.toString();
|
|
||||||
}
|
|
||||||
|
|
||||||
public Set<String> getHeaderNames()
|
/**
|
||||||
{
|
* Get the header names
|
||||||
return headers.keySet();
|
*
|
||||||
}
|
* @return the set of header names
|
||||||
|
*/
|
||||||
|
Set<String> getHeaderNames();
|
||||||
|
|
||||||
public Map<String, List<String>> getHeaders()
|
/**
|
||||||
{
|
* Get the headers map
|
||||||
return headers;
|
*
|
||||||
}
|
* @return the map of headers
|
||||||
|
*/
|
||||||
|
Map<String, List<String>> getHeaders();
|
||||||
|
|
||||||
public List<String> getHeaders(String name)
|
/**
|
||||||
{
|
* Get the multi-value header value
|
||||||
return headers.get(name);
|
*
|
||||||
}
|
* @param name the header name
|
||||||
|
* @return the list of values (null if header doesn't exist)
|
||||||
|
*/
|
||||||
|
List<String> getHeaders(String name);
|
||||||
|
|
||||||
public int getStatusCode()
|
/**
|
||||||
{
|
* Get the HTTP Response Status Code
|
||||||
return statusCode;
|
*
|
||||||
}
|
* @return the status code
|
||||||
|
*/
|
||||||
|
int getStatusCode();
|
||||||
|
|
||||||
public String getStatusReason()
|
/**
|
||||||
{
|
* Get the HTTP Response Status Reason
|
||||||
return statusReason;
|
*
|
||||||
}
|
* @return the HTTP Response status reason
|
||||||
|
*/
|
||||||
|
String getStatusReason();
|
||||||
|
|
||||||
public boolean isSuccess()
|
/**
|
||||||
{
|
* Test if upgrade response is successful.
|
||||||
return success;
|
* <p>
|
||||||
}
|
* Merely notes if the response was sent as a WebSocket Upgrade,
|
||||||
|
* or was failed (resulting in no upgrade handshake)
|
||||||
|
*
|
||||||
|
* @return true if upgrade response was generated, false if no upgrade response was generated
|
||||||
|
*/
|
||||||
|
boolean isSuccess();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Issue a forbidden upgrade response.
|
* Issue a forbidden upgrade response.
|
||||||
|
@ -142,67 +115,69 @@ public class UpgradeResponse
|
||||||
* Use this when the origin or authentication is invalid.
|
* Use this when the origin or authentication is invalid.
|
||||||
*
|
*
|
||||||
* @param message
|
* @param message
|
||||||
* the short 1 line detail message about the forbidden response
|
* the short 1 line detail message about the forbidden response
|
||||||
* @throws IOException
|
* @throws IOException
|
||||||
* if unable to send the forbidden
|
* if unable to send the forbidden
|
||||||
*/
|
*/
|
||||||
public void sendForbidden(String message) throws IOException
|
void sendForbidden(String message) throws IOException;
|
||||||
{
|
|
||||||
throw new UnsupportedOperationException("Not supported");
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Set the accepted WebSocket Protocol.
|
* Set the accepted WebSocket Protocol.
|
||||||
*
|
*
|
||||||
* @param protocol
|
* @param protocol
|
||||||
* the protocol to list as accepted
|
* the protocol to list as accepted
|
||||||
*/
|
*/
|
||||||
public void setAcceptedSubProtocol(String protocol)
|
void setAcceptedSubProtocol(String protocol);
|
||||||
{
|
|
||||||
setHeader(SEC_WEBSOCKET_PROTOCOL,protocol);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Set the list of extensions that are approved for use with this websocket.
|
* Set the list of extensions that are approved for use with this websocket.
|
||||||
* <p>
|
* <p>
|
||||||
* Notes:
|
* Notes:
|
||||||
* <ul>
|
* <ul>
|
||||||
* <li>Per the spec you cannot add extensions that have not been seen in the {@link UpgradeRequest}, just remove entries you don't want to use</li>
|
* <li>Per the spec you cannot add extensions that have not been seen in the {@link UpgradeRequest}, just remove
|
||||||
* <li>If this is unused, or a null is passed, then the list negotiation will follow default behavior and use the complete list of extensions that are
|
* entries you don't want to use</li>
|
||||||
|
* <li>If this is unused, or a null is passed, then the list negotiation will follow default behavior and use the
|
||||||
|
* complete list of extensions that are
|
||||||
* available in this WebSocket server implementation.</li>
|
* available in this WebSocket server implementation.</li>
|
||||||
* </ul>
|
* </ul>
|
||||||
*
|
*
|
||||||
* @param extensions
|
* @param extensions
|
||||||
* the list of extensions to use.
|
* the list of extensions to use.
|
||||||
*/
|
*/
|
||||||
public void setExtensions(List<ExtensionConfig> extensions)
|
void setExtensions(List<ExtensionConfig> extensions);
|
||||||
{
|
|
||||||
this.extensions.clear();
|
|
||||||
if (extensions != null)
|
|
||||||
{
|
|
||||||
this.extensions.addAll(extensions);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setHeader(String name, String value)
|
/**
|
||||||
{
|
* Set a header
|
||||||
List<String> values = new ArrayList<>();
|
* <p>
|
||||||
values.add(value);
|
* Overrides previous value of header (if set)
|
||||||
headers.put(name,values);
|
*
|
||||||
}
|
* @param name the header name
|
||||||
|
* @param value the header value
|
||||||
|
*/
|
||||||
|
void setHeader(String name, String value);
|
||||||
|
|
||||||
public void setStatusCode(int statusCode)
|
/**
|
||||||
{
|
* Set the HTTP Response status code
|
||||||
this.statusCode = statusCode;
|
*
|
||||||
}
|
* @param statusCode the status code
|
||||||
|
*/
|
||||||
|
void setStatusCode(int statusCode);
|
||||||
|
|
||||||
public void setStatusReason(String statusReason)
|
/**
|
||||||
{
|
* Set the HTTP Response status reason phrase
|
||||||
this.statusReason = statusReason;
|
* <p>
|
||||||
}
|
* Note, not all implementation of UpgradeResponse can support this feature
|
||||||
|
*
|
||||||
|
* @param statusReason the status reason phrase
|
||||||
|
*/
|
||||||
|
void setStatusReason(String statusReason);
|
||||||
|
|
||||||
public void setSuccess(boolean success)
|
/**
|
||||||
{
|
* Set the success of the upgrade response.
|
||||||
this.success = success;
|
* <p>
|
||||||
}
|
*
|
||||||
|
* @param success true to indicate a response to the upgrade handshake was sent, false to indicate no upgrade
|
||||||
|
* response was sent
|
||||||
|
*/
|
||||||
|
void setSuccess(boolean success);
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,27 @@
|
||||||
|
//
|
||||||
|
// ========================================================================
|
||||||
|
// Copyright (c) 1995-2016 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.api;
|
||||||
|
|
||||||
|
public final class WebSocketConstants
|
||||||
|
{
|
||||||
|
public static final String SEC_WEBSOCKET_EXTENSIONS = "Sec-WebSocket-Extensions";
|
||||||
|
public static final String SEC_WEBSOCKET_PROTOCOL = "Sec-WebSocket-Protocol";
|
||||||
|
public static final String SEC_WEBSOCKET_VERSION = "Sec-WebSocket-Version";
|
||||||
|
public static final int SPEC_VERSION = 13;
|
||||||
|
}
|
|
@ -25,6 +25,11 @@
|
||||||
<artifactId>jetty-io</artifactId>
|
<artifactId>jetty-io</artifactId>
|
||||||
<version>${project.version}</version>
|
<version>${project.version}</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.eclipse.jetty</groupId>
|
||||||
|
<artifactId>jetty-client</artifactId>
|
||||||
|
<version>${project.version}</version>
|
||||||
|
</dependency>
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.eclipse.jetty.websocket</groupId>
|
<groupId>org.eclipse.jetty.websocket</groupId>
|
||||||
<artifactId>websocket-common</artifactId>
|
<artifactId>websocket-common</artifactId>
|
||||||
|
|
|
@ -19,10 +19,10 @@
|
||||||
package org.eclipse.jetty.websocket.client;
|
package org.eclipse.jetty.websocket.client;
|
||||||
|
|
||||||
import java.net.CookieStore;
|
import java.net.CookieStore;
|
||||||
import java.net.HttpCookie;
|
|
||||||
import java.net.URI;
|
import java.net.URI;
|
||||||
import java.nio.charset.StandardCharsets;
|
import java.nio.charset.StandardCharsets;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
|
import java.util.Arrays;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
@ -30,22 +30,21 @@ import java.util.Set;
|
||||||
import java.util.TreeSet;
|
import java.util.TreeSet;
|
||||||
import java.util.concurrent.ThreadLocalRandom;
|
import java.util.concurrent.ThreadLocalRandom;
|
||||||
|
|
||||||
|
import org.eclipse.jetty.client.HttpClient;
|
||||||
|
import org.eclipse.jetty.http.HttpField;
|
||||||
|
import org.eclipse.jetty.http.HttpFields;
|
||||||
import org.eclipse.jetty.util.B64Code;
|
import org.eclipse.jetty.util.B64Code;
|
||||||
import org.eclipse.jetty.util.LazyList;
|
|
||||||
import org.eclipse.jetty.util.MultiMap;
|
import org.eclipse.jetty.util.MultiMap;
|
||||||
import org.eclipse.jetty.util.StringUtil;
|
import org.eclipse.jetty.util.StringUtil;
|
||||||
import org.eclipse.jetty.util.UrlEncoded;
|
import org.eclipse.jetty.util.UrlEncoded;
|
||||||
import org.eclipse.jetty.util.log.Log;
|
|
||||||
import org.eclipse.jetty.util.log.Logger;
|
|
||||||
import org.eclipse.jetty.websocket.api.UpgradeRequest;
|
|
||||||
import org.eclipse.jetty.websocket.api.extensions.ExtensionConfig;
|
import org.eclipse.jetty.websocket.api.extensions.ExtensionConfig;
|
||||||
|
import org.eclipse.jetty.websocket.common.UpgradeRequestAdapter;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Allowing a generate from a UpgradeRequest
|
* Allowing a generate from a UpgradeRequest
|
||||||
*/
|
*/
|
||||||
public class ClientUpgradeRequest extends UpgradeRequest
|
public class ClientUpgradeRequest extends UpgradeRequestAdapter
|
||||||
{
|
{
|
||||||
private static final Logger LOG = Log.getLogger(ClientUpgradeRequest.class);
|
|
||||||
private static final Set<String> FORBIDDEN_HEADERS;
|
private static final Set<String> FORBIDDEN_HEADERS;
|
||||||
|
|
||||||
static
|
static
|
||||||
|
@ -68,6 +67,7 @@ public class ClientUpgradeRequest extends UpgradeRequest
|
||||||
}
|
}
|
||||||
|
|
||||||
private final String key;
|
private final String key;
|
||||||
|
private Object localEndpoint;
|
||||||
|
|
||||||
public ClientUpgradeRequest()
|
public ClientUpgradeRequest()
|
||||||
{
|
{
|
||||||
|
@ -80,125 +80,46 @@ public class ClientUpgradeRequest extends UpgradeRequest
|
||||||
super(requestURI);
|
super(requestURI);
|
||||||
this.key = genRandomKey();
|
this.key = genRandomKey();
|
||||||
}
|
}
|
||||||
|
|
||||||
public String generate()
|
public ClientUpgradeRequest(WebSocketUpgradeRequest wsRequest)
|
||||||
{
|
{
|
||||||
URI uri = getRequestURI();
|
this(wsRequest.getURI());
|
||||||
|
// cookies
|
||||||
StringBuilder request = new StringBuilder(512);
|
this.setCookies(wsRequest.getCookies());
|
||||||
request.append("GET ");
|
// headers
|
||||||
if (StringUtil.isBlank(uri.getPath()))
|
Map<String, List<String>> headers = new HashMap<>();
|
||||||
|
HttpFields fields = wsRequest.getHeaders();
|
||||||
|
for (HttpField field : fields)
|
||||||
{
|
{
|
||||||
request.append("/");
|
String key = field.getName();
|
||||||
}
|
List<String> values = headers.get(key);
|
||||||
else
|
if (values == null)
|
||||||
{
|
|
||||||
request.append(uri.getPath());
|
|
||||||
}
|
|
||||||
if (StringUtil.isNotBlank(uri.getRawQuery()))
|
|
||||||
{
|
|
||||||
request.append("?").append(uri.getRawQuery());
|
|
||||||
}
|
|
||||||
request.append(" HTTP/1.1\r\n");
|
|
||||||
|
|
||||||
request.append("Host: ").append(uri.getHost());
|
|
||||||
if (uri.getPort() > 0)
|
|
||||||
{
|
|
||||||
request.append(':').append(uri.getPort());
|
|
||||||
}
|
|
||||||
request.append("\r\n");
|
|
||||||
|
|
||||||
// WebSocket specifics
|
|
||||||
request.append("Upgrade: websocket\r\n");
|
|
||||||
request.append("Connection: Upgrade\r\n");
|
|
||||||
request.append("Sec-WebSocket-Key: ").append(key).append("\r\n");
|
|
||||||
request.append("Sec-WebSocket-Version: 13\r\n"); // RFC-6455 specified version
|
|
||||||
|
|
||||||
// (Per the hybi list): Add no-cache headers to avoid compatibility issue.
|
|
||||||
// There are some proxies that rewrite "Connection: upgrade"
|
|
||||||
// to "Connection: close" in the response if a request doesn't contain
|
|
||||||
// these headers.
|
|
||||||
request.append("Pragma: no-cache\r\n");
|
|
||||||
request.append("Cache-Control: no-cache\r\n");
|
|
||||||
|
|
||||||
// Extensions
|
|
||||||
if (!getExtensions().isEmpty())
|
|
||||||
{
|
|
||||||
request.append("Sec-WebSocket-Extensions: ");
|
|
||||||
boolean needDelim = false;
|
|
||||||
for (ExtensionConfig ext : getExtensions())
|
|
||||||
{
|
{
|
||||||
if (needDelim)
|
values = new ArrayList<>();
|
||||||
{
|
|
||||||
request.append(", ");
|
|
||||||
}
|
|
||||||
request.append(ext.getParameterizedName());
|
|
||||||
needDelim = true;
|
|
||||||
}
|
}
|
||||||
request.append("\r\n");
|
values.addAll(Arrays.asList(field.getValues()));
|
||||||
}
|
headers.put(key,values);
|
||||||
|
// sub protocols
|
||||||
// Sub Protocols
|
if(key.equalsIgnoreCase("Sec-WebSocket-Protocol"))
|
||||||
if (!getSubProtocols().isEmpty())
|
|
||||||
{
|
|
||||||
request.append("Sec-WebSocket-Protocol: ");
|
|
||||||
boolean needDelim = false;
|
|
||||||
for (String protocol : getSubProtocols())
|
|
||||||
{
|
{
|
||||||
if (needDelim)
|
for(String subProtocol: field.getValue().split(","))
|
||||||
{
|
{
|
||||||
request.append(", ");
|
setSubProtocols(subProtocol);
|
||||||
}
|
}
|
||||||
request.append(protocol);
|
|
||||||
needDelim = true;
|
|
||||||
}
|
}
|
||||||
request.append("\r\n");
|
// extensions
|
||||||
}
|
if(key.equalsIgnoreCase("Sec-WebSocket-Extensions"))
|
||||||
|
|
||||||
// Cookies
|
|
||||||
List<HttpCookie> cookies = getCookies();
|
|
||||||
if ((cookies != null) && (cookies.size() > 0))
|
|
||||||
{
|
|
||||||
request.append("Cookie: ");
|
|
||||||
boolean needDelim = false;
|
|
||||||
for (HttpCookie cookie : cookies)
|
|
||||||
{
|
{
|
||||||
if (needDelim)
|
for(ExtensionConfig ext: ExtensionConfig.parseList(field.getValues()))
|
||||||
{
|
{
|
||||||
request.append("; ");
|
addExtensions(ext);
|
||||||
}
|
}
|
||||||
|
|
||||||
request.append(cookie.getName()).append("=");
|
|
||||||
if (cookie.getVersion() == 1)
|
|
||||||
{
|
|
||||||
// must be enclosed with quotes
|
|
||||||
request.append('"').append(cookie.getValue()).append('"');
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
request.append(cookie.getValue());
|
|
||||||
}
|
|
||||||
needDelim = true;
|
|
||||||
}
|
}
|
||||||
request.append("\r\n");
|
|
||||||
}
|
}
|
||||||
|
super.setHeaders(headers);
|
||||||
// Other headers
|
// sessions
|
||||||
for (String key : getHeaders().keySet())
|
setHttpVersion(wsRequest.getVersion().toString());
|
||||||
{
|
setMethod(wsRequest.getMethod());
|
||||||
if (FORBIDDEN_HEADERS.contains(key))
|
|
||||||
{
|
|
||||||
LOG.debug("Skipping forbidden header - {}",key);
|
|
||||||
continue; // skip
|
|
||||||
}
|
|
||||||
request.append(key).append(": ");
|
|
||||||
request.append(getHeader(key));
|
|
||||||
request.append("\r\n");
|
|
||||||
}
|
|
||||||
|
|
||||||
// request header end
|
|
||||||
request.append("\r\n");
|
|
||||||
return request.toString();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private final String genRandomKey()
|
private final String genRandomKey()
|
||||||
|
@ -213,26 +134,14 @@ public class ClientUpgradeRequest extends UpgradeRequest
|
||||||
return key;
|
return key;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param cookieStore the cookie store to use
|
||||||
|
* @deprecated use either {@link WebSocketClient#setCookieStore(CookieStore)} or {@link HttpClient#setCookieStore(CookieStore)} instead
|
||||||
|
*/
|
||||||
|
@Deprecated
|
||||||
public void setCookiesFrom(CookieStore cookieStore)
|
public void setCookiesFrom(CookieStore cookieStore)
|
||||||
{
|
{
|
||||||
if (cookieStore == null)
|
throw new UnsupportedOperationException("Request specific CookieStore no longer supported");
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
List<HttpCookie> existing = getCookies();
|
|
||||||
List<HttpCookie> extra = cookieStore.get(getRequestURI());
|
|
||||||
|
|
||||||
List<HttpCookie> cookies = new ArrayList<>();
|
|
||||||
if (LazyList.hasEntry(existing))
|
|
||||||
{
|
|
||||||
cookies.addAll(existing);
|
|
||||||
}
|
|
||||||
if (LazyList.hasEntry(extra))
|
|
||||||
{
|
|
||||||
cookies.addAll(extra);
|
|
||||||
}
|
|
||||||
setCookies(cookies);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -269,4 +178,14 @@ public class ClientUpgradeRequest extends UpgradeRequest
|
||||||
super.setParameterMap(pmap);
|
super.setParameterMap(pmap);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void setLocalEndpoint(Object websocket)
|
||||||
|
{
|
||||||
|
this.localEndpoint = websocket;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Object getLocalEndpoint()
|
||||||
|
{
|
||||||
|
return localEndpoint;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -19,27 +19,46 @@
|
||||||
package org.eclipse.jetty.websocket.client;
|
package org.eclipse.jetty.websocket.client;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.nio.ByteBuffer;
|
import java.util.List;
|
||||||
|
|
||||||
import org.eclipse.jetty.util.BufferUtil;
|
import org.eclipse.jetty.client.HttpResponse;
|
||||||
import org.eclipse.jetty.util.log.Log;
|
import org.eclipse.jetty.http.HttpField;
|
||||||
import org.eclipse.jetty.util.log.Logger;
|
import org.eclipse.jetty.http.HttpFields;
|
||||||
import org.eclipse.jetty.websocket.api.UpgradeResponse;
|
import org.eclipse.jetty.http.HttpHeader;
|
||||||
import org.eclipse.jetty.websocket.common.io.http.HttpResponseHeaderParseListener;
|
import org.eclipse.jetty.websocket.api.extensions.ExtensionConfig;
|
||||||
|
import org.eclipse.jetty.websocket.common.UpgradeResponseAdapter;
|
||||||
|
|
||||||
public class ClientUpgradeResponse extends UpgradeResponse implements HttpResponseHeaderParseListener
|
public class ClientUpgradeResponse extends UpgradeResponseAdapter
|
||||||
{
|
{
|
||||||
private static final Logger LOG = Log.getLogger(ClientUpgradeResponse.class);
|
private List<ExtensionConfig> extensions;
|
||||||
private ByteBuffer remainingBuffer;
|
|
||||||
|
|
||||||
public ClientUpgradeResponse()
|
public ClientUpgradeResponse()
|
||||||
{
|
{
|
||||||
super();
|
super();
|
||||||
}
|
}
|
||||||
|
|
||||||
public ByteBuffer getRemainingBuffer()
|
public ClientUpgradeResponse(HttpResponse response)
|
||||||
{
|
{
|
||||||
return remainingBuffer;
|
super();
|
||||||
|
setStatusCode(response.getStatus());
|
||||||
|
setStatusReason(response.getReason());
|
||||||
|
|
||||||
|
HttpFields fields = response.getHeaders();
|
||||||
|
for (HttpField field : fields)
|
||||||
|
{
|
||||||
|
addHeader(field.getName(),field.getValue());
|
||||||
|
}
|
||||||
|
|
||||||
|
HttpField extensionsField = fields.getField(HttpHeader.SEC_WEBSOCKET_EXTENSIONS);
|
||||||
|
if (extensionsField != null)
|
||||||
|
this.extensions = ExtensionConfig.parseList(extensionsField.getValues());
|
||||||
|
setAcceptedSubProtocol(fields.get(HttpHeader.SEC_WEBSOCKET_SUBPROTOCOL));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<ExtensionConfig> getExtensions()
|
||||||
|
{
|
||||||
|
return this.extensions;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -47,14 +66,4 @@ public class ClientUpgradeResponse extends UpgradeResponse implements HttpRespon
|
||||||
{
|
{
|
||||||
throw new UnsupportedOperationException("Not supported on client implementation");
|
throw new UnsupportedOperationException("Not supported on client implementation");
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public void setRemainingBuffer(ByteBuffer remainingBuffer)
|
|
||||||
{
|
|
||||||
if (LOG.isDebugEnabled())
|
|
||||||
{
|
|
||||||
LOG.debug("Saving remaining header: {}",BufferUtil.toDetailString(remainingBuffer));
|
|
||||||
}
|
|
||||||
this.remainingBuffer = remainingBuffer;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,29 @@
|
||||||
|
//
|
||||||
|
// ========================================================================
|
||||||
|
// Copyright (c) 1995-2016 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.client;
|
||||||
|
|
||||||
|
import org.eclipse.jetty.websocket.api.WebSocketAdapter;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* WebSocket endpoint that does nothing.
|
||||||
|
*/
|
||||||
|
public class NoOpEndpoint extends WebSocketAdapter
|
||||||
|
{
|
||||||
|
/* does nothing */
|
||||||
|
}
|
|
@ -28,26 +28,23 @@ import java.util.Locale;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
import java.util.concurrent.Executor;
|
import java.util.concurrent.Executor;
|
||||||
import java.util.concurrent.Future;
|
import java.util.concurrent.Future;
|
||||||
|
import java.util.concurrent.ThreadLocalRandom;
|
||||||
|
|
||||||
|
import org.eclipse.jetty.client.HttpClient;
|
||||||
import org.eclipse.jetty.io.ByteBufferPool;
|
import org.eclipse.jetty.io.ByteBufferPool;
|
||||||
import org.eclipse.jetty.io.MappedByteBufferPool;
|
import org.eclipse.jetty.io.MappedByteBufferPool;
|
||||||
import org.eclipse.jetty.io.SelectorManager;
|
|
||||||
import org.eclipse.jetty.util.DecoratedObjectFactory;
|
import org.eclipse.jetty.util.DecoratedObjectFactory;
|
||||||
import org.eclipse.jetty.util.HttpCookieStore;
|
|
||||||
import org.eclipse.jetty.util.StringUtil;
|
import org.eclipse.jetty.util.StringUtil;
|
||||||
import org.eclipse.jetty.util.component.ContainerLifeCycle;
|
import org.eclipse.jetty.util.component.ContainerLifeCycle;
|
||||||
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.util.ssl.SslContextFactory;
|
import org.eclipse.jetty.util.ssl.SslContextFactory;
|
||||||
import org.eclipse.jetty.util.thread.QueuedThreadPool;
|
|
||||||
import org.eclipse.jetty.util.thread.ScheduledExecutorScheduler;
|
|
||||||
import org.eclipse.jetty.util.thread.Scheduler;
|
import org.eclipse.jetty.util.thread.Scheduler;
|
||||||
import org.eclipse.jetty.util.thread.ShutdownThread;
|
import org.eclipse.jetty.util.thread.ShutdownThread;
|
||||||
import org.eclipse.jetty.websocket.api.Session;
|
import org.eclipse.jetty.websocket.api.Session;
|
||||||
import org.eclipse.jetty.websocket.api.WebSocketPolicy;
|
import org.eclipse.jetty.websocket.api.WebSocketPolicy;
|
||||||
import org.eclipse.jetty.websocket.api.extensions.ExtensionConfig;
|
import org.eclipse.jetty.websocket.api.extensions.ExtensionConfig;
|
||||||
import org.eclipse.jetty.websocket.api.extensions.ExtensionFactory;
|
import org.eclipse.jetty.websocket.api.extensions.ExtensionFactory;
|
||||||
import org.eclipse.jetty.websocket.client.io.ConnectPromise;
|
|
||||||
import org.eclipse.jetty.websocket.client.io.ConnectionManager;
|
import org.eclipse.jetty.websocket.client.io.ConnectionManager;
|
||||||
import org.eclipse.jetty.websocket.client.io.UpgradeListener;
|
import org.eclipse.jetty.websocket.client.io.UpgradeListener;
|
||||||
import org.eclipse.jetty.websocket.client.masks.Masker;
|
import org.eclipse.jetty.websocket.client.masks.Masker;
|
||||||
|
@ -55,7 +52,6 @@ import org.eclipse.jetty.websocket.client.masks.RandomMasker;
|
||||||
import org.eclipse.jetty.websocket.common.SessionFactory;
|
import org.eclipse.jetty.websocket.common.SessionFactory;
|
||||||
import org.eclipse.jetty.websocket.common.WebSocketSession;
|
import org.eclipse.jetty.websocket.common.WebSocketSession;
|
||||||
import org.eclipse.jetty.websocket.common.WebSocketSessionFactory;
|
import org.eclipse.jetty.websocket.common.WebSocketSessionFactory;
|
||||||
import org.eclipse.jetty.websocket.common.events.EventDriver;
|
|
||||||
import org.eclipse.jetty.websocket.common.events.EventDriverFactory;
|
import org.eclipse.jetty.websocket.common.events.EventDriverFactory;
|
||||||
import org.eclipse.jetty.websocket.common.extensions.WebSocketExtensionFactory;
|
import org.eclipse.jetty.websocket.common.extensions.WebSocketExtensionFactory;
|
||||||
import org.eclipse.jetty.websocket.common.scopes.WebSocketContainerScope;
|
import org.eclipse.jetty.websocket.common.scopes.WebSocketContainerScope;
|
||||||
|
@ -67,95 +63,259 @@ public class WebSocketClient extends ContainerLifeCycle implements WebSocketCont
|
||||||
{
|
{
|
||||||
private static final Logger LOG = Log.getLogger(WebSocketClient.class);
|
private static final Logger LOG = Log.getLogger(WebSocketClient.class);
|
||||||
|
|
||||||
private final WebSocketPolicy policy = WebSocketPolicy.newClientPolicy();
|
// From HttpClient
|
||||||
private final SslContextFactory sslContextFactory;
|
private final HttpClient httpClient;
|
||||||
private final WebSocketExtensionFactory extensionRegistry;
|
|
||||||
private boolean daemon = false;
|
|
||||||
private EventDriverFactory eventDriverFactory;
|
|
||||||
private SessionFactory sessionFactory;
|
|
||||||
private ByteBufferPool bufferPool;
|
|
||||||
private Executor executor;
|
|
||||||
private DecoratedObjectFactory objectFactory;
|
|
||||||
private Scheduler scheduler;
|
|
||||||
private CookieStore cookieStore;
|
|
||||||
private ConnectionManager connectionManager;
|
|
||||||
private Masker masker;
|
|
||||||
private SocketAddress bindAddress;
|
|
||||||
private long connectTimeout = SelectorManager.DEFAULT_CONNECT_TIMEOUT;
|
|
||||||
private boolean dispatchIO = true;
|
|
||||||
|
|
||||||
|
// Other
|
||||||
|
private final WebSocketPolicy policy = WebSocketPolicy.newClientPolicy();
|
||||||
|
private final WebSocketExtensionFactory extensionRegistry;
|
||||||
|
private final EventDriverFactory eventDriverFactory;
|
||||||
|
private final SessionFactory sessionFactory;
|
||||||
|
private final DecoratedObjectFactory objectFactory;
|
||||||
|
private Masker masker;
|
||||||
|
|
||||||
|
private final int id = ThreadLocalRandom.current().nextInt();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Instantiate a WebSocketClient with defaults
|
||||||
|
*/
|
||||||
public WebSocketClient()
|
public WebSocketClient()
|
||||||
{
|
{
|
||||||
this((SslContextFactory)null,null);
|
// Create synthetic HttpClient
|
||||||
|
this(new HttpClient());
|
||||||
|
addBean(this.httpClient);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Instantiate a WebSocketClient using HttpClient for defaults
|
||||||
|
*
|
||||||
|
* @param httpClient
|
||||||
|
* the HttpClient to base internal defaults off of
|
||||||
|
*/
|
||||||
|
public WebSocketClient(HttpClient httpClient)
|
||||||
|
{
|
||||||
|
this(httpClient,new DecoratedObjectFactory());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Instantiate a WebSocketClient using HttpClient for defaults
|
||||||
|
*
|
||||||
|
* @param httpClient
|
||||||
|
* the HttpClient to base internal defaults off of
|
||||||
|
* @param objectFactory
|
||||||
|
* the DecoratedObjectFactory for all client instantiated classes
|
||||||
|
*/
|
||||||
|
public WebSocketClient(HttpClient httpClient, DecoratedObjectFactory objectFactory)
|
||||||
|
{
|
||||||
|
this.httpClient = httpClient;
|
||||||
|
this.objectFactory = objectFactory;
|
||||||
|
this.extensionRegistry = new WebSocketExtensionFactory(this);
|
||||||
|
this.masker = new RandomMasker();
|
||||||
|
this.eventDriverFactory = new EventDriverFactory(policy);
|
||||||
|
this.sessionFactory = new WebSocketSessionFactory(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a new WebSocketClient
|
||||||
|
*
|
||||||
|
* @param executor
|
||||||
|
* the executor to use
|
||||||
|
* @deprecated use {@link #WebSocketClient(HttpClient)} instead
|
||||||
|
*/
|
||||||
|
@Deprecated
|
||||||
public WebSocketClient(Executor executor)
|
public WebSocketClient(Executor executor)
|
||||||
{
|
{
|
||||||
this(null,executor);
|
this(null,executor);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a new WebSocketClient
|
||||||
|
*
|
||||||
|
* @param bufferPool
|
||||||
|
* byte buffer pool to use
|
||||||
|
* @deprecated use {@link #WebSocketClient(HttpClient)} instead
|
||||||
|
*/
|
||||||
|
@Deprecated
|
||||||
public WebSocketClient(ByteBufferPool bufferPool)
|
public WebSocketClient(ByteBufferPool bufferPool)
|
||||||
{
|
{
|
||||||
this(null,null,bufferPool);
|
this(null,null,bufferPool);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a new WebSocketClient
|
||||||
|
*
|
||||||
|
* @param sslContextFactory
|
||||||
|
* ssl context factory to use
|
||||||
|
* @deprecated use {@link #WebSocketClient(HttpClient)} instead
|
||||||
|
*/
|
||||||
|
@Deprecated
|
||||||
public WebSocketClient(SslContextFactory sslContextFactory)
|
public WebSocketClient(SslContextFactory sslContextFactory)
|
||||||
{
|
{
|
||||||
this(sslContextFactory,null);
|
this(sslContextFactory,null);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a new WebSocketClient
|
||||||
|
*
|
||||||
|
* @param sslContextFactory
|
||||||
|
* ssl context factory to use
|
||||||
|
* @param executor
|
||||||
|
* the executor to use
|
||||||
|
* @deprecated use {@link #WebSocketClient(HttpClient)} instead
|
||||||
|
*/
|
||||||
|
@Deprecated
|
||||||
public WebSocketClient(SslContextFactory sslContextFactory, Executor executor)
|
public WebSocketClient(SslContextFactory sslContextFactory, Executor executor)
|
||||||
{
|
{
|
||||||
this(sslContextFactory,executor,new MappedByteBufferPool());
|
this(sslContextFactory,executor,new MappedByteBufferPool());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create WebSocketClient other Container Scope, to allow sharing of
|
||||||
|
* internal features like Executor, ByteBufferPool, SSLContextFactory, etc.
|
||||||
|
*
|
||||||
|
* @param scope
|
||||||
|
* the Container Scope
|
||||||
|
*/
|
||||||
public WebSocketClient(WebSocketContainerScope scope)
|
public WebSocketClient(WebSocketContainerScope scope)
|
||||||
{
|
{
|
||||||
this(scope.getSslContextFactory(), scope.getExecutor(), scope.getBufferPool(), scope.getObjectFactory());
|
this(scope.getSslContextFactory(),scope.getExecutor(),scope.getBufferPool(),scope.getObjectFactory());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create WebSocketClient other Container Scope, to allow sharing of
|
||||||
|
* internal features like Executor, ByteBufferPool, SSLContextFactory, etc.
|
||||||
|
*
|
||||||
|
* @param scope
|
||||||
|
* the Container Scope
|
||||||
|
* @param sslContextFactory
|
||||||
|
* SSL ContextFactory to use in preference to one from
|
||||||
|
* {@link WebSocketContainerScope#getSslContextFactory()}
|
||||||
|
*/
|
||||||
public WebSocketClient(WebSocketContainerScope scope, SslContextFactory sslContextFactory)
|
public WebSocketClient(WebSocketContainerScope scope, SslContextFactory sslContextFactory)
|
||||||
{
|
{
|
||||||
this(sslContextFactory, scope.getExecutor(), scope.getBufferPool(), scope.getObjectFactory());
|
this(sslContextFactory,scope.getExecutor(),scope.getBufferPool(),scope.getObjectFactory());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create WebSocketClient using sharing instances of SSLContextFactory
|
||||||
|
* Executor, and ByteBufferPool
|
||||||
|
*
|
||||||
|
* @param sslContextFactory
|
||||||
|
* shared SSL ContextFactory
|
||||||
|
* @param executor
|
||||||
|
* shared Executor
|
||||||
|
* @param bufferPool
|
||||||
|
* shared ByteBufferPool
|
||||||
|
*/
|
||||||
public WebSocketClient(SslContextFactory sslContextFactory, Executor executor, ByteBufferPool bufferPool)
|
public WebSocketClient(SslContextFactory sslContextFactory, Executor executor, ByteBufferPool bufferPool)
|
||||||
{
|
{
|
||||||
this(sslContextFactory, executor, bufferPool, new DecoratedObjectFactory());
|
this(sslContextFactory,executor,bufferPool,new DecoratedObjectFactory());
|
||||||
}
|
}
|
||||||
|
|
||||||
public WebSocketClient(SslContextFactory sslContextFactory, Executor executor, ByteBufferPool bufferPool, DecoratedObjectFactory objectFactory)
|
/**
|
||||||
|
* Create WebSocketClient using sharing instances of SSLContextFactory
|
||||||
|
* Executor, and ByteBufferPool
|
||||||
|
*
|
||||||
|
* @param sslContextFactory
|
||||||
|
* shared SSL ContextFactory
|
||||||
|
* @param executor
|
||||||
|
* shared Executor
|
||||||
|
* @param bufferPool
|
||||||
|
* shared ByteBufferPool
|
||||||
|
* @param objectFactory
|
||||||
|
* shared DecoratedObjectFactory
|
||||||
|
*/
|
||||||
|
private WebSocketClient(SslContextFactory sslContextFactory, Executor executor, ByteBufferPool bufferPool, DecoratedObjectFactory objectFactory)
|
||||||
{
|
{
|
||||||
|
this.httpClient = new HttpClient(sslContextFactory);
|
||||||
|
this.httpClient.setExecutor(executor);
|
||||||
|
this.httpClient.setByteBufferPool(bufferPool);
|
||||||
|
addBean(this.httpClient);
|
||||||
|
|
||||||
this.sslContextFactory = sslContextFactory;
|
if (objectFactory == null)
|
||||||
if(sslContextFactory!=null)
|
this.objectFactory = new DecoratedObjectFactory();
|
||||||
addBean(sslContextFactory);
|
else
|
||||||
setExecutor(executor);
|
this.objectFactory = objectFactory;
|
||||||
setBufferPool(bufferPool);
|
|
||||||
|
|
||||||
this.objectFactory = objectFactory;
|
|
||||||
this.extensionRegistry = new WebSocketExtensionFactory(this);
|
|
||||||
|
|
||||||
this.masker = new RandomMasker();
|
|
||||||
this.eventDriverFactory = new EventDriverFactory(policy);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
this.extensionRegistry = new WebSocketExtensionFactory(this);
|
||||||
|
|
||||||
|
this.masker = new RandomMasker();
|
||||||
|
this.eventDriverFactory = new EventDriverFactory(policy);
|
||||||
|
this.sessionFactory = new WebSocketSessionFactory(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create WebSocketClient based on pre-existing Container Scope, to allow sharing of
|
||||||
|
* internal features like Executor, ByteBufferPool, SSLContextFactory, etc.
|
||||||
|
*
|
||||||
|
* @param scope
|
||||||
|
* the Container Scope
|
||||||
|
* @param eventDriverFactory
|
||||||
|
* the EventDriver Factory to use
|
||||||
|
* @param sessionFactory
|
||||||
|
* the SessionFactory to use
|
||||||
|
*/
|
||||||
|
public WebSocketClient(WebSocketContainerScope scope, EventDriverFactory eventDriverFactory, SessionFactory sessionFactory)
|
||||||
|
{
|
||||||
|
this.httpClient = new HttpClient(scope.getSslContextFactory());
|
||||||
|
this.httpClient.setExecutor(scope.getExecutor());
|
||||||
|
addBean(this.httpClient);
|
||||||
|
|
||||||
|
this.objectFactory = new DecoratedObjectFactory();
|
||||||
|
this.extensionRegistry = new WebSocketExtensionFactory(this);
|
||||||
|
|
||||||
|
this.masker = new RandomMasker();
|
||||||
|
this.eventDriverFactory = eventDriverFactory;
|
||||||
|
this.sessionFactory = sessionFactory;
|
||||||
|
}
|
||||||
|
|
||||||
public Future<Session> connect(Object websocket, URI toUri) throws IOException
|
public Future<Session> connect(Object websocket, URI toUri) throws IOException
|
||||||
{
|
{
|
||||||
ClientUpgradeRequest request = new ClientUpgradeRequest(toUri);
|
ClientUpgradeRequest request = new ClientUpgradeRequest(toUri);
|
||||||
request.setRequestURI(toUri);
|
request.setRequestURI(toUri);
|
||||||
request.setCookiesFrom(this.cookieStore);
|
request.setLocalEndpoint(websocket);
|
||||||
|
|
||||||
return connect(websocket,toUri,request);
|
return connect(websocket,toUri,request);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Connect to remote websocket endpoint
|
||||||
|
*
|
||||||
|
* @param websocket
|
||||||
|
* the websocket object
|
||||||
|
* @param toUri
|
||||||
|
* the websocket uri to connect to
|
||||||
|
* @param request
|
||||||
|
* the upgrade request information
|
||||||
|
* @return the future for the session, available on success of connect
|
||||||
|
* @throws IOException
|
||||||
|
* if unable to connect
|
||||||
|
*/
|
||||||
public Future<Session> connect(Object websocket, URI toUri, ClientUpgradeRequest request) throws IOException
|
public Future<Session> connect(Object websocket, URI toUri, ClientUpgradeRequest request) throws IOException
|
||||||
{
|
{
|
||||||
return connect(websocket,toUri,request,null);
|
return connect(websocket,toUri,request,(UpgradeListener)null);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Connect to remote websocket endpoint
|
||||||
|
*
|
||||||
|
* @param websocket
|
||||||
|
* the websocket object
|
||||||
|
* @param toUri
|
||||||
|
* the websocket uri to connect to
|
||||||
|
* @param request
|
||||||
|
* the upgrade request information
|
||||||
|
* @param upgradeListener
|
||||||
|
* the upgrade listener
|
||||||
|
* @return the future for the session, available on success of connect
|
||||||
|
* @throws IOException
|
||||||
|
* if unable to connect
|
||||||
|
*/
|
||||||
public Future<Session> connect(Object websocket, URI toUri, ClientUpgradeRequest request, UpgradeListener upgradeListener) throws IOException
|
public Future<Session> connect(Object websocket, URI toUri, ClientUpgradeRequest request, UpgradeListener upgradeListener) throws IOException
|
||||||
{
|
{
|
||||||
|
/* Note: UpgradeListener is used by javax.websocket.ClientEndpointConfig.Configurator
|
||||||
|
* See: org.eclipse.jetty.websocket.jsr356.JsrUpgradeListener
|
||||||
|
*/
|
||||||
if (!isStarted())
|
if (!isStarted())
|
||||||
{
|
{
|
||||||
throw new IllegalStateException(WebSocketClient.class.getSimpleName() + "@" + this.hashCode() + " is not started");
|
throw new IllegalStateException(WebSocketClient.class.getSimpleName() + "@" + this.hashCode() + " is not started");
|
||||||
|
@ -179,7 +339,7 @@ public class WebSocketClient extends ContainerLifeCycle implements WebSocketCont
|
||||||
}
|
}
|
||||||
|
|
||||||
request.setRequestURI(toUri);
|
request.setRequestURI(toUri);
|
||||||
request.setCookiesFrom(this.cookieStore);
|
request.setLocalEndpoint(websocket);
|
||||||
|
|
||||||
// Validate Requested Extensions
|
// Validate Requested Extensions
|
||||||
for (ExtensionConfig reqExt : request.getExtensions())
|
for (ExtensionConfig reqExt : request.getExtensions())
|
||||||
|
@ -193,84 +353,12 @@ public class WebSocketClient extends ContainerLifeCycle implements WebSocketCont
|
||||||
if (LOG.isDebugEnabled())
|
if (LOG.isDebugEnabled())
|
||||||
LOG.debug("connect websocket {} to {}",websocket,toUri);
|
LOG.debug("connect websocket {} to {}",websocket,toUri);
|
||||||
|
|
||||||
// Grab Connection Manager
|
init();
|
||||||
initializeClient();
|
|
||||||
ConnectionManager manager = getConnectionManager();
|
WebSocketUpgradeRequest wsReq = new WebSocketUpgradeRequest(this,httpClient,request);
|
||||||
|
|
||||||
// Setup Driver for user provided websocket
|
|
||||||
EventDriver driver = null;
|
|
||||||
if (websocket instanceof EventDriver)
|
|
||||||
{
|
|
||||||
// Use the EventDriver as-is
|
|
||||||
driver = (EventDriver)websocket;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
// Wrap websocket with appropriate EventDriver
|
|
||||||
driver = eventDriverFactory.wrap(websocket);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (driver == null)
|
|
||||||
{
|
|
||||||
throw new IllegalStateException("Unable to identify as websocket object: " + websocket.getClass().getName());
|
|
||||||
}
|
|
||||||
|
|
||||||
// Create the appropriate (physical vs virtual) connection task
|
|
||||||
ConnectPromise promise = manager.connect(this,driver,request);
|
|
||||||
|
|
||||||
if (upgradeListener != null)
|
|
||||||
{
|
|
||||||
promise.setUpgradeListener(upgradeListener);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (LOG.isDebugEnabled())
|
|
||||||
LOG.debug("Connect Promise: {}",promise);
|
|
||||||
|
|
||||||
// Execute the connection on the executor thread
|
|
||||||
executor.execute(promise);
|
|
||||||
|
|
||||||
// Return the future
|
|
||||||
return promise;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void doStart() throws Exception
|
|
||||||
{
|
|
||||||
if (LOG.isDebugEnabled())
|
|
||||||
LOG.debug("Starting {}",this);
|
|
||||||
|
|
||||||
String name = WebSocketClient.class.getSimpleName() + "@" + hashCode();
|
|
||||||
|
|
||||||
if (bufferPool == null)
|
|
||||||
{
|
|
||||||
setBufferPool(new MappedByteBufferPool());
|
|
||||||
}
|
|
||||||
|
|
||||||
if (scheduler == null)
|
|
||||||
{
|
|
||||||
scheduler = new ScheduledExecutorScheduler(name + "-scheduler",daemon);
|
|
||||||
addBean(scheduler);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (cookieStore == null)
|
|
||||||
{
|
|
||||||
setCookieStore(new HttpCookieStore.Empty());
|
|
||||||
}
|
|
||||||
|
|
||||||
if(this.sessionFactory == null)
|
|
||||||
{
|
|
||||||
setSessionFactory(new WebSocketSessionFactory(this));
|
|
||||||
}
|
|
||||||
|
|
||||||
if(this.objectFactory == null)
|
wsReq.setUpgradeListener(upgradeListener);
|
||||||
{
|
return wsReq.sendAsync();
|
||||||
this.objectFactory = new DecoratedObjectFactory();
|
|
||||||
}
|
|
||||||
|
|
||||||
super.doStart();
|
|
||||||
|
|
||||||
if (LOG.isDebugEnabled())
|
|
||||||
LOG.debug("Started {}",this);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -287,19 +375,14 @@ public class WebSocketClient extends ContainerLifeCycle implements WebSocketCont
|
||||||
|
|
||||||
super.doStop();
|
super.doStop();
|
||||||
|
|
||||||
if (cookieStore != null)
|
|
||||||
{
|
|
||||||
cookieStore.removeAll();
|
|
||||||
cookieStore = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (LOG.isDebugEnabled())
|
if (LOG.isDebugEnabled())
|
||||||
LOG.debug("Stopped {}",this);
|
LOG.debug("Stopped {}",this);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Deprecated
|
||||||
public boolean isDispatchIO()
|
public boolean isDispatchIO()
|
||||||
{
|
{
|
||||||
return dispatchIO;
|
return httpClient.isDispatchIO();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -314,29 +397,30 @@ public class WebSocketClient extends ContainerLifeCycle implements WebSocketCont
|
||||||
|
|
||||||
public SocketAddress getBindAddress()
|
public SocketAddress getBindAddress()
|
||||||
{
|
{
|
||||||
return bindAddress;
|
return httpClient.getBindAddress();
|
||||||
}
|
}
|
||||||
|
|
||||||
public ByteBufferPool getBufferPool()
|
public ByteBufferPool getBufferPool()
|
||||||
{
|
{
|
||||||
return bufferPool;
|
return httpClient.getByteBufferPool();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Deprecated
|
||||||
public ConnectionManager getConnectionManager()
|
public ConnectionManager getConnectionManager()
|
||||||
{
|
{
|
||||||
return connectionManager;
|
throw new UnsupportedOperationException("ConnectionManager is no longer supported");
|
||||||
}
|
}
|
||||||
|
|
||||||
public long getConnectTimeout()
|
public long getConnectTimeout()
|
||||||
{
|
{
|
||||||
return connectTimeout;
|
return httpClient.getConnectTimeout();
|
||||||
}
|
}
|
||||||
|
|
||||||
public CookieStore getCookieStore()
|
public CookieStore getCookieStore()
|
||||||
{
|
{
|
||||||
return cookieStore;
|
return httpClient.getCookieStore();
|
||||||
}
|
}
|
||||||
|
|
||||||
public EventDriverFactory getEventDriverFactory()
|
public EventDriverFactory getEventDriverFactory()
|
||||||
{
|
{
|
||||||
return eventDriverFactory;
|
return eventDriverFactory;
|
||||||
|
@ -344,7 +428,7 @@ public class WebSocketClient extends ContainerLifeCycle implements WebSocketCont
|
||||||
|
|
||||||
public Executor getExecutor()
|
public Executor getExecutor()
|
||||||
{
|
{
|
||||||
return executor;
|
return httpClient.getExecutor();
|
||||||
}
|
}
|
||||||
|
|
||||||
public ExtensionFactory getExtensionFactory()
|
public ExtensionFactory getExtensionFactory()
|
||||||
|
@ -425,9 +509,9 @@ public class WebSocketClient extends ContainerLifeCycle implements WebSocketCont
|
||||||
|
|
||||||
public Scheduler getScheduler()
|
public Scheduler getScheduler()
|
||||||
{
|
{
|
||||||
return scheduler;
|
return httpClient.getScheduler();
|
||||||
}
|
}
|
||||||
|
|
||||||
public SessionFactory getSessionFactory()
|
public SessionFactory getSessionFactory()
|
||||||
{
|
{
|
||||||
return sessionFactory;
|
return sessionFactory;
|
||||||
|
@ -439,45 +523,27 @@ public class WebSocketClient extends ContainerLifeCycle implements WebSocketCont
|
||||||
*/
|
*/
|
||||||
public SslContextFactory getSslContextFactory()
|
public SslContextFactory getSslContextFactory()
|
||||||
{
|
{
|
||||||
return sslContextFactory;
|
return httpClient.getSslContextFactory();
|
||||||
}
|
}
|
||||||
|
|
||||||
private synchronized void initializeClient() throws IOException
|
private synchronized void init() throws IOException
|
||||||
{
|
{
|
||||||
if (!ShutdownThread.isRegistered(this))
|
if (!ShutdownThread.isRegistered(this))
|
||||||
{
|
{
|
||||||
ShutdownThread.register(this);
|
ShutdownThread.register(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (executor == null)
|
|
||||||
{
|
|
||||||
QueuedThreadPool threadPool = new QueuedThreadPool();
|
|
||||||
String name = WebSocketClient.class.getSimpleName() + "@" + hashCode();
|
|
||||||
threadPool.setName(name);
|
|
||||||
threadPool.setDaemon(daemon);
|
|
||||||
executor = threadPool;
|
|
||||||
addManaged(threadPool);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
addBean(executor,false);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (connectionManager == null)
|
|
||||||
{
|
|
||||||
connectionManager = newConnectionManager();
|
|
||||||
addManaged(connectionManager);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Factory method for new ConnectionManager (used by other projects like cometd)
|
* Factory method for new ConnectionManager
|
||||||
*
|
*
|
||||||
* @return the ConnectionManager instance to use
|
* @return the ConnectionManager instance to use
|
||||||
|
* @deprecated use HttpClient instead
|
||||||
*/
|
*/
|
||||||
|
@Deprecated
|
||||||
protected ConnectionManager newConnectionManager()
|
protected ConnectionManager newConnectionManager()
|
||||||
{
|
{
|
||||||
return new ConnectionManager(this);
|
throw new UnsupportedOperationException("ConnectionManager is no longer supported");
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -494,16 +560,18 @@ public class WebSocketClient extends ContainerLifeCycle implements WebSocketCont
|
||||||
if (LOG.isDebugEnabled())
|
if (LOG.isDebugEnabled())
|
||||||
LOG.debug("Session Opened: {}",session);
|
LOG.debug("Session Opened: {}",session);
|
||||||
addManaged(session);
|
addManaged(session);
|
||||||
|
LOG.debug("post-onSessionOpened() - {}", this);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setAsyncWriteTimeout(long ms)
|
public void setAsyncWriteTimeout(long ms)
|
||||||
{
|
{
|
||||||
this.policy.setAsyncWriteTimeout(ms);
|
this.policy.setAsyncWriteTimeout(ms);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param bindAddress the address to bind to
|
* @param bindAddress
|
||||||
* @deprecated use {@link #setBindAddress(SocketAddress)} instead
|
* the address to bind to
|
||||||
|
* @deprecated (this is a bad bad bad typo) use {@link #setBindAddress(SocketAddress)} instead
|
||||||
*/
|
*/
|
||||||
@Deprecated
|
@Deprecated
|
||||||
public void setBindAdddress(SocketAddress bindAddress)
|
public void setBindAdddress(SocketAddress bindAddress)
|
||||||
|
@ -513,13 +581,12 @@ public class WebSocketClient extends ContainerLifeCycle implements WebSocketCont
|
||||||
|
|
||||||
public void setBindAddress(SocketAddress bindAddress)
|
public void setBindAddress(SocketAddress bindAddress)
|
||||||
{
|
{
|
||||||
this.bindAddress = bindAddress;
|
this.httpClient.setBindAddress(bindAddress);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setBufferPool(ByteBufferPool bufferPool)
|
public void setBufferPool(ByteBufferPool bufferPool)
|
||||||
{
|
{
|
||||||
updateBean(this.bufferPool,bufferPool);
|
this.httpClient.setByteBufferPool(bufferPool);
|
||||||
this.bufferPool = bufferPool;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -530,38 +597,28 @@ public class WebSocketClient extends ContainerLifeCycle implements WebSocketCont
|
||||||
*/
|
*/
|
||||||
public void setConnectTimeout(long ms)
|
public void setConnectTimeout(long ms)
|
||||||
{
|
{
|
||||||
if (ms < 0)
|
this.httpClient.setConnectTimeout(ms);
|
||||||
{
|
|
||||||
throw new IllegalStateException("Connect Timeout cannot be negative");
|
|
||||||
}
|
|
||||||
this.connectTimeout = ms;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setCookieStore(CookieStore cookieStore)
|
public void setCookieStore(CookieStore cookieStore)
|
||||||
{
|
{
|
||||||
updateBean(this.cookieStore,cookieStore);
|
this.httpClient.setCookieStore(cookieStore);
|
||||||
this.cookieStore = cookieStore;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setDaemon(boolean daemon)
|
public void setDaemon(boolean daemon)
|
||||||
{
|
{
|
||||||
this.daemon = daemon;
|
// do nothing
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Deprecated
|
||||||
public void setDispatchIO(boolean dispatchIO)
|
public void setDispatchIO(boolean dispatchIO)
|
||||||
{
|
{
|
||||||
this.dispatchIO = dispatchIO;
|
this.httpClient.setDispatchIO(dispatchIO);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setEventDriverFactory(EventDriverFactory factory)
|
|
||||||
{
|
|
||||||
this.eventDriverFactory = factory;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setExecutor(Executor executor)
|
public void setExecutor(Executor executor)
|
||||||
{
|
{
|
||||||
updateBean(this.executor,executor);
|
this.httpClient.setExecutor(executor);
|
||||||
this.executor = executor;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setMasker(Masker masker)
|
public void setMasker(Masker masker)
|
||||||
|
@ -573,7 +630,7 @@ public class WebSocketClient extends ContainerLifeCycle implements WebSocketCont
|
||||||
{
|
{
|
||||||
this.policy.setMaxBinaryMessageBufferSize(max);
|
this.policy.setMaxBinaryMessageBufferSize(max);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Set the max idle timeout for new connections.
|
* Set the max idle timeout for new connections.
|
||||||
* <p>
|
* <p>
|
||||||
|
@ -585,6 +642,7 @@ public class WebSocketClient extends ContainerLifeCycle implements WebSocketCont
|
||||||
public void setMaxIdleTimeout(long ms)
|
public void setMaxIdleTimeout(long ms)
|
||||||
{
|
{
|
||||||
this.policy.setIdleTimeout(ms);
|
this.policy.setIdleTimeout(ms);
|
||||||
|
this.httpClient.setIdleTimeout(ms);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setMaxTextMessageBufferSize(int max)
|
public void setMaxTextMessageBufferSize(int max)
|
||||||
|
@ -592,16 +650,27 @@ public class WebSocketClient extends ContainerLifeCycle implements WebSocketCont
|
||||||
this.policy.setMaxTextMessageBufferSize(max);
|
this.policy.setMaxTextMessageBufferSize(max);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setSessionFactory(SessionFactory sessionFactory)
|
|
||||||
{
|
|
||||||
updateBean(this.sessionFactory,sessionFactory);
|
|
||||||
this.sessionFactory = sessionFactory;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void dump(Appendable out, String indent) throws IOException
|
public void dump(Appendable out, String indent) throws IOException
|
||||||
{
|
{
|
||||||
dumpThis(out);
|
dumpThis(out);
|
||||||
dump(out, indent, getOpenSessions());
|
dump(out,indent,getOpenSessions());
|
||||||
|
}
|
||||||
|
|
||||||
|
public HttpClient getHttpClient()
|
||||||
|
{
|
||||||
|
return this.httpClient;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString()
|
||||||
|
{
|
||||||
|
final StringBuilder sb = new StringBuilder("WebSocketClient@");
|
||||||
|
sb.append(Integer.toHexString(id));
|
||||||
|
sb.append("[httpClient=").append(httpClient);
|
||||||
|
sb.append(",openSessions.size=");
|
||||||
|
sb.append(getOpenSessions().size());
|
||||||
|
sb.append(']');
|
||||||
|
return sb.toString();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,634 @@
|
||||||
|
//
|
||||||
|
// ========================================================================
|
||||||
|
// Copyright (c) 1995-2016 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.client;
|
||||||
|
|
||||||
|
import java.net.HttpCookie;
|
||||||
|
import java.net.URI;
|
||||||
|
import java.nio.charset.StandardCharsets;
|
||||||
|
import java.security.Principal;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.TreeMap;
|
||||||
|
import java.util.concurrent.CompletableFuture;
|
||||||
|
import java.util.concurrent.ExecutionException;
|
||||||
|
import java.util.concurrent.ThreadLocalRandom;
|
||||||
|
import java.util.concurrent.TimeoutException;
|
||||||
|
|
||||||
|
import org.eclipse.jetty.client.HttpClient;
|
||||||
|
import org.eclipse.jetty.client.HttpConversation;
|
||||||
|
import org.eclipse.jetty.client.HttpRequest;
|
||||||
|
import org.eclipse.jetty.client.HttpResponse;
|
||||||
|
import org.eclipse.jetty.client.HttpResponseException;
|
||||||
|
import org.eclipse.jetty.client.api.ContentResponse;
|
||||||
|
import org.eclipse.jetty.client.api.Response;
|
||||||
|
import org.eclipse.jetty.client.api.Response.CompleteListener;
|
||||||
|
import org.eclipse.jetty.client.api.Result;
|
||||||
|
import org.eclipse.jetty.client.http.HttpConnectionOverHTTP;
|
||||||
|
import org.eclipse.jetty.client.http.HttpConnectionUpgrader;
|
||||||
|
import org.eclipse.jetty.http.HttpField;
|
||||||
|
import org.eclipse.jetty.http.HttpFields;
|
||||||
|
import org.eclipse.jetty.http.HttpHeader;
|
||||||
|
import org.eclipse.jetty.http.HttpMethod;
|
||||||
|
import org.eclipse.jetty.http.HttpStatus;
|
||||||
|
import org.eclipse.jetty.http.HttpVersion;
|
||||||
|
import org.eclipse.jetty.io.EndPoint;
|
||||||
|
import org.eclipse.jetty.util.B64Code;
|
||||||
|
import org.eclipse.jetty.util.MultiMap;
|
||||||
|
import org.eclipse.jetty.util.QuotedStringTokenizer;
|
||||||
|
import org.eclipse.jetty.util.StringUtil;
|
||||||
|
import org.eclipse.jetty.util.UrlEncoded;
|
||||||
|
import org.eclipse.jetty.util.log.Log;
|
||||||
|
import org.eclipse.jetty.util.log.Logger;
|
||||||
|
import org.eclipse.jetty.websocket.api.Session;
|
||||||
|
import org.eclipse.jetty.websocket.api.UpgradeException;
|
||||||
|
import org.eclipse.jetty.websocket.api.UpgradeRequest;
|
||||||
|
import org.eclipse.jetty.websocket.api.WebSocketConstants;
|
||||||
|
import org.eclipse.jetty.websocket.api.extensions.ExtensionConfig;
|
||||||
|
import org.eclipse.jetty.websocket.api.extensions.ExtensionFactory;
|
||||||
|
import org.eclipse.jetty.websocket.client.io.UpgradeListener;
|
||||||
|
import org.eclipse.jetty.websocket.client.io.WebSocketClientConnection;
|
||||||
|
import org.eclipse.jetty.websocket.common.AcceptHash;
|
||||||
|
import org.eclipse.jetty.websocket.common.SessionFactory;
|
||||||
|
import org.eclipse.jetty.websocket.common.WebSocketSession;
|
||||||
|
import org.eclipse.jetty.websocket.common.events.EventDriver;
|
||||||
|
import org.eclipse.jetty.websocket.common.extensions.ExtensionStack;
|
||||||
|
|
||||||
|
public class WebSocketUpgradeRequest extends HttpRequest implements CompleteListener, HttpConnectionUpgrader
|
||||||
|
{
|
||||||
|
private static final Logger LOG = Log.getLogger(WebSocketUpgradeRequest.class);
|
||||||
|
|
||||||
|
private class ClientUpgradeRequestFacade implements UpgradeRequest
|
||||||
|
{
|
||||||
|
private List<ExtensionConfig> extensions;
|
||||||
|
private List<String> subProtocols;
|
||||||
|
private Object session;
|
||||||
|
|
||||||
|
public ClientUpgradeRequestFacade()
|
||||||
|
{
|
||||||
|
this.extensions = new ArrayList<>();
|
||||||
|
this.subProtocols = new ArrayList<>();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void init(ClientUpgradeRequest request)
|
||||||
|
{
|
||||||
|
this.extensions = new ArrayList<>(request.getExtensions());
|
||||||
|
this.subProtocols = new ArrayList<>(request.getSubProtocols());
|
||||||
|
|
||||||
|
// Copy values from ClientUpgradeRequest into place
|
||||||
|
if (StringUtil.isNotBlank(request.getOrigin()))
|
||||||
|
header(HttpHeader.ORIGIN,request.getOrigin());
|
||||||
|
for (HttpCookie cookie : request.getCookies())
|
||||||
|
{
|
||||||
|
cookie(cookie);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<ExtensionConfig> getExtensions()
|
||||||
|
{
|
||||||
|
return extensions;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<String> getSubProtocols()
|
||||||
|
{
|
||||||
|
return subProtocols;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void addExtensions(ExtensionConfig... configs)
|
||||||
|
{
|
||||||
|
for (ExtensionConfig config : configs)
|
||||||
|
{
|
||||||
|
this.extensions.add(config);
|
||||||
|
}
|
||||||
|
updateExtensionHeader();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void addExtensions(String... configs)
|
||||||
|
{
|
||||||
|
this.extensions.addAll(ExtensionConfig.parseList(configs));
|
||||||
|
updateExtensionHeader();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void clearHeaders()
|
||||||
|
{
|
||||||
|
throw new UnsupportedOperationException("Clearing all headers breaks WebSocket upgrade");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getHeader(String name)
|
||||||
|
{
|
||||||
|
return getHttpFields().get(name);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getHeaderInt(String name)
|
||||||
|
{
|
||||||
|
String value = getHttpFields().get(name);
|
||||||
|
if(value == null) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
return Integer.parseInt(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<String> getHeaders(String name)
|
||||||
|
{
|
||||||
|
return getHttpFields().getValuesList(name);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getHttpVersion()
|
||||||
|
{
|
||||||
|
return getVersion().asString();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getOrigin()
|
||||||
|
{
|
||||||
|
return getHttpFields().get(HttpHeader.ORIGIN);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Map<String, List<String>> getParameterMap()
|
||||||
|
{
|
||||||
|
Map<String,List<String>> paramMap = new TreeMap<>(String.CASE_INSENSITIVE_ORDER);
|
||||||
|
|
||||||
|
String query = getQueryString();
|
||||||
|
MultiMap<String> multimap = new MultiMap<>();
|
||||||
|
UrlEncoded.decodeTo(query,multimap,StandardCharsets.UTF_8);
|
||||||
|
|
||||||
|
paramMap.putAll(multimap);
|
||||||
|
|
||||||
|
return paramMap;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getProtocolVersion()
|
||||||
|
{
|
||||||
|
String ver = getHttpFields().get(HttpHeader.SEC_WEBSOCKET_VERSION);
|
||||||
|
if (ver == null)
|
||||||
|
{
|
||||||
|
return Integer.toString(WebSocketConstants.SPEC_VERSION);
|
||||||
|
}
|
||||||
|
return ver;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getQueryString()
|
||||||
|
{
|
||||||
|
return getURI().getQuery();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public URI getRequestURI()
|
||||||
|
{
|
||||||
|
return getURI();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Object getSession()
|
||||||
|
{
|
||||||
|
return this.session;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Principal getUserPrincipal()
|
||||||
|
{
|
||||||
|
// HttpClient doesn't use Principal concepts
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean hasSubProtocol(String test)
|
||||||
|
{
|
||||||
|
return getSubProtocols().contains(test);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isOrigin(String test)
|
||||||
|
{
|
||||||
|
return test.equalsIgnoreCase(getOrigin());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isSecure()
|
||||||
|
{
|
||||||
|
// TODO: need to obtain information from actual request to know of SSL was used?
|
||||||
|
return "wss".equalsIgnoreCase(getURI().getScheme());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setCookies(List<HttpCookie> cookies)
|
||||||
|
{
|
||||||
|
for(HttpCookie cookie: cookies)
|
||||||
|
cookie(cookie);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setExtensions(List<ExtensionConfig> configs)
|
||||||
|
{
|
||||||
|
this.extensions = configs;
|
||||||
|
updateExtensionHeader();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void updateExtensionHeader()
|
||||||
|
{
|
||||||
|
HttpFields headers = getHttpFields();
|
||||||
|
headers.remove(HttpHeader.SEC_WEBSOCKET_EXTENSIONS);
|
||||||
|
for (ExtensionConfig config : extensions)
|
||||||
|
{
|
||||||
|
headers.add(HttpHeader.SEC_WEBSOCKET_EXTENSIONS,config.getParameterizedName());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setHeader(String name, List<String> values)
|
||||||
|
{
|
||||||
|
getHttpFields().put(name,values);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setHeader(String name, String value)
|
||||||
|
{
|
||||||
|
getHttpFields().put(name,value);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setHeaders(Map<String, List<String>> headers)
|
||||||
|
{
|
||||||
|
for (Map.Entry<String, List<String>> entry : headers.entrySet())
|
||||||
|
{
|
||||||
|
getHttpFields().put(entry.getKey(),entry.getValue());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setHttpVersion(String httpVersion)
|
||||||
|
{
|
||||||
|
version(HttpVersion.fromString(httpVersion));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setMethod(String method)
|
||||||
|
{
|
||||||
|
method(method);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setRequestURI(URI uri)
|
||||||
|
{
|
||||||
|
throw new UnsupportedOperationException("Cannot reset/change RequestURI");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setSession(Object session)
|
||||||
|
{
|
||||||
|
this.session = session;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setSubProtocols(List<String> protocols)
|
||||||
|
{
|
||||||
|
this.subProtocols = protocols;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setSubProtocols(String... protocols)
|
||||||
|
{
|
||||||
|
this.subProtocols.clear();
|
||||||
|
this.subProtocols.addAll(Arrays.asList(protocols));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<HttpCookie> getCookies()
|
||||||
|
{
|
||||||
|
return WebSocketUpgradeRequest.this.getCookies();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Map<String, List<String>> getHeaders()
|
||||||
|
{
|
||||||
|
Map<String, List<String>> headersMap = new TreeMap<>(String.CASE_INSENSITIVE_ORDER);
|
||||||
|
HttpFields fields = getHttpFields();
|
||||||
|
for(String name: fields.getFieldNamesCollection())
|
||||||
|
{
|
||||||
|
headersMap.put(name,fields.getValuesList(name));
|
||||||
|
}
|
||||||
|
return headersMap;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getHost()
|
||||||
|
{
|
||||||
|
return WebSocketUpgradeRequest.this.getHost();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getMethod()
|
||||||
|
{
|
||||||
|
return WebSocketUpgradeRequest.this.getMethod();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private final WebSocketClient wsClient;
|
||||||
|
private final EventDriver localEndpoint;
|
||||||
|
private final CompletableFuture<Session> fut;
|
||||||
|
/** WebSocket API UpgradeRequest Facade to HttpClient HttpRequest */
|
||||||
|
private final ClientUpgradeRequestFacade apiRequestFacade;
|
||||||
|
private UpgradeListener upgradeListener;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Exists for internal use of HttpClient by WebSocketClient.
|
||||||
|
* <p>
|
||||||
|
* Maintained for Backward compatibility and also for JSR356 WebSocket ClientContainer use.
|
||||||
|
*
|
||||||
|
* @param httpClient the HttpClient that this request uses
|
||||||
|
* @param request the ClientUpgradeRequest (backward compat) to base this request from
|
||||||
|
*/
|
||||||
|
protected WebSocketUpgradeRequest(WebSocketClient wsClient, HttpClient httpClient, ClientUpgradeRequest request)
|
||||||
|
{
|
||||||
|
this(wsClient, httpClient,request.getRequestURI(),request.getLocalEndpoint());
|
||||||
|
apiRequestFacade.init(request);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initiating a WebSocket Upgrade using HTTP/1.1
|
||||||
|
*
|
||||||
|
* @param httpClient the HttpClient that this request uses
|
||||||
|
* @param localEndpoint the local endpoint (following Jetty WebSocket Client API rules) to use for incoming
|
||||||
|
* WebSocket events
|
||||||
|
* @param wsURI the WebSocket URI to connect to
|
||||||
|
*/
|
||||||
|
public WebSocketUpgradeRequest(WebSocketClient wsClient, HttpClient httpClient, URI wsURI, Object localEndpoint)
|
||||||
|
{
|
||||||
|
super(httpClient,new HttpConversation(),wsURI);
|
||||||
|
|
||||||
|
apiRequestFacade = new ClientUpgradeRequestFacade();
|
||||||
|
|
||||||
|
if (!wsURI.isAbsolute())
|
||||||
|
{
|
||||||
|
throw new IllegalArgumentException("WebSocket URI must be an absolute URI: " + wsURI);
|
||||||
|
}
|
||||||
|
|
||||||
|
String scheme = wsURI.getScheme();
|
||||||
|
if (scheme == null || !(scheme.equalsIgnoreCase("ws") || scheme.equalsIgnoreCase("wss")))
|
||||||
|
{
|
||||||
|
throw new IllegalArgumentException("WebSocket URI must use 'ws' or 'wss' scheme: " + wsURI);
|
||||||
|
}
|
||||||
|
|
||||||
|
this.wsClient = wsClient;
|
||||||
|
try
|
||||||
|
{
|
||||||
|
if (!this.wsClient.isRunning())
|
||||||
|
{
|
||||||
|
this.wsClient.start();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
throw new IllegalStateException("Unable to start WebSocketClient", e);
|
||||||
|
}
|
||||||
|
this.localEndpoint = this.wsClient.getEventDriverFactory().wrap(localEndpoint);
|
||||||
|
|
||||||
|
this.fut = new CompletableFuture<Session>();
|
||||||
|
}
|
||||||
|
|
||||||
|
private final String genRandomKey()
|
||||||
|
{
|
||||||
|
byte[] bytes = new byte[16];
|
||||||
|
ThreadLocalRandom.current().nextBytes(bytes);
|
||||||
|
return new String(B64Code.encode(bytes));
|
||||||
|
}
|
||||||
|
|
||||||
|
private ExtensionFactory getExtensionFactory()
|
||||||
|
{
|
||||||
|
return this.wsClient.getExtensionFactory();
|
||||||
|
}
|
||||||
|
|
||||||
|
private SessionFactory getSessionFactory()
|
||||||
|
{
|
||||||
|
return this.wsClient.getSessionFactory();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void initWebSocketHeaders()
|
||||||
|
{
|
||||||
|
method(HttpMethod.GET);
|
||||||
|
version(HttpVersion.HTTP_1_1);
|
||||||
|
|
||||||
|
// The Upgrade Headers
|
||||||
|
header(HttpHeader.UPGRADE,"websocket");
|
||||||
|
header(HttpHeader.CONNECTION,"Upgrade");
|
||||||
|
|
||||||
|
// The WebSocket Headers
|
||||||
|
header(HttpHeader.SEC_WEBSOCKET_KEY,genRandomKey());
|
||||||
|
header(HttpHeader.SEC_WEBSOCKET_VERSION,"13");
|
||||||
|
|
||||||
|
// (Per the hybi list): Add no-cache headers to avoid compatibility issue.
|
||||||
|
// There are some proxies that rewrite "Connection: upgrade"
|
||||||
|
// to "Connection: close" in the response if a request doesn't contain
|
||||||
|
// these headers.
|
||||||
|
header(HttpHeader.PRAGMA,"no-cache");
|
||||||
|
header(HttpHeader.CACHE_CONTROL,"no-cache");
|
||||||
|
|
||||||
|
// handle "Sec-WebSocket-Extensions"
|
||||||
|
if (!apiRequestFacade.getExtensions().isEmpty())
|
||||||
|
{
|
||||||
|
for (ExtensionConfig ext : apiRequestFacade.getExtensions())
|
||||||
|
{
|
||||||
|
header(HttpHeader.SEC_WEBSOCKET_EXTENSIONS,ext.getParameterizedName());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// handle "Sec-WebSocket-Protocol"
|
||||||
|
if (!apiRequestFacade.getSubProtocols().isEmpty())
|
||||||
|
{
|
||||||
|
for (String protocol : apiRequestFacade.getSubProtocols())
|
||||||
|
{
|
||||||
|
header(HttpHeader.SEC_WEBSOCKET_SUBPROTOCOL,protocol);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onComplete(Result result)
|
||||||
|
{
|
||||||
|
if (LOG.isDebugEnabled())
|
||||||
|
{
|
||||||
|
LOG.debug("onComplete() - {}",result);
|
||||||
|
}
|
||||||
|
|
||||||
|
URI requestURI = result.getRequest().getURI();
|
||||||
|
Response response = result.getResponse();
|
||||||
|
int responseStatusCode = response.getStatus();
|
||||||
|
String responseLine = responseStatusCode + " " + response.getReason();
|
||||||
|
|
||||||
|
if (result.isFailed())
|
||||||
|
{
|
||||||
|
if (LOG.isDebugEnabled())
|
||||||
|
{
|
||||||
|
if (result.getFailure() != null)
|
||||||
|
LOG.debug("General Failure", result.getFailure());
|
||||||
|
if (result.getRequestFailure() != null)
|
||||||
|
LOG.debug("Request Failure", result.getRequestFailure());
|
||||||
|
if (result.getResponseFailure() != null)
|
||||||
|
LOG.debug("Response Failure", result.getResponseFailure());
|
||||||
|
}
|
||||||
|
|
||||||
|
Throwable failure = result.getFailure();
|
||||||
|
if ((failure instanceof java.net.ConnectException) || (failure instanceof UpgradeException))
|
||||||
|
{
|
||||||
|
// handle as-is
|
||||||
|
handleException(failure);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// wrap in UpgradeException
|
||||||
|
handleException(new UpgradeException(requestURI,responseStatusCode,responseLine,failure));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (responseStatusCode != HttpStatus.SWITCHING_PROTOCOLS_101)
|
||||||
|
{
|
||||||
|
// Failed to upgrade (other reason)
|
||||||
|
handleException(new UpgradeException(requestURI,responseStatusCode,responseLine));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void handleException(Throwable failure)
|
||||||
|
{
|
||||||
|
localEndpoint.incomingError(failure);
|
||||||
|
fut.completeExceptionally(failure);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ContentResponse send() throws InterruptedException, TimeoutException, ExecutionException
|
||||||
|
{
|
||||||
|
throw new RuntimeException("Working with raw ContentResponse is invalid for WebSocket");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void send(final CompleteListener listener)
|
||||||
|
{
|
||||||
|
initWebSocketHeaders();
|
||||||
|
super.send(listener);
|
||||||
|
}
|
||||||
|
|
||||||
|
public CompletableFuture<Session> sendAsync()
|
||||||
|
{
|
||||||
|
send(this);
|
||||||
|
return fut;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void upgrade(HttpResponse response, HttpConnectionOverHTTP oldConn)
|
||||||
|
{
|
||||||
|
if (!this.getHeaders().get(HttpHeader.UPGRADE).equalsIgnoreCase("websocket"))
|
||||||
|
{
|
||||||
|
// Not my upgrade
|
||||||
|
throw new HttpResponseException("Not WebSocket Upgrade",response);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (upgradeListener != null)
|
||||||
|
{
|
||||||
|
upgradeListener.onHandshakeRequest(apiRequestFacade);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check the Accept hash
|
||||||
|
String reqKey = this.getHeaders().get(HttpHeader.SEC_WEBSOCKET_KEY);
|
||||||
|
String expectedHash = AcceptHash.hashKey(reqKey);
|
||||||
|
String respHash = response.getHeaders().get(HttpHeader.SEC_WEBSOCKET_ACCEPT);
|
||||||
|
|
||||||
|
if (expectedHash.equalsIgnoreCase(respHash) == false)
|
||||||
|
{
|
||||||
|
throw new HttpResponseException("Invalid Sec-WebSocket-Accept hash",response);
|
||||||
|
}
|
||||||
|
|
||||||
|
// We can upgrade
|
||||||
|
EndPoint endp = oldConn.getEndPoint();
|
||||||
|
|
||||||
|
WebSocketClientConnection connection = new WebSocketClientConnection(endp,wsClient.getExecutor(),wsClient.getScheduler(),localEndpoint.getPolicy(),
|
||||||
|
wsClient.getBufferPool());
|
||||||
|
|
||||||
|
URI requestURI = this.getURI();
|
||||||
|
|
||||||
|
WebSocketSession session = getSessionFactory().createSession(requestURI,localEndpoint,connection);
|
||||||
|
session.setUpgradeRequest(new ClientUpgradeRequest(this));
|
||||||
|
session.setUpgradeResponse(new ClientUpgradeResponse(response));
|
||||||
|
connection.addListener(session);
|
||||||
|
|
||||||
|
ExtensionStack extensionStack = new ExtensionStack(getExtensionFactory());
|
||||||
|
List<ExtensionConfig> extensions = new ArrayList<>();
|
||||||
|
HttpField extField = response.getHeaders().getField(HttpHeader.SEC_WEBSOCKET_EXTENSIONS);
|
||||||
|
if (extField != null)
|
||||||
|
{
|
||||||
|
String[] extValues = extField.getValues();
|
||||||
|
if (extValues != null)
|
||||||
|
{
|
||||||
|
for (String extVal : extValues)
|
||||||
|
{
|
||||||
|
QuotedStringTokenizer tok = new QuotedStringTokenizer(extVal,",");
|
||||||
|
while (tok.hasMoreTokens())
|
||||||
|
{
|
||||||
|
extensions.add(ExtensionConfig.parse(tok.nextToken()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
extensionStack.negotiate(extensions);
|
||||||
|
|
||||||
|
extensionStack.configure(connection.getParser());
|
||||||
|
extensionStack.configure(connection.getGenerator());
|
||||||
|
|
||||||
|
// Setup Incoming Routing
|
||||||
|
connection.setNextIncomingFrames(extensionStack);
|
||||||
|
extensionStack.setNextIncoming(session);
|
||||||
|
|
||||||
|
// Setup Outgoing Routing
|
||||||
|
session.setOutgoingHandler(extensionStack);
|
||||||
|
extensionStack.setNextOutgoing(connection);
|
||||||
|
|
||||||
|
session.addManaged(extensionStack);
|
||||||
|
session.setFuture(fut);
|
||||||
|
wsClient.addManaged(session);
|
||||||
|
|
||||||
|
if (upgradeListener != null)
|
||||||
|
{
|
||||||
|
upgradeListener.onHandshakeResponse(new ClientUpgradeResponse(response));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Now swap out the connection
|
||||||
|
endp.upgrade(connection);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setUpgradeListener(UpgradeListener upgradeListener)
|
||||||
|
{
|
||||||
|
this.upgradeListener = upgradeListener;
|
||||||
|
}
|
||||||
|
|
||||||
|
private HttpFields getHttpFields()
|
||||||
|
{
|
||||||
|
return super.getHeaders();
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,118 +0,0 @@
|
||||||
//
|
|
||||||
// ========================================================================
|
|
||||||
// Copyright (c) 1995-2016 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.client.io;
|
|
||||||
|
|
||||||
import org.eclipse.jetty.util.FuturePromise;
|
|
||||||
import org.eclipse.jetty.util.log.Log;
|
|
||||||
import org.eclipse.jetty.util.log.Logger;
|
|
||||||
import org.eclipse.jetty.websocket.api.Session;
|
|
||||||
import org.eclipse.jetty.websocket.client.ClientUpgradeRequest;
|
|
||||||
import org.eclipse.jetty.websocket.client.ClientUpgradeResponse;
|
|
||||||
import org.eclipse.jetty.websocket.client.WebSocketClient;
|
|
||||||
import org.eclipse.jetty.websocket.client.masks.Masker;
|
|
||||||
import org.eclipse.jetty.websocket.common.WebSocketSession;
|
|
||||||
import org.eclipse.jetty.websocket.common.events.EventDriver;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Holder for the pending connect information.
|
|
||||||
*/
|
|
||||||
public abstract class ConnectPromise extends FuturePromise<Session> implements Runnable
|
|
||||||
{
|
|
||||||
private static final Logger LOG = Log.getLogger(ConnectPromise.class);
|
|
||||||
private final WebSocketClient client;
|
|
||||||
private final EventDriver driver;
|
|
||||||
private final ClientUpgradeRequest request;
|
|
||||||
private final Masker masker;
|
|
||||||
private UpgradeListener upgradeListener;
|
|
||||||
private ClientUpgradeResponse response;
|
|
||||||
private WebSocketSession session;
|
|
||||||
|
|
||||||
public ConnectPromise(WebSocketClient client, EventDriver driver, ClientUpgradeRequest request)
|
|
||||||
{
|
|
||||||
this.client = client;
|
|
||||||
this.driver = driver;
|
|
||||||
this.request = request;
|
|
||||||
this.masker = client.getMasker();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void failed(Throwable cause)
|
|
||||||
{
|
|
||||||
// Notify websocket of failure to connect
|
|
||||||
driver.onError(cause);
|
|
||||||
|
|
||||||
// Notify promise/future of failure to connect
|
|
||||||
super.failed(cause);
|
|
||||||
}
|
|
||||||
|
|
||||||
public WebSocketClient getClient()
|
|
||||||
{
|
|
||||||
return client;
|
|
||||||
}
|
|
||||||
|
|
||||||
public EventDriver getDriver()
|
|
||||||
{
|
|
||||||
return this.driver;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Masker getMasker()
|
|
||||||
{
|
|
||||||
return masker;
|
|
||||||
}
|
|
||||||
|
|
||||||
public ClientUpgradeRequest getRequest()
|
|
||||||
{
|
|
||||||
return this.request;
|
|
||||||
}
|
|
||||||
|
|
||||||
public ClientUpgradeResponse getResponse()
|
|
||||||
{
|
|
||||||
return response;
|
|
||||||
}
|
|
||||||
|
|
||||||
public UpgradeListener getUpgradeListener()
|
|
||||||
{
|
|
||||||
return upgradeListener;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setResponse(ClientUpgradeResponse response)
|
|
||||||
{
|
|
||||||
this.response = response;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setUpgradeListener(UpgradeListener upgradeListener)
|
|
||||||
{
|
|
||||||
this.upgradeListener = upgradeListener;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void succeeded()
|
|
||||||
{
|
|
||||||
if(LOG.isDebugEnabled())
|
|
||||||
LOG.debug("{}.succeeded()",this.getClass().getSimpleName());
|
|
||||||
session.setUpgradeRequest(request);
|
|
||||||
session.setUpgradeResponse(response);
|
|
||||||
// session.open();
|
|
||||||
super.succeeded(session);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setSession(WebSocketSession session)
|
|
||||||
{
|
|
||||||
this.session = session;
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -18,87 +18,21 @@
|
||||||
|
|
||||||
package org.eclipse.jetty.websocket.client.io;
|
package org.eclipse.jetty.websocket.client.io;
|
||||||
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.net.InetSocketAddress;
|
import java.net.InetSocketAddress;
|
||||||
import java.net.SocketAddress;
|
|
||||||
import java.net.URI;
|
import java.net.URI;
|
||||||
import java.nio.channels.SocketChannel;
|
|
||||||
import java.util.Locale;
|
import java.util.Locale;
|
||||||
|
|
||||||
|
import org.eclipse.jetty.client.HttpClient;
|
||||||
import org.eclipse.jetty.util.component.ContainerLifeCycle;
|
import org.eclipse.jetty.util.component.ContainerLifeCycle;
|
||||||
import org.eclipse.jetty.util.log.Log;
|
|
||||||
import org.eclipse.jetty.util.log.Logger;
|
|
||||||
import org.eclipse.jetty.websocket.client.ClientUpgradeRequest;
|
|
||||||
import org.eclipse.jetty.websocket.client.WebSocketClient;
|
import org.eclipse.jetty.websocket.client.WebSocketClient;
|
||||||
import org.eclipse.jetty.websocket.common.events.EventDriver;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Internal Connection/Client Manager used to track active clients, their physical vs virtual connection information, and provide some means to create new
|
* Deprecated ConnectionManager
|
||||||
* physical or virtual connections.
|
* @deprecated use {@link HttpClient} with WebSocketClient directly
|
||||||
*/
|
*/
|
||||||
|
@Deprecated
|
||||||
public class ConnectionManager extends ContainerLifeCycle
|
public class ConnectionManager extends ContainerLifeCycle
|
||||||
{
|
{
|
||||||
private class PhysicalConnect extends ConnectPromise
|
|
||||||
{
|
|
||||||
private SocketAddress bindAddress;
|
|
||||||
|
|
||||||
public PhysicalConnect(WebSocketClient client, EventDriver driver, ClientUpgradeRequest request)
|
|
||||||
{
|
|
||||||
super(client,driver,request);
|
|
||||||
this.bindAddress = client.getBindAddress();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void run()
|
|
||||||
{
|
|
||||||
SocketChannel channel = null;
|
|
||||||
try
|
|
||||||
{
|
|
||||||
channel = SocketChannel.open();
|
|
||||||
if (bindAddress != null)
|
|
||||||
{
|
|
||||||
channel.bind(bindAddress);
|
|
||||||
}
|
|
||||||
|
|
||||||
URI wsUri = getRequest().getRequestURI();
|
|
||||||
|
|
||||||
channel.socket().setTcpNoDelay(true); // disable nagle
|
|
||||||
channel.configureBlocking(false); // async always
|
|
||||||
|
|
||||||
InetSocketAddress address = toSocketAddress(wsUri);
|
|
||||||
|
|
||||||
if (channel.connect(address))
|
|
||||||
{
|
|
||||||
getSelector().accept(channel, this);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
getSelector().connect(channel, this);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
catch (Throwable t)
|
|
||||||
{
|
|
||||||
// close the socket channel
|
|
||||||
if (channel != null)
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
channel.close();
|
|
||||||
}
|
|
||||||
catch (IOException ignore)
|
|
||||||
{
|
|
||||||
LOG.ignore(ignore);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// notify the future
|
|
||||||
failed(t);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private static final Logger LOG = Log.getLogger(ConnectionManager.class);
|
|
||||||
|
|
||||||
public static InetSocketAddress toSocketAddress(URI uri)
|
public static InetSocketAddress toSocketAddress(URI uri)
|
||||||
{
|
{
|
||||||
if (!uri.isAbsolute())
|
if (!uri.isAbsolute())
|
||||||
|
@ -130,51 +64,7 @@ public class ConnectionManager extends ContainerLifeCycle
|
||||||
return new InetSocketAddress(uri.getHost(),port);
|
return new InetSocketAddress(uri.getHost(),port);
|
||||||
}
|
}
|
||||||
|
|
||||||
private final WebSocketClient client;
|
|
||||||
private WebSocketClientSelectorManager selector;
|
|
||||||
|
|
||||||
public ConnectionManager(WebSocketClient client)
|
public ConnectionManager(WebSocketClient client)
|
||||||
{
|
{
|
||||||
this.client = client;
|
|
||||||
}
|
|
||||||
|
|
||||||
public ConnectPromise connect(WebSocketClient client, EventDriver driver, ClientUpgradeRequest request)
|
|
||||||
{
|
|
||||||
return new PhysicalConnect(client,driver,request);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void doStart() throws Exception
|
|
||||||
{
|
|
||||||
selector = newWebSocketClientSelectorManager(client);
|
|
||||||
selector.setSslContextFactory(client.getSslContextFactory());
|
|
||||||
selector.setConnectTimeout(client.getConnectTimeout());
|
|
||||||
addBean(selector);
|
|
||||||
|
|
||||||
super.doStart();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void doStop() throws Exception
|
|
||||||
{
|
|
||||||
super.doStop();
|
|
||||||
removeBean(selector);
|
|
||||||
}
|
|
||||||
|
|
||||||
public WebSocketClientSelectorManager getSelector()
|
|
||||||
{
|
|
||||||
return selector;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Factory method for new WebSocketClientSelectorManager (used by other projects like cometd)
|
|
||||||
*
|
|
||||||
* @param client
|
|
||||||
* the client used to create the WebSocketClientSelectorManager
|
|
||||||
* @return the new WebSocketClientSelectorManager
|
|
||||||
*/
|
|
||||||
protected WebSocketClientSelectorManager newWebSocketClientSelectorManager(WebSocketClient client)
|
|
||||||
{
|
|
||||||
return new WebSocketClientSelectorManager(client);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,391 +0,0 @@
|
||||||
//
|
|
||||||
// ========================================================================
|
|
||||||
// Copyright (c) 1995-2016 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.client.io;
|
|
||||||
|
|
||||||
import java.io.EOFException;
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.net.URI;
|
|
||||||
import java.nio.ByteBuffer;
|
|
||||||
import java.nio.charset.StandardCharsets;
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.concurrent.Executor;
|
|
||||||
|
|
||||||
import org.eclipse.jetty.io.AbstractConnection;
|
|
||||||
import org.eclipse.jetty.io.ByteBufferPool;
|
|
||||||
import org.eclipse.jetty.io.Connection;
|
|
||||||
import org.eclipse.jetty.io.EndPoint;
|
|
||||||
import org.eclipse.jetty.util.BufferUtil;
|
|
||||||
import org.eclipse.jetty.util.FutureCallback;
|
|
||||||
import org.eclipse.jetty.util.QuotedStringTokenizer;
|
|
||||||
import org.eclipse.jetty.util.log.Log;
|
|
||||||
import org.eclipse.jetty.util.log.Logger;
|
|
||||||
import org.eclipse.jetty.websocket.api.UpgradeException;
|
|
||||||
import org.eclipse.jetty.websocket.api.WebSocketPolicy;
|
|
||||||
import org.eclipse.jetty.websocket.api.extensions.ExtensionConfig;
|
|
||||||
import org.eclipse.jetty.websocket.client.ClientUpgradeRequest;
|
|
||||||
import org.eclipse.jetty.websocket.client.ClientUpgradeResponse;
|
|
||||||
import org.eclipse.jetty.websocket.common.AcceptHash;
|
|
||||||
import org.eclipse.jetty.websocket.common.SessionFactory;
|
|
||||||
import org.eclipse.jetty.websocket.common.WebSocketSession;
|
|
||||||
import org.eclipse.jetty.websocket.common.events.EventDriver;
|
|
||||||
import org.eclipse.jetty.websocket.common.extensions.ExtensionStack;
|
|
||||||
import org.eclipse.jetty.websocket.common.io.http.HttpResponseHeaderParser;
|
|
||||||
import org.eclipse.jetty.websocket.common.io.http.HttpResponseHeaderParser.ParseException;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* This is the initial connection handling that exists immediately after physical connection is established to
|
|
||||||
* destination server.
|
|
||||||
* <p>
|
|
||||||
* Eventually, upon successful Upgrade request/response, this connection swaps itself out for the
|
|
||||||
* WebSocektClientConnection handler.
|
|
||||||
*/
|
|
||||||
public class UpgradeConnection extends AbstractConnection implements Connection.UpgradeFrom
|
|
||||||
{
|
|
||||||
public class SendUpgradeRequest extends FutureCallback implements Runnable
|
|
||||||
{
|
|
||||||
private final Logger LOG = Log.getLogger(UpgradeConnection.SendUpgradeRequest.class);
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void run()
|
|
||||||
{
|
|
||||||
URI uri = connectPromise.getRequest().getRequestURI();
|
|
||||||
request.setRequestURI(uri);
|
|
||||||
|
|
||||||
UpgradeListener handshakeListener = connectPromise.getUpgradeListener();
|
|
||||||
if (handshakeListener != null)
|
|
||||||
{
|
|
||||||
handshakeListener.onHandshakeRequest(request);
|
|
||||||
}
|
|
||||||
|
|
||||||
String rawRequest = request.generate();
|
|
||||||
|
|
||||||
ByteBuffer buf = BufferUtil.toBuffer(rawRequest,StandardCharsets.UTF_8);
|
|
||||||
getEndPoint().write(this,buf);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void succeeded()
|
|
||||||
{
|
|
||||||
if (LOG.isDebugEnabled())
|
|
||||||
{
|
|
||||||
LOG.debug("Upgrade Request Write Success");
|
|
||||||
}
|
|
||||||
// Writing the request header is complete.
|
|
||||||
super.succeeded();
|
|
||||||
state = State.RESPONSE;
|
|
||||||
// start the interest in fill
|
|
||||||
fillInterested();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void failed(Throwable cause)
|
|
||||||
{
|
|
||||||
if (LOG.isDebugEnabled())
|
|
||||||
{
|
|
||||||
LOG.debug("Upgrade Request Write Failure",cause);
|
|
||||||
}
|
|
||||||
super.failed(cause);
|
|
||||||
state = State.FAILURE;
|
|
||||||
// Fail the connect promise when a fundamental exception during connect occurs.
|
|
||||||
connectPromise.failed(cause);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/** HTTP Response Code: 101 Switching Protocols */
|
|
||||||
private static final int SWITCHING_PROTOCOLS = 101;
|
|
||||||
|
|
||||||
private enum State
|
|
||||||
{
|
|
||||||
REQUEST,
|
|
||||||
RESPONSE,
|
|
||||||
FAILURE,
|
|
||||||
UPGRADE
|
|
||||||
}
|
|
||||||
|
|
||||||
private static final Logger LOG = Log.getLogger(UpgradeConnection.class);
|
|
||||||
private final ByteBufferPool bufferPool;
|
|
||||||
private final ConnectPromise connectPromise;
|
|
||||||
private final HttpResponseHeaderParser parser;
|
|
||||||
private State state = State.REQUEST;
|
|
||||||
private ClientUpgradeRequest request;
|
|
||||||
private ClientUpgradeResponse response;
|
|
||||||
|
|
||||||
public UpgradeConnection(EndPoint endp, Executor executor, ConnectPromise connectPromise)
|
|
||||||
{
|
|
||||||
super(endp,executor);
|
|
||||||
this.connectPromise = connectPromise;
|
|
||||||
this.bufferPool = connectPromise.getClient().getBufferPool();
|
|
||||||
this.request = connectPromise.getRequest();
|
|
||||||
|
|
||||||
// Setup the parser
|
|
||||||
this.parser = new HttpResponseHeaderParser(new ClientUpgradeResponse());
|
|
||||||
}
|
|
||||||
|
|
||||||
public void disconnect(boolean onlyOutput)
|
|
||||||
{
|
|
||||||
EndPoint endPoint = getEndPoint();
|
|
||||||
// We need to gently close first, to allow
|
|
||||||
// SSL close alerts to be sent by Jetty
|
|
||||||
if (LOG.isDebugEnabled())
|
|
||||||
{
|
|
||||||
LOG.debug("Shutting down output {}",endPoint);
|
|
||||||
}
|
|
||||||
|
|
||||||
endPoint.shutdownOutput();
|
|
||||||
if (!onlyOutput)
|
|
||||||
{
|
|
||||||
if (LOG.isDebugEnabled())
|
|
||||||
{
|
|
||||||
LOG.debug("Closing {}",endPoint);
|
|
||||||
}
|
|
||||||
endPoint.close();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void failUpgrade(Throwable cause)
|
|
||||||
{
|
|
||||||
close();
|
|
||||||
connectPromise.failed(cause);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void notifyConnect(ClientUpgradeResponse response)
|
|
||||||
{
|
|
||||||
connectPromise.setResponse(response);
|
|
||||||
|
|
||||||
UpgradeListener handshakeListener = connectPromise.getUpgradeListener();
|
|
||||||
if (handshakeListener != null)
|
|
||||||
{
|
|
||||||
handshakeListener.onHandshakeResponse(response);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public ByteBuffer onUpgradeFrom()
|
|
||||||
{
|
|
||||||
return connectPromise.getResponse().getRemainingBuffer();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onFillable()
|
|
||||||
{
|
|
||||||
if (LOG.isDebugEnabled())
|
|
||||||
{
|
|
||||||
LOG.debug("onFillable");
|
|
||||||
}
|
|
||||||
ByteBuffer buffer = bufferPool.acquire(getInputBufferSize(),false);
|
|
||||||
BufferUtil.clear(buffer);
|
|
||||||
try
|
|
||||||
{
|
|
||||||
read(buffer);
|
|
||||||
}
|
|
||||||
finally
|
|
||||||
{
|
|
||||||
bufferPool.release(buffer);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (state == State.RESPONSE)
|
|
||||||
{
|
|
||||||
// Continue Reading
|
|
||||||
fillInterested();
|
|
||||||
}
|
|
||||||
else if (state == State.UPGRADE)
|
|
||||||
{
|
|
||||||
// Stop Reading, upgrade the connection now
|
|
||||||
upgradeConnection(response);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onOpen()
|
|
||||||
{
|
|
||||||
super.onOpen();
|
|
||||||
getExecutor().execute(new SendUpgradeRequest());
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onClose()
|
|
||||||
{
|
|
||||||
if (LOG.isDebugEnabled())
|
|
||||||
{
|
|
||||||
LOG.debug("Closed connection {}",this);
|
|
||||||
}
|
|
||||||
super.onClose();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected boolean onReadTimeout()
|
|
||||||
{
|
|
||||||
if (LOG.isDebugEnabled())
|
|
||||||
{
|
|
||||||
LOG.debug("Timeout on connection {}",this);
|
|
||||||
}
|
|
||||||
|
|
||||||
failUpgrade(new IOException("Timeout while performing WebSocket Upgrade"));
|
|
||||||
|
|
||||||
return super.onReadTimeout();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Read / Parse the waiting read/fill buffer
|
|
||||||
*
|
|
||||||
* @param buffer
|
|
||||||
* the buffer to fill into from the endpoint
|
|
||||||
*/
|
|
||||||
private void read(ByteBuffer buffer)
|
|
||||||
{
|
|
||||||
EndPoint endPoint = getEndPoint();
|
|
||||||
try
|
|
||||||
{
|
|
||||||
while (true)
|
|
||||||
{
|
|
||||||
int filled = endPoint.fill(buffer);
|
|
||||||
if (filled == 0)
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
else if (filled < 0)
|
|
||||||
{
|
|
||||||
LOG.warn("read - EOF Reached");
|
|
||||||
state = State.FAILURE;
|
|
||||||
failUpgrade(new EOFException("Reading WebSocket Upgrade response"));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
if (LOG.isDebugEnabled())
|
|
||||||
{
|
|
||||||
LOG.debug("Filled {} bytes - {}",filled,BufferUtil.toDetailString(buffer));
|
|
||||||
}
|
|
||||||
response = (ClientUpgradeResponse)parser.parse(buffer);
|
|
||||||
if (response != null)
|
|
||||||
{
|
|
||||||
// Got a response!
|
|
||||||
validateResponse(response);
|
|
||||||
notifyConnect(response);
|
|
||||||
state = State.UPGRADE;
|
|
||||||
return; // do no more reading
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
catch (IOException | ParseException e)
|
|
||||||
{
|
|
||||||
LOG.ignore(e);
|
|
||||||
state = State.FAILURE;
|
|
||||||
UpgradeException ue = new UpgradeException(request.getRequestURI(),e);
|
|
||||||
connectPromise.failed(ue);
|
|
||||||
disconnect(false);
|
|
||||||
}
|
|
||||||
catch (UpgradeException e)
|
|
||||||
{
|
|
||||||
LOG.ignore(e);
|
|
||||||
state = State.FAILURE;
|
|
||||||
connectPromise.failed(e);
|
|
||||||
disconnect(false);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void upgradeConnection(ClientUpgradeResponse response)
|
|
||||||
{
|
|
||||||
EndPoint endp = getEndPoint();
|
|
||||||
Executor executor = getExecutor();
|
|
||||||
|
|
||||||
EventDriver websocket = connectPromise.getDriver();
|
|
||||||
WebSocketPolicy policy = websocket.getPolicy();
|
|
||||||
|
|
||||||
WebSocketClientConnection connection = new WebSocketClientConnection(endp,executor,connectPromise,policy);
|
|
||||||
|
|
||||||
SessionFactory sessionFactory = connectPromise.getClient().getSessionFactory();
|
|
||||||
WebSocketSession session = sessionFactory.createSession(request.getRequestURI(),websocket,connection);
|
|
||||||
session.setPolicy(policy);
|
|
||||||
session.setUpgradeRequest(request);
|
|
||||||
session.setUpgradeResponse(response);
|
|
||||||
connection.addListener(session);
|
|
||||||
connectPromise.setSession(session);
|
|
||||||
|
|
||||||
// Initialize / Negotiate Extensions
|
|
||||||
ExtensionStack extensionStack = new ExtensionStack(connectPromise.getClient().getExtensionFactory());
|
|
||||||
extensionStack.negotiate(response.getExtensions());
|
|
||||||
|
|
||||||
extensionStack.configure(connection.getParser());
|
|
||||||
extensionStack.configure(connection.getGenerator());
|
|
||||||
|
|
||||||
// Setup Incoming Routing
|
|
||||||
connection.setNextIncomingFrames(extensionStack);
|
|
||||||
extensionStack.setNextIncoming(session);
|
|
||||||
|
|
||||||
// Setup Outgoing Routing
|
|
||||||
session.setOutgoingHandler(extensionStack);
|
|
||||||
extensionStack.setNextOutgoing(connection);
|
|
||||||
|
|
||||||
session.addManaged(extensionStack);
|
|
||||||
connectPromise.getClient().addManaged(session);
|
|
||||||
|
|
||||||
// Now swap out the connection
|
|
||||||
endp.upgrade(connection);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void validateResponse(ClientUpgradeResponse response)
|
|
||||||
{
|
|
||||||
// Validate Response Status Code
|
|
||||||
if (response.getStatusCode() != SWITCHING_PROTOCOLS)
|
|
||||||
{
|
|
||||||
// TODO: use jetty-http and org.eclipse.jetty.http.HttpStatus for more meaningful exception messages
|
|
||||||
throw new UpgradeException(request.getRequestURI(),response.getStatusCode(),"Didn't switch protocols, expected status <" + SWITCHING_PROTOCOLS
|
|
||||||
+ ">, but got <" + response.getStatusCode() + ">");
|
|
||||||
}
|
|
||||||
|
|
||||||
// Validate Connection header
|
|
||||||
String connection = response.getHeader("Connection");
|
|
||||||
if (!"upgrade".equalsIgnoreCase(connection))
|
|
||||||
{
|
|
||||||
throw new UpgradeException(request.getRequestURI(),response.getStatusCode(),"Connection is " + connection + " (expected upgrade)");
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check the Accept hash
|
|
||||||
String reqKey = request.getKey();
|
|
||||||
String expectedHash = AcceptHash.hashKey(reqKey);
|
|
||||||
String respHash = response.getHeader("Sec-WebSocket-Accept");
|
|
||||||
|
|
||||||
response.setSuccess(true);
|
|
||||||
if (expectedHash.equalsIgnoreCase(respHash) == false)
|
|
||||||
{
|
|
||||||
response.setSuccess(false);
|
|
||||||
throw new UpgradeException(request.getRequestURI(),response.getStatusCode(),"Invalid Sec-WebSocket-Accept hash");
|
|
||||||
}
|
|
||||||
|
|
||||||
// Parse extensions
|
|
||||||
List<ExtensionConfig> extensions = new ArrayList<>();
|
|
||||||
List<String> extValues = response.getHeaders("Sec-WebSocket-Extensions");
|
|
||||||
if (extValues != null)
|
|
||||||
{
|
|
||||||
for (String extVal : extValues)
|
|
||||||
{
|
|
||||||
// TODO use QuotedCSV ???
|
|
||||||
QuotedStringTokenizer tok = new QuotedStringTokenizer(extVal,",");
|
|
||||||
while (tok.hasMoreTokens())
|
|
||||||
{
|
|
||||||
extensions.add(ExtensionConfig.parse(tok.nextToken()));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
response.setExtensions(extensions);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -20,15 +20,17 @@ package org.eclipse.jetty.websocket.client.io;
|
||||||
|
|
||||||
import java.net.InetSocketAddress;
|
import java.net.InetSocketAddress;
|
||||||
import java.util.concurrent.Executor;
|
import java.util.concurrent.Executor;
|
||||||
import java.util.concurrent.atomic.AtomicBoolean;
|
|
||||||
|
|
||||||
|
import org.eclipse.jetty.io.ByteBufferPool;
|
||||||
import org.eclipse.jetty.io.EndPoint;
|
import org.eclipse.jetty.io.EndPoint;
|
||||||
|
import org.eclipse.jetty.util.thread.Scheduler;
|
||||||
import org.eclipse.jetty.websocket.api.BatchMode;
|
import org.eclipse.jetty.websocket.api.BatchMode;
|
||||||
import org.eclipse.jetty.websocket.api.WebSocketPolicy;
|
import org.eclipse.jetty.websocket.api.WebSocketPolicy;
|
||||||
import org.eclipse.jetty.websocket.api.WriteCallback;
|
import org.eclipse.jetty.websocket.api.WriteCallback;
|
||||||
import org.eclipse.jetty.websocket.api.extensions.Frame;
|
import org.eclipse.jetty.websocket.api.extensions.Frame;
|
||||||
import org.eclipse.jetty.websocket.api.extensions.IncomingFrames;
|
import org.eclipse.jetty.websocket.api.extensions.IncomingFrames;
|
||||||
import org.eclipse.jetty.websocket.client.masks.Masker;
|
import org.eclipse.jetty.websocket.client.masks.Masker;
|
||||||
|
import org.eclipse.jetty.websocket.client.masks.RandomMasker;
|
||||||
import org.eclipse.jetty.websocket.common.WebSocketFrame;
|
import org.eclipse.jetty.websocket.common.WebSocketFrame;
|
||||||
import org.eclipse.jetty.websocket.common.io.AbstractWebSocketConnection;
|
import org.eclipse.jetty.websocket.common.io.AbstractWebSocketConnection;
|
||||||
|
|
||||||
|
@ -37,16 +39,12 @@ import org.eclipse.jetty.websocket.common.io.AbstractWebSocketConnection;
|
||||||
*/
|
*/
|
||||||
public class WebSocketClientConnection extends AbstractWebSocketConnection
|
public class WebSocketClientConnection extends AbstractWebSocketConnection
|
||||||
{
|
{
|
||||||
private final ConnectPromise connectPromise;
|
|
||||||
private final Masker masker;
|
private final Masker masker;
|
||||||
private final AtomicBoolean opened = new AtomicBoolean(false);
|
|
||||||
|
|
||||||
public WebSocketClientConnection(EndPoint endp, Executor executor, ConnectPromise connectPromise, WebSocketPolicy policy)
|
public WebSocketClientConnection(EndPoint endp, Executor executor, Scheduler scheduler, WebSocketPolicy websocketPolicy, ByteBufferPool bufferPool)
|
||||||
{
|
{
|
||||||
super(endp,executor,connectPromise.getClient().getScheduler(),policy,connectPromise.getClient().getBufferPool());
|
super(endp,executor,scheduler,websocketPolicy,bufferPool);
|
||||||
this.connectPromise = connectPromise;
|
this.masker = new RandomMasker();
|
||||||
this.masker = connectPromise.getMasker();
|
|
||||||
assert (this.masker != null);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -60,18 +58,7 @@ public class WebSocketClientConnection extends AbstractWebSocketConnection
|
||||||
{
|
{
|
||||||
return getEndPoint().getRemoteAddress();
|
return getEndPoint().getRemoteAddress();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onOpen()
|
|
||||||
{
|
|
||||||
super.onOpen();
|
|
||||||
boolean beenOpened = opened.getAndSet(true);
|
|
||||||
if (!beenOpened)
|
|
||||||
{
|
|
||||||
connectPromise.succeeded();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Override to set the masker.
|
* Override to set the masker.
|
||||||
*/
|
*/
|
||||||
|
|
|
@ -1,162 +0,0 @@
|
||||||
//
|
|
||||||
// ========================================================================
|
|
||||||
// Copyright (c) 1995-2016 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.client.io;
|
|
||||||
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.nio.channels.SelectableChannel;
|
|
||||||
import java.nio.channels.SelectionKey;
|
|
||||||
import java.nio.channels.SocketChannel;
|
|
||||||
import java.util.concurrent.Executor;
|
|
||||||
|
|
||||||
import javax.net.ssl.SSLEngine;
|
|
||||||
|
|
||||||
import org.eclipse.jetty.io.ByteBufferPool;
|
|
||||||
import org.eclipse.jetty.io.Connection;
|
|
||||||
import org.eclipse.jetty.io.EndPoint;
|
|
||||||
import org.eclipse.jetty.io.ManagedSelector;
|
|
||||||
import org.eclipse.jetty.io.SelectChannelEndPoint;
|
|
||||||
import org.eclipse.jetty.io.SelectorManager;
|
|
||||||
import org.eclipse.jetty.io.SocketChannelEndPoint;
|
|
||||||
import org.eclipse.jetty.io.ssl.SslConnection;
|
|
||||||
import org.eclipse.jetty.util.log.Log;
|
|
||||||
import org.eclipse.jetty.util.log.Logger;
|
|
||||||
import org.eclipse.jetty.util.ssl.SslContextFactory;
|
|
||||||
import org.eclipse.jetty.websocket.api.WebSocketPolicy;
|
|
||||||
import org.eclipse.jetty.websocket.client.WebSocketClient;
|
|
||||||
|
|
||||||
public class WebSocketClientSelectorManager extends SelectorManager
|
|
||||||
{
|
|
||||||
private static final Logger LOG = Log.getLogger(WebSocketClientSelectorManager.class);
|
|
||||||
private final WebSocketPolicy policy;
|
|
||||||
private final ByteBufferPool bufferPool;
|
|
||||||
private SslContextFactory sslContextFactory;
|
|
||||||
|
|
||||||
public WebSocketClientSelectorManager(WebSocketClient client)
|
|
||||||
{
|
|
||||||
super(client.getExecutor(),client.getScheduler());
|
|
||||||
this.bufferPool = client.getBufferPool();
|
|
||||||
this.policy = client.getPolicy();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void connectionFailed(SelectableChannel channel, Throwable ex, Object attachment)
|
|
||||||
{
|
|
||||||
if (LOG.isDebugEnabled())
|
|
||||||
LOG.debug("Connection Failed",ex);
|
|
||||||
ConnectPromise connect = (ConnectPromise)attachment;
|
|
||||||
connect.failed(ex);
|
|
||||||
}
|
|
||||||
|
|
||||||
public SslContextFactory getSslContextFactory()
|
|
||||||
{
|
|
||||||
return sslContextFactory;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Connection newConnection(final SelectableChannel channel, EndPoint endPoint, final Object attachment) throws IOException
|
|
||||||
{
|
|
||||||
if (LOG.isDebugEnabled())
|
|
||||||
LOG.debug("newConnection({},{},{})",channel,endPoint,attachment);
|
|
||||||
ConnectPromise connectPromise = (ConnectPromise)attachment;
|
|
||||||
|
|
||||||
try
|
|
||||||
{
|
|
||||||
String scheme = connectPromise.getRequest().getRequestURI().getScheme();
|
|
||||||
|
|
||||||
if ("wss".equalsIgnoreCase(scheme))
|
|
||||||
{
|
|
||||||
// Encrypted "wss://"
|
|
||||||
SslContextFactory sslContextFactory = getSslContextFactory();
|
|
||||||
if (sslContextFactory != null)
|
|
||||||
{
|
|
||||||
SSLEngine engine = newSSLEngine(sslContextFactory,channel);
|
|
||||||
SslConnection sslConnection = new SslConnection(bufferPool,getExecutor(),endPoint,engine);
|
|
||||||
sslConnection.setRenegotiationAllowed(sslContextFactory.isRenegotiationAllowed());
|
|
||||||
EndPoint sslEndPoint = sslConnection.getDecryptedEndPoint();
|
|
||||||
|
|
||||||
Connection connection = newUpgradeConnection(channel,sslEndPoint,connectPromise);
|
|
||||||
sslEndPoint.setIdleTimeout(connectPromise.getClient().getMaxIdleTimeout());
|
|
||||||
sslEndPoint.setConnection(connection);
|
|
||||||
return sslConnection;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
throw new IOException("Cannot init SSL");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
// Standard "ws://"
|
|
||||||
endPoint.setIdleTimeout(connectPromise.getDriver().getPolicy().getIdleTimeout());
|
|
||||||
return newUpgradeConnection(channel,endPoint,connectPromise);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
catch (IOException e)
|
|
||||||
{
|
|
||||||
LOG.ignore(e);
|
|
||||||
connectPromise.failed(e);
|
|
||||||
// rethrow
|
|
||||||
throw e;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected EndPoint newEndPoint(SelectableChannel channel, ManagedSelector selector, SelectionKey selectionKey) throws IOException
|
|
||||||
{
|
|
||||||
if (LOG.isDebugEnabled())
|
|
||||||
LOG.debug("newEndPoint({}, {}, {})",channel,selector,selectionKey);
|
|
||||||
SocketChannelEndPoint endp = new SocketChannelEndPoint(channel, selector, selectionKey, getScheduler());
|
|
||||||
endp.setIdleTimeout(policy.getIdleTimeout());
|
|
||||||
return endp;
|
|
||||||
}
|
|
||||||
|
|
||||||
public SSLEngine newSSLEngine(SslContextFactory sslContextFactory, SelectableChannel channel)
|
|
||||||
{
|
|
||||||
String peerHost = null;
|
|
||||||
int peerPort = 0;
|
|
||||||
if (channel instanceof SocketChannel)
|
|
||||||
{
|
|
||||||
SocketChannel sc = (SocketChannel)channel;
|
|
||||||
peerHost = sc.socket().getInetAddress().getHostName();
|
|
||||||
peerPort = sc.socket().getPort();
|
|
||||||
}
|
|
||||||
SSLEngine engine = sslContextFactory.newSSLEngine(peerHost,peerPort);
|
|
||||||
engine.setUseClientMode(true);
|
|
||||||
return engine;
|
|
||||||
}
|
|
||||||
|
|
||||||
public UpgradeConnection newUpgradeConnection(SelectableChannel channel, EndPoint endPoint, ConnectPromise connectPromise)
|
|
||||||
{
|
|
||||||
WebSocketClient client = connectPromise.getClient();
|
|
||||||
Executor executor = client.getExecutor();
|
|
||||||
UpgradeConnection connection = new UpgradeConnection(endPoint,executor,connectPromise);
|
|
||||||
return connection;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setSslContextFactory(SslContextFactory sslContextFactory)
|
|
||||||
{
|
|
||||||
this.sslContextFactory = sslContextFactory;
|
|
||||||
}
|
|
||||||
|
|
||||||
public WebSocketPolicy getPolicy()
|
|
||||||
{
|
|
||||||
return policy;
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -18,7 +18,14 @@
|
||||||
|
|
||||||
package org.eclipse.jetty.websocket.client;
|
package org.eclipse.jetty.websocket.client;
|
||||||
|
|
||||||
import static org.hamcrest.Matchers.*;
|
import static org.hamcrest.Matchers.allOf;
|
||||||
|
import static org.hamcrest.Matchers.anyOf;
|
||||||
|
import static org.hamcrest.Matchers.containsString;
|
||||||
|
import static org.hamcrest.Matchers.empty;
|
||||||
|
import static org.hamcrest.Matchers.instanceOf;
|
||||||
|
import static org.hamcrest.Matchers.is;
|
||||||
|
import static org.hamcrest.Matchers.notNullValue;
|
||||||
|
import static org.hamcrest.Matchers.nullValue;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.lang.reflect.Field;
|
import java.lang.reflect.Field;
|
||||||
|
@ -34,10 +41,12 @@ import java.util.concurrent.TimeoutException;
|
||||||
import java.util.concurrent.atomic.AtomicBoolean;
|
import java.util.concurrent.atomic.AtomicBoolean;
|
||||||
import java.util.concurrent.atomic.AtomicInteger;
|
import java.util.concurrent.atomic.AtomicInteger;
|
||||||
|
|
||||||
|
import org.eclipse.jetty.client.HttpClient;
|
||||||
|
import org.eclipse.jetty.client.http.HttpClientTransportOverHTTP;
|
||||||
import org.eclipse.jetty.io.EndPoint;
|
import org.eclipse.jetty.io.EndPoint;
|
||||||
import org.eclipse.jetty.io.EofException;
|
import org.eclipse.jetty.io.EofException;
|
||||||
import org.eclipse.jetty.io.ManagedSelector;
|
import org.eclipse.jetty.io.ManagedSelector;
|
||||||
import org.eclipse.jetty.io.SelectChannelEndPoint;
|
import org.eclipse.jetty.io.SelectorManager;
|
||||||
import org.eclipse.jetty.io.SocketChannelEndPoint;
|
import org.eclipse.jetty.io.SocketChannelEndPoint;
|
||||||
import org.eclipse.jetty.toolchain.test.EventQueue;
|
import org.eclipse.jetty.toolchain.test.EventQueue;
|
||||||
import org.eclipse.jetty.toolchain.test.TestTracker;
|
import org.eclipse.jetty.toolchain.test.TestTracker;
|
||||||
|
@ -50,8 +59,6 @@ import org.eclipse.jetty.websocket.api.ProtocolException;
|
||||||
import org.eclipse.jetty.websocket.api.Session;
|
import org.eclipse.jetty.websocket.api.Session;
|
||||||
import org.eclipse.jetty.websocket.api.StatusCode;
|
import org.eclipse.jetty.websocket.api.StatusCode;
|
||||||
import org.eclipse.jetty.websocket.api.WebSocketAdapter;
|
import org.eclipse.jetty.websocket.api.WebSocketAdapter;
|
||||||
import org.eclipse.jetty.websocket.client.io.ConnectionManager;
|
|
||||||
import org.eclipse.jetty.websocket.client.io.WebSocketClientSelectorManager;
|
|
||||||
import org.eclipse.jetty.websocket.common.CloseInfo;
|
import org.eclipse.jetty.websocket.common.CloseInfo;
|
||||||
import org.eclipse.jetty.websocket.common.OpCode;
|
import org.eclipse.jetty.websocket.common.OpCode;
|
||||||
import org.eclipse.jetty.websocket.common.Parser;
|
import org.eclipse.jetty.websocket.common.Parser;
|
||||||
|
@ -74,10 +81,10 @@ import org.junit.Test;
|
||||||
public class ClientCloseTest
|
public class ClientCloseTest
|
||||||
{
|
{
|
||||||
private static final Logger LOG = Log.getLogger(ClientCloseTest.class);
|
private static final Logger LOG = Log.getLogger(ClientCloseTest.class);
|
||||||
|
|
||||||
private static class CloseTrackingSocket extends WebSocketAdapter
|
private static class CloseTrackingSocket extends WebSocketAdapter
|
||||||
{
|
{
|
||||||
private static final Logger LOG = Log.getLogger(ClientCloseTest.CloseTrackingSocket.class);
|
private static final Logger LOG = ClientCloseTest.LOG.getLogger("CloseTrackingSocket");
|
||||||
|
|
||||||
public int closeCode = -1;
|
public int closeCode = -1;
|
||||||
public String closeReason = null;
|
public String closeReason = null;
|
||||||
|
@ -148,6 +155,7 @@ public class ClientCloseTest
|
||||||
@Override
|
@Override
|
||||||
public void onWebSocketConnect(Session session)
|
public void onWebSocketConnect(Session session)
|
||||||
{
|
{
|
||||||
|
LOG.debug("onWebSocketConnect({})",session);
|
||||||
super.onWebSocketConnect(session);
|
super.onWebSocketConnect(session);
|
||||||
openLatch.countDown();
|
openLatch.countDown();
|
||||||
}
|
}
|
||||||
|
@ -255,42 +263,20 @@ public class ClientCloseTest
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static class TestWebSocketClient extends WebSocketClient
|
public static class TestClientTransportOverHTTP extends HttpClientTransportOverHTTP
|
||||||
{
|
{
|
||||||
@Override
|
@Override
|
||||||
protected ConnectionManager newConnectionManager()
|
protected SelectorManager newSelectorManager(HttpClient client)
|
||||||
{
|
{
|
||||||
return new TestConnectionManager(this);
|
return new ClientSelectorManager(client, 1){
|
||||||
}
|
@Override
|
||||||
}
|
protected EndPoint newEndPoint(SelectableChannel channel, ManagedSelector selector, SelectionKey key)
|
||||||
|
{
|
||||||
public static class TestConnectionManager extends ConnectionManager
|
TestEndPoint endPoint = new TestEndPoint(channel,selector,key,getScheduler());
|
||||||
{
|
endPoint.setIdleTimeout(client.getIdleTimeout());
|
||||||
public TestConnectionManager(WebSocketClient client)
|
return endPoint;
|
||||||
{
|
}
|
||||||
super(client);
|
};
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected WebSocketClientSelectorManager newWebSocketClientSelectorManager(WebSocketClient client)
|
|
||||||
{
|
|
||||||
return new TestSelectorManager(client);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public static class TestSelectorManager extends WebSocketClientSelectorManager
|
|
||||||
{
|
|
||||||
public TestSelectorManager(WebSocketClient client)
|
|
||||||
{
|
|
||||||
super(client);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected EndPoint newEndPoint(SelectableChannel channel, ManagedSelector selectSet, SelectionKey selectionKey) throws IOException
|
|
||||||
{
|
|
||||||
TestEndPoint endp = new TestEndPoint(channel,selectSet,selectionKey,getScheduler());
|
|
||||||
endp.setIdleTimeout(getPolicy().getIdleTimeout());
|
|
||||||
return endp;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -315,7 +301,9 @@ public class ClientCloseTest
|
||||||
@Before
|
@Before
|
||||||
public void startClient() throws Exception
|
public void startClient() throws Exception
|
||||||
{
|
{
|
||||||
client = new TestWebSocketClient();
|
HttpClient httpClient = new HttpClient(new TestClientTransportOverHTTP(), null);
|
||||||
|
client = new WebSocketClient(httpClient);
|
||||||
|
client.addBean(httpClient);
|
||||||
client.start();
|
client.start();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -329,10 +317,7 @@ public class ClientCloseTest
|
||||||
@After
|
@After
|
||||||
public void stopClient() throws Exception
|
public void stopClient() throws Exception
|
||||||
{
|
{
|
||||||
if (client.isRunning())
|
client.stop();
|
||||||
{
|
|
||||||
client.stop();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@After
|
@After
|
||||||
|
@ -390,6 +375,7 @@ public class ClientCloseTest
|
||||||
clientSocket.assertReceivedCloseEvent(timeout,is(StatusCode.NORMAL),containsString("From Server"));
|
clientSocket.assertReceivedCloseEvent(timeout,is(StatusCode.NORMAL),containsString("From Server"));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Ignore("Need sbordet's help here")
|
||||||
@Test
|
@Test
|
||||||
public void testNetworkCongestion() throws Exception
|
public void testNetworkCongestion() throws Exception
|
||||||
{
|
{
|
||||||
|
@ -562,7 +548,7 @@ public class ClientCloseTest
|
||||||
clientSocket.assertReceivedCloseEvent(timeout,is(StatusCode.SHUTDOWN),containsString("Timeout"));
|
clientSocket.assertReceivedCloseEvent(timeout,is(StatusCode.SHUTDOWN),containsString("Timeout"));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test(timeout = 5000L)
|
||||||
public void testStopLifecycle() throws Exception
|
public void testStopLifecycle() throws Exception
|
||||||
{
|
{
|
||||||
// Set client timeout
|
// Set client timeout
|
||||||
|
|
|
@ -18,19 +18,24 @@
|
||||||
|
|
||||||
package org.eclipse.jetty.websocket.client;
|
package org.eclipse.jetty.websocket.client;
|
||||||
|
|
||||||
import static org.hamcrest.Matchers.*;
|
import static org.hamcrest.Matchers.greaterThanOrEqualTo;
|
||||||
import static org.junit.Assert.*;
|
import static org.hamcrest.Matchers.instanceOf;
|
||||||
|
import static org.hamcrest.Matchers.is;
|
||||||
|
import static org.hamcrest.Matchers.notNullValue;
|
||||||
|
import static org.junit.Assert.assertThat;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.net.ConnectException;
|
import java.net.ConnectException;
|
||||||
import java.net.SocketTimeoutException;
|
import java.net.SocketTimeoutException;
|
||||||
import java.net.URI;
|
import java.net.URI;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.concurrent.CompletableFuture;
|
||||||
import java.util.concurrent.ExecutionException;
|
import java.util.concurrent.ExecutionException;
|
||||||
import java.util.concurrent.Future;
|
import java.util.concurrent.Future;
|
||||||
import java.util.concurrent.TimeUnit;
|
import java.util.concurrent.TimeUnit;
|
||||||
import java.util.concurrent.TimeoutException;
|
import java.util.concurrent.TimeoutException;
|
||||||
|
|
||||||
|
import org.eclipse.jetty.client.HttpClient;
|
||||||
import org.eclipse.jetty.toolchain.test.OS;
|
import org.eclipse.jetty.toolchain.test.OS;
|
||||||
import org.eclipse.jetty.toolchain.test.TestTracker;
|
import org.eclipse.jetty.toolchain.test.TestTracker;
|
||||||
import org.eclipse.jetty.websocket.api.Session;
|
import org.eclipse.jetty.websocket.api.Session;
|
||||||
|
@ -87,7 +92,8 @@ public class ClientConnectTest
|
||||||
@Before
|
@Before
|
||||||
public void startClient() throws Exception
|
public void startClient() throws Exception
|
||||||
{
|
{
|
||||||
client = new WebSocketClient(bufferPool);
|
client = new WebSocketClient();
|
||||||
|
client.setBufferPool(bufferPool);
|
||||||
client.setConnectTimeout(timeout);
|
client.setConnectTimeout(timeout);
|
||||||
client.start();
|
client.start();
|
||||||
}
|
}
|
||||||
|
@ -124,10 +130,36 @@ public class ClientConnectTest
|
||||||
|
|
||||||
Session sess = future.get(30,TimeUnit.SECONDS);
|
Session sess = future.get(30,TimeUnit.SECONDS);
|
||||||
|
|
||||||
sess.close();
|
wsocket.waitForConnected(1, TimeUnit.SECONDS);
|
||||||
|
|
||||||
assertThat("Connect.UpgradeRequest", wsocket.connectUpgradeRequest, notNullValue());
|
assertThat("Connect.UpgradeRequest", wsocket.connectUpgradeRequest, notNullValue());
|
||||||
assertThat("Connect.UpgradeResponse", wsocket.connectUpgradeResponse, notNullValue());
|
assertThat("Connect.UpgradeResponse", wsocket.connectUpgradeResponse, notNullValue());
|
||||||
|
|
||||||
|
sess.close();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testAltConnect() throws Exception
|
||||||
|
{
|
||||||
|
JettyTrackingSocket wsocket = new JettyTrackingSocket();
|
||||||
|
URI wsUri = server.getWsUri();
|
||||||
|
|
||||||
|
HttpClient httpClient = new HttpClient();
|
||||||
|
httpClient.start();
|
||||||
|
|
||||||
|
WebSocketUpgradeRequest req = new WebSocketUpgradeRequest(new WebSocketClient(), httpClient, wsUri, wsocket);
|
||||||
|
req.header("X-Foo","Req");
|
||||||
|
CompletableFuture<Session> sess = req.sendAsync();
|
||||||
|
|
||||||
|
sess.thenAccept((s) -> {
|
||||||
|
System.out.printf("Session: %s%n",s);
|
||||||
|
s.close();
|
||||||
|
assertThat("Connect.UpgradeRequest",wsocket.connectUpgradeRequest,notNullValue());
|
||||||
|
assertThat("Connect.UpgradeResponse",wsocket.connectUpgradeResponse,notNullValue());
|
||||||
|
});
|
||||||
|
|
||||||
|
IBlockheadServerConnection connection = server.accept();
|
||||||
|
connection.upgrade();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
@ -141,7 +173,9 @@ public class ClientConnectTest
|
||||||
IBlockheadServerConnection connection = server.accept();
|
IBlockheadServerConnection connection = server.accept();
|
||||||
connection.readRequest();
|
connection.readRequest();
|
||||||
// no upgrade, just fail with a 404 error
|
// no upgrade, just fail with a 404 error
|
||||||
connection.respond("HTTP/1.1 404 NOT FOUND\r\n\r\n");
|
connection.respond("HTTP/1.1 404 NOT FOUND\r\n" +
|
||||||
|
"Content-Length: 0\r\n" +
|
||||||
|
"\r\n");
|
||||||
|
|
||||||
// The attempt to get upgrade response future should throw error
|
// The attempt to get upgrade response future should throw error
|
||||||
try
|
try
|
||||||
|
@ -170,7 +204,9 @@ public class ClientConnectTest
|
||||||
IBlockheadServerConnection connection = server.accept();
|
IBlockheadServerConnection connection = server.accept();
|
||||||
connection.readRequest();
|
connection.readRequest();
|
||||||
// Send OK to GET but not upgrade
|
// Send OK to GET but not upgrade
|
||||||
connection.respond("HTTP/1.1 200 OK\r\n\r\n");
|
connection.respond("HTTP/1.1 200 OK\r\n" +
|
||||||
|
"Content-Length: 0\r\n" +
|
||||||
|
"\r\n");
|
||||||
|
|
||||||
// The attempt to get upgrade response future should throw error
|
// The attempt to get upgrade response future should throw error
|
||||||
try
|
try
|
||||||
|
@ -205,6 +241,7 @@ public class ClientConnectTest
|
||||||
resp.append("HTTP/1.1 200 OK\r\n"); // intentionally 200 (not 101)
|
resp.append("HTTP/1.1 200 OK\r\n"); // intentionally 200 (not 101)
|
||||||
// Include a value accept key
|
// Include a value accept key
|
||||||
resp.append("Sec-WebSocket-Accept: ").append(AcceptHash.hashKey(key)).append("\r\n");
|
resp.append("Sec-WebSocket-Accept: ").append(AcceptHash.hashKey(key)).append("\r\n");
|
||||||
|
resp.append("Content-Length: 0\r\n");
|
||||||
resp.append("\r\n");
|
resp.append("\r\n");
|
||||||
connection.respond(resp.toString());
|
connection.respond(resp.toString());
|
||||||
|
|
||||||
|
|
|
@ -18,15 +18,19 @@
|
||||||
|
|
||||||
package org.eclipse.jetty.websocket.client;
|
package org.eclipse.jetty.websocket.client;
|
||||||
|
|
||||||
import static org.hamcrest.Matchers.*;
|
import static org.hamcrest.Matchers.containsString;
|
||||||
import static org.junit.Assert.*;
|
import static org.hamcrest.Matchers.is;
|
||||||
|
import static org.junit.Assert.assertThat;
|
||||||
|
import static org.junit.Assert.assertTrue;
|
||||||
|
|
||||||
import java.net.CookieManager;
|
import java.net.CookieManager;
|
||||||
import java.net.HttpCookie;
|
import java.net.HttpCookie;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.concurrent.CountDownLatch;
|
||||||
import java.util.concurrent.Future;
|
import java.util.concurrent.Future;
|
||||||
import java.util.concurrent.TimeUnit;
|
import java.util.concurrent.TimeUnit;
|
||||||
|
import java.util.concurrent.TimeoutException;
|
||||||
|
|
||||||
import org.eclipse.jetty.toolchain.test.EventQueue;
|
import org.eclipse.jetty.toolchain.test.EventQueue;
|
||||||
import org.eclipse.jetty.util.log.Log;
|
import org.eclipse.jetty.util.log.Log;
|
||||||
|
@ -51,18 +55,33 @@ public class CookieTest
|
||||||
{
|
{
|
||||||
public EventQueue<String> messageQueue = new EventQueue<>();
|
public EventQueue<String> messageQueue = new EventQueue<>();
|
||||||
public EventQueue<Throwable> errorQueue = new EventQueue<>();
|
public EventQueue<Throwable> errorQueue = new EventQueue<>();
|
||||||
|
private CountDownLatch openLatch = new CountDownLatch(1);
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onWebSocketConnect(Session sess)
|
||||||
|
{
|
||||||
|
openLatch.countDown();
|
||||||
|
super.onWebSocketConnect(sess);
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onWebSocketText(String message)
|
public void onWebSocketText(String message)
|
||||||
{
|
{
|
||||||
|
System.err.printf("onTEXT - %s%n",message);
|
||||||
messageQueue.add(message);
|
messageQueue.add(message);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onWebSocketError(Throwable cause)
|
public void onWebSocketError(Throwable cause)
|
||||||
{
|
{
|
||||||
|
System.err.printf("onERROR - %s%n",cause);
|
||||||
errorQueue.add(cause);
|
errorQueue.add(cause);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void awaitOpen(int duration, TimeUnit unit) throws InterruptedException
|
||||||
|
{
|
||||||
|
assertTrue("Open Latch", openLatch.await(duration,unit));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private WebSocketClient client;
|
private WebSocketClient client;
|
||||||
|
@ -125,7 +144,7 @@ public class CookieTest
|
||||||
String serverCookies = confirmClientUpgradeAndCookies(clientSocket,clientConnectFuture,serverConn);
|
String serverCookies = confirmClientUpgradeAndCookies(clientSocket,clientConnectFuture,serverConn);
|
||||||
|
|
||||||
assertThat("Cookies seen at server side",serverCookies,containsString("hello=world"));
|
assertThat("Cookies seen at server side",serverCookies,containsString("hello=world"));
|
||||||
assertThat("Cookies seen at server side",serverCookies,containsString("foo=\"bar is the word\""));
|
assertThat("Cookies seen at server side",serverCookies,containsString("foo=bar is the word"));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
@ -149,7 +168,7 @@ public class CookieTest
|
||||||
// client confirms upgrade and receipt of frame
|
// client confirms upgrade and receipt of frame
|
||||||
String serverCookies = confirmClientUpgradeAndCookies(clientSocket,clientConnectFuture,serverConn);
|
String serverCookies = confirmClientUpgradeAndCookies(clientSocket,clientConnectFuture,serverConn);
|
||||||
|
|
||||||
Assert.assertThat("Cookies seen at server side",serverCookies,containsString("hello=\"world\""));
|
Assert.assertThat("Cookies seen at server side",serverCookies,containsString("hello=world"));
|
||||||
}
|
}
|
||||||
|
|
||||||
private String confirmClientUpgradeAndCookies(CookieTrackingSocket clientSocket, Future<Session> clientConnectFuture, IBlockheadServerConnection serverConn)
|
private String confirmClientUpgradeAndCookies(CookieTrackingSocket clientSocket, Future<Session> clientConnectFuture, IBlockheadServerConnection serverConn)
|
||||||
|
@ -164,18 +183,29 @@ public class CookieTest
|
||||||
serverCookieFrame.setFin(true);
|
serverCookieFrame.setFin(true);
|
||||||
serverCookieFrame.setPayload(QuoteUtil.join(upgradeRequestCookies,","));
|
serverCookieFrame.setPayload(QuoteUtil.join(upgradeRequestCookies,","));
|
||||||
serverConn.write(serverCookieFrame);
|
serverConn.write(serverCookieFrame);
|
||||||
|
serverConn.flush();
|
||||||
// Server closes connection
|
|
||||||
serverConn.close(StatusCode.NORMAL);
|
|
||||||
|
|
||||||
// Confirm client connect on future
|
// Confirm client connect on future
|
||||||
clientConnectFuture.get(30000,TimeUnit.MILLISECONDS);
|
clientConnectFuture.get(10,TimeUnit.SECONDS);
|
||||||
|
clientSocket.awaitOpen(2,TimeUnit.SECONDS);
|
||||||
// Wait for client receipt of cookie frame via client websocket
|
|
||||||
clientSocket.messageQueue.awaitEventCount(1,2,TimeUnit.SECONDS);
|
try
|
||||||
|
{
|
||||||
|
// Wait for client receipt of cookie frame via client websocket
|
||||||
|
clientSocket.messageQueue.awaitEventCount(1, 3, TimeUnit.SECONDS);
|
||||||
|
}
|
||||||
|
catch (TimeoutException e)
|
||||||
|
{
|
||||||
|
e.printStackTrace(System.err);
|
||||||
|
assertThat("Message Count", clientSocket.messageQueue.size(), is(1));
|
||||||
|
}
|
||||||
|
|
||||||
String cookies = clientSocket.messageQueue.poll();
|
String cookies = clientSocket.messageQueue.poll();
|
||||||
LOG.debug("Cookies seen at server: {}",cookies);
|
LOG.debug("Cookies seen at server: {}",cookies);
|
||||||
|
|
||||||
|
// Server closes connection
|
||||||
|
serverConn.close(StatusCode.NORMAL);
|
||||||
|
|
||||||
return cookies;
|
return cookies;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -20,6 +20,8 @@ package org.eclipse.jetty.websocket.client;
|
||||||
|
|
||||||
import static org.hamcrest.Matchers.greaterThanOrEqualTo;
|
import static org.hamcrest.Matchers.greaterThanOrEqualTo;
|
||||||
import static org.hamcrest.Matchers.is;
|
import static org.hamcrest.Matchers.is;
|
||||||
|
import static org.hamcrest.Matchers.notNullValue;
|
||||||
|
import static org.junit.Assert.assertThat;
|
||||||
|
|
||||||
import java.util.concurrent.CountDownLatch;
|
import java.util.concurrent.CountDownLatch;
|
||||||
import java.util.concurrent.Exchanger;
|
import java.util.concurrent.Exchanger;
|
||||||
|
@ -29,9 +31,9 @@ import java.util.concurrent.TimeoutException;
|
||||||
import org.eclipse.jetty.toolchain.test.EventQueue;
|
import org.eclipse.jetty.toolchain.test.EventQueue;
|
||||||
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.Session;
|
|
||||||
import org.eclipse.jetty.websocket.api.UpgradeRequest;
|
import org.eclipse.jetty.websocket.api.UpgradeRequest;
|
||||||
import org.eclipse.jetty.websocket.api.UpgradeResponse;
|
import org.eclipse.jetty.websocket.api.UpgradeResponse;
|
||||||
|
import org.eclipse.jetty.websocket.api.Session;
|
||||||
import org.eclipse.jetty.websocket.api.WebSocketAdapter;
|
import org.eclipse.jetty.websocket.api.WebSocketAdapter;
|
||||||
import org.junit.Assert;
|
import org.junit.Assert;
|
||||||
|
|
||||||
|
@ -84,16 +86,19 @@ public class JettyTrackingSocket extends WebSocketAdapter
|
||||||
|
|
||||||
public void assertNotClosed()
|
public void assertNotClosed()
|
||||||
{
|
{
|
||||||
|
LOG.debug("assertNotClosed() - {}", closeLatch.getCount());
|
||||||
Assert.assertThat("Closed Latch",closeLatch.getCount(),greaterThanOrEqualTo(1L));
|
Assert.assertThat("Closed Latch",closeLatch.getCount(),greaterThanOrEqualTo(1L));
|
||||||
}
|
}
|
||||||
|
|
||||||
public void assertNotOpened()
|
public void assertNotOpened()
|
||||||
{
|
{
|
||||||
|
LOG.debug("assertNotOpened() - {}", openLatch.getCount());
|
||||||
Assert.assertThat("Open Latch",openLatch.getCount(),greaterThanOrEqualTo(1L));
|
Assert.assertThat("Open Latch",openLatch.getCount(),greaterThanOrEqualTo(1L));
|
||||||
}
|
}
|
||||||
|
|
||||||
public void assertWasOpened() throws InterruptedException
|
public void assertWasOpened() throws InterruptedException
|
||||||
{
|
{
|
||||||
|
LOG.debug("assertWasOpened() - {}", openLatch.getCount());
|
||||||
Assert.assertThat("Was Opened",openLatch.await(30,TimeUnit.SECONDS),is(true));
|
Assert.assertThat("Was Opened",openLatch.await(30,TimeUnit.SECONDS),is(true));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -128,6 +133,7 @@ public class JettyTrackingSocket extends WebSocketAdapter
|
||||||
public void onWebSocketConnect(Session session)
|
public void onWebSocketConnect(Session session)
|
||||||
{
|
{
|
||||||
super.onWebSocketConnect(session);
|
super.onWebSocketConnect(session);
|
||||||
|
assertThat("Session", session, notNullValue());
|
||||||
connectUpgradeRequest = session.getUpgradeRequest();
|
connectUpgradeRequest = session.getUpgradeRequest();
|
||||||
connectUpgradeResponse = session.getUpgradeResponse();
|
connectUpgradeResponse = session.getUpgradeResponse();
|
||||||
openLatch.countDown();
|
openLatch.countDown();
|
||||||
|
|
|
@ -0,0 +1,111 @@
|
||||||
|
//
|
||||||
|
// ========================================================================
|
||||||
|
// Copyright (c) 1995-2016 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.client;
|
||||||
|
|
||||||
|
import static org.hamcrest.Matchers.is;
|
||||||
|
import static org.hamcrest.Matchers.nullValue;
|
||||||
|
import static org.junit.Assert.assertThat;
|
||||||
|
|
||||||
|
import org.eclipse.jetty.client.HttpClient;
|
||||||
|
import org.junit.Test;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Test various init techniques for WebSocketClient
|
||||||
|
*/
|
||||||
|
public class WebSocketClientInitTest
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* This is the new Jetty 9.4 advanced usage mode of WebSocketClient,
|
||||||
|
* that allows for more robust HTTP configurations (such as authentication,
|
||||||
|
* cookies, and proxies)
|
||||||
|
*
|
||||||
|
* @throws Exception
|
||||||
|
* on test failure
|
||||||
|
*/
|
||||||
|
@Test
|
||||||
|
public void testInit_HttpClient_StartedOutside() throws Exception
|
||||||
|
{
|
||||||
|
HttpClient http = new HttpClient();
|
||||||
|
http.start();
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
WebSocketClient ws = new WebSocketClient(http);
|
||||||
|
ws.start();
|
||||||
|
try
|
||||||
|
{
|
||||||
|
assertThat("HttpClient",ws.getHttpClient(),is(http));
|
||||||
|
|
||||||
|
assertThat("WebSocketClient started",ws.isStarted(),is(true));
|
||||||
|
assertThat("HttpClient started",http.isStarted(),is(true));
|
||||||
|
|
||||||
|
HttpClient httpBean = ws.getBean(HttpClient.class);
|
||||||
|
assertThat("HttpClient should not be found in WebSocketClient",httpBean,nullValue());
|
||||||
|
assertThat("HttpClient bean is managed",ws.isManaged(httpBean),is(false));
|
||||||
|
assertThat("WebSocketClient should not be found in HttpClient",http.getBean(WebSocketClient.class),nullValue());
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
ws.stop();
|
||||||
|
}
|
||||||
|
assertThat("WebSocketClient stopped",ws.isStopped(),is(true));
|
||||||
|
assertThat("HttpClient stopped",http.isStopped(),is(false));
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
http.stop();
|
||||||
|
}
|
||||||
|
|
||||||
|
assertThat("HttpClient stopped",http.isStopped(),is(true));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This is the backward compatibility mode of WebSocketClient.
|
||||||
|
* This is also the primary mode that JSR356 Standalone WebSocket Client is initialized.
|
||||||
|
*
|
||||||
|
* @throws Exception
|
||||||
|
* on test failure
|
||||||
|
*/
|
||||||
|
@Test
|
||||||
|
public void testInit_HttpClient_SyntheticStart() throws Exception
|
||||||
|
{
|
||||||
|
HttpClient http = null;
|
||||||
|
WebSocketClient ws = new WebSocketClient();
|
||||||
|
ws.start();
|
||||||
|
try
|
||||||
|
{
|
||||||
|
http = ws.getHttpClient();
|
||||||
|
|
||||||
|
assertThat("WebSocketClient started",ws.isStarted(),is(true));
|
||||||
|
assertThat("HttpClient started",http.isStarted(),is(true));
|
||||||
|
|
||||||
|
HttpClient httpBean = ws.getBean(HttpClient.class);
|
||||||
|
assertThat("HttpClient bean found in WebSocketClient",httpBean,is(http));
|
||||||
|
assertThat("HttpClient bean is managed",ws.isManaged(httpBean),is(true));
|
||||||
|
assertThat("WebSocketClient should not be found in HttpClient",http.getBean(WebSocketClient.class),nullValue());
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
ws.stop();
|
||||||
|
}
|
||||||
|
|
||||||
|
assertThat("WebSocketClient stopped",ws.isStopped(),is(true));
|
||||||
|
assertThat("HttpClient stopped",http.isStopped(),is(true));
|
||||||
|
}
|
||||||
|
}
|
|
@ -18,7 +18,10 @@
|
||||||
|
|
||||||
package org.eclipse.jetty.websocket.client;
|
package org.eclipse.jetty.websocket.client;
|
||||||
|
|
||||||
import static org.hamcrest.Matchers.*;
|
import static org.hamcrest.Matchers.greaterThan;
|
||||||
|
import static org.hamcrest.Matchers.is;
|
||||||
|
import static org.hamcrest.Matchers.notNullValue;
|
||||||
|
import static org.hamcrest.Matchers.nullValue;
|
||||||
|
|
||||||
import java.net.InetSocketAddress;
|
import java.net.InetSocketAddress;
|
||||||
import java.net.URI;
|
import java.net.URI;
|
||||||
|
@ -50,329 +53,263 @@ import org.junit.runner.RunWith;
|
||||||
public class WebSocketClientTest
|
public class WebSocketClientTest
|
||||||
{
|
{
|
||||||
private BlockheadServer server;
|
private BlockheadServer server;
|
||||||
|
private WebSocketClient client;
|
||||||
|
|
||||||
@Before
|
@Before
|
||||||
public void startServer() throws Exception
|
public void startClientServer() throws Exception
|
||||||
{
|
{
|
||||||
|
client = new WebSocketClient();
|
||||||
|
client.start();
|
||||||
server = new BlockheadServer();
|
server = new BlockheadServer();
|
||||||
server.start();
|
server.start();
|
||||||
}
|
}
|
||||||
|
|
||||||
@After
|
@After
|
||||||
public void stopServer() throws Exception
|
public void stopClientServer() throws Exception
|
||||||
{
|
{
|
||||||
|
client.stop();
|
||||||
server.stop();
|
server.stop();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test(expected = IllegalArgumentException.class)
|
@Test(expected = IllegalArgumentException.class)
|
||||||
public void testAddExtension_NotInstalled() throws Exception
|
public void testAddExtension_NotInstalled() throws Exception
|
||||||
{
|
{
|
||||||
WebSocketClient client = new WebSocketClient();
|
JettyTrackingSocket cliSock = new JettyTrackingSocket();
|
||||||
client.start();
|
|
||||||
try
|
|
||||||
{
|
|
||||||
JettyTrackingSocket cliSock = new JettyTrackingSocket();
|
|
||||||
|
|
||||||
client.getPolicy().setIdleTimeout(10000);
|
client.getPolicy().setIdleTimeout(10000);
|
||||||
|
|
||||||
URI wsUri = server.getWsUri();
|
URI wsUri = server.getWsUri();
|
||||||
ClientUpgradeRequest request = new ClientUpgradeRequest();
|
ClientUpgradeRequest request = new ClientUpgradeRequest();
|
||||||
request.setSubProtocols("echo");
|
request.setSubProtocols("echo");
|
||||||
request.addExtensions("x-bad");
|
request.addExtensions("x-bad");
|
||||||
|
|
||||||
// Should trigger failure on bad extension
|
// Should trigger failure on bad extension
|
||||||
client.connect(cliSock,wsUri,request);
|
client.connect(cliSock,wsUri,request);
|
||||||
}
|
|
||||||
finally
|
|
||||||
{
|
|
||||||
client.stop();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testBasicEcho_FromClient() throws Exception
|
public void testBasicEcho_FromClient() throws Exception
|
||||||
{
|
{
|
||||||
WebSocketClient client = new WebSocketClient();
|
JettyTrackingSocket cliSock = new JettyTrackingSocket();
|
||||||
client.start();
|
|
||||||
try
|
|
||||||
{
|
|
||||||
JettyTrackingSocket cliSock = new JettyTrackingSocket();
|
|
||||||
|
|
||||||
client.getPolicy().setIdleTimeout(10000);
|
client.getPolicy().setIdleTimeout(10000);
|
||||||
|
|
||||||
URI wsUri = server.getWsUri();
|
URI wsUri = server.getWsUri();
|
||||||
ClientUpgradeRequest request = new ClientUpgradeRequest();
|
ClientUpgradeRequest request = new ClientUpgradeRequest();
|
||||||
request.setSubProtocols("echo");
|
request.setSubProtocols("echo");
|
||||||
Future<Session> future = client.connect(cliSock,wsUri,request);
|
Future<Session> future = client.connect(cliSock,wsUri,request);
|
||||||
|
|
||||||
final IBlockheadServerConnection srvSock = server.accept();
|
final IBlockheadServerConnection srvSock = server.accept();
|
||||||
srvSock.upgrade();
|
srvSock.upgrade();
|
||||||
|
|
||||||
Session sess = future.get(30,TimeUnit.SECONDS);
|
Session sess = future.get(30,TimeUnit.SECONDS);
|
||||||
Assert.assertThat("Session",sess,notNullValue());
|
Assert.assertThat("Session",sess,notNullValue());
|
||||||
Assert.assertThat("Session.open",sess.isOpen(),is(true));
|
Assert.assertThat("Session.open",sess.isOpen(),is(true));
|
||||||
Assert.assertThat("Session.upgradeRequest",sess.getUpgradeRequest(),notNullValue());
|
Assert.assertThat("Session.upgradeRequest",sess.getUpgradeRequest(),notNullValue());
|
||||||
Assert.assertThat("Session.upgradeResponse",sess.getUpgradeResponse(),notNullValue());
|
Assert.assertThat("Session.upgradeResponse",sess.getUpgradeResponse(),notNullValue());
|
||||||
|
|
||||||
cliSock.assertWasOpened();
|
cliSock.assertWasOpened();
|
||||||
cliSock.assertNotClosed();
|
cliSock.assertNotClosed();
|
||||||
|
|
||||||
Collection<WebSocketSession> sessions = client.getBeans(WebSocketSession.class);
|
Collection<WebSocketSession> sessions = client.getOpenSessions();
|
||||||
Assert.assertThat("client.connectionManager.sessions.size",sessions.size(),is(1));
|
Assert.assertThat("client.connectionManager.sessions.size",sessions.size(),is(1));
|
||||||
|
|
||||||
RemoteEndpoint remote = cliSock.getSession().getRemote();
|
RemoteEndpoint remote = cliSock.getSession().getRemote();
|
||||||
remote.sendStringByFuture("Hello World!");
|
remote.sendStringByFuture("Hello World!");
|
||||||
if (remote.getBatchMode() == BatchMode.ON)
|
if (remote.getBatchMode() == BatchMode.ON)
|
||||||
remote.flush();
|
remote.flush();
|
||||||
srvSock.echoMessage(1,30,TimeUnit.SECONDS);
|
srvSock.echoMessage(1,30,TimeUnit.SECONDS);
|
||||||
// wait for response from server
|
// wait for response from server
|
||||||
cliSock.waitForMessage(30,TimeUnit.SECONDS);
|
cliSock.waitForMessage(30,TimeUnit.SECONDS);
|
||||||
|
|
||||||
cliSock.assertMessage("Hello World!");
|
cliSock.assertMessage("Hello World!");
|
||||||
}
|
|
||||||
finally
|
|
||||||
{
|
|
||||||
client.stop();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testBasicEcho_UsingCallback() throws Exception
|
public void testBasicEcho_UsingCallback() throws Exception
|
||||||
{
|
{
|
||||||
WebSocketClient client = new WebSocketClient();
|
client.setMaxIdleTimeout(160000);
|
||||||
client.start();
|
JettyTrackingSocket cliSock = new JettyTrackingSocket();
|
||||||
try
|
|
||||||
{
|
|
||||||
JettyTrackingSocket cliSock = new JettyTrackingSocket();
|
|
||||||
|
|
||||||
client.getPolicy().setIdleTimeout(10000);
|
URI wsUri = server.getWsUri();
|
||||||
|
ClientUpgradeRequest request = new ClientUpgradeRequest();
|
||||||
|
request.setSubProtocols("echo");
|
||||||
|
Future<Session> future = client.connect(cliSock,wsUri,request);
|
||||||
|
|
||||||
URI wsUri = server.getWsUri();
|
final IBlockheadServerConnection srvSock = server.accept();
|
||||||
ClientUpgradeRequest request = new ClientUpgradeRequest();
|
srvSock.upgrade();
|
||||||
request.setSubProtocols("echo");
|
|
||||||
Future<Session> future = client.connect(cliSock,wsUri,request);
|
|
||||||
|
|
||||||
final IBlockheadServerConnection srvSock = server.accept();
|
Session sess = future.get(30,TimeUnit.SECONDS);
|
||||||
srvSock.upgrade();
|
Assert.assertThat("Session",sess,notNullValue());
|
||||||
|
Assert.assertThat("Session.open",sess.isOpen(),is(true));
|
||||||
|
Assert.assertThat("Session.upgradeRequest",sess.getUpgradeRequest(),notNullValue());
|
||||||
|
Assert.assertThat("Session.upgradeResponse",sess.getUpgradeResponse(),notNullValue());
|
||||||
|
|
||||||
Session sess = future.get(30,TimeUnit.SECONDS);
|
cliSock.assertWasOpened();
|
||||||
Assert.assertThat("Session",sess,notNullValue());
|
cliSock.assertNotClosed();
|
||||||
Assert.assertThat("Session.open",sess.isOpen(),is(true));
|
|
||||||
Assert.assertThat("Session.upgradeRequest",sess.getUpgradeRequest(),notNullValue());
|
|
||||||
Assert.assertThat("Session.upgradeResponse",sess.getUpgradeResponse(),notNullValue());
|
|
||||||
|
|
||||||
cliSock.assertWasOpened();
|
Collection<WebSocketSession> sessions = client.getBeans(WebSocketSession.class);
|
||||||
cliSock.assertNotClosed();
|
Assert.assertThat("client.connectionManager.sessions.size",sessions.size(),is(1));
|
||||||
|
|
||||||
Collection<WebSocketSession> sessions = client.getBeans(WebSocketSession.class);
|
FutureWriteCallback callback = new FutureWriteCallback();
|
||||||
Assert.assertThat("client.connectionManager.sessions.size",sessions.size(),is(1));
|
|
||||||
|
|
||||||
FutureWriteCallback callback = new FutureWriteCallback();
|
cliSock.getSession().getRemote().sendString("Hello World!",callback);
|
||||||
|
callback.get(1,TimeUnit.SECONDS);
|
||||||
cliSock.getSession().getRemote().sendString("Hello World!",callback);
|
|
||||||
callback.get(1,TimeUnit.SECONDS);
|
|
||||||
}
|
|
||||||
finally
|
|
||||||
{
|
|
||||||
client.stop();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testBasicEcho_FromServer() throws Exception
|
public void testBasicEcho_FromServer() throws Exception
|
||||||
{
|
{
|
||||||
WebSocketClient client = new WebSocketClient();
|
JettyTrackingSocket wsocket = new JettyTrackingSocket();
|
||||||
client.start();
|
Future<Session> future = client.connect(wsocket,server.getWsUri());
|
||||||
try
|
|
||||||
{
|
|
||||||
JettyTrackingSocket wsocket = new JettyTrackingSocket();
|
|
||||||
Future<Session> future = client.connect(wsocket,server.getWsUri());
|
|
||||||
|
|
||||||
// Server
|
// Server
|
||||||
final IBlockheadServerConnection srvSock = server.accept();
|
final IBlockheadServerConnection srvSock = server.accept();
|
||||||
srvSock.upgrade();
|
srvSock.upgrade();
|
||||||
|
|
||||||
// Validate connect
|
// Validate connect
|
||||||
Session sess = future.get(30,TimeUnit.SECONDS);
|
Session sess = future.get(30,TimeUnit.SECONDS);
|
||||||
Assert.assertThat("Session",sess,notNullValue());
|
Assert.assertThat("Session",sess,notNullValue());
|
||||||
Assert.assertThat("Session.open",sess.isOpen(),is(true));
|
Assert.assertThat("Session.open",sess.isOpen(),is(true));
|
||||||
Assert.assertThat("Session.upgradeRequest",sess.getUpgradeRequest(),notNullValue());
|
Assert.assertThat("Session.upgradeRequest",sess.getUpgradeRequest(),notNullValue());
|
||||||
Assert.assertThat("Session.upgradeResponse",sess.getUpgradeResponse(),notNullValue());
|
Assert.assertThat("Session.upgradeResponse",sess.getUpgradeResponse(),notNullValue());
|
||||||
|
|
||||||
// Have server send initial message
|
// Have server send initial message
|
||||||
srvSock.write(new TextFrame().setPayload("Hello World"));
|
srvSock.write(new TextFrame().setPayload("Hello World"));
|
||||||
|
|
||||||
// Verify connect
|
// Verify connect
|
||||||
future.get(30,TimeUnit.SECONDS);
|
future.get(30,TimeUnit.SECONDS);
|
||||||
wsocket.assertWasOpened();
|
wsocket.assertWasOpened();
|
||||||
wsocket.awaitMessage(1,TimeUnit.SECONDS,2);
|
wsocket.awaitMessage(1,TimeUnit.SECONDS,2);
|
||||||
|
|
||||||
wsocket.assertMessage("Hello World");
|
wsocket.assertMessage("Hello World");
|
||||||
}
|
|
||||||
finally
|
|
||||||
{
|
|
||||||
client.stop();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testLocalRemoteAddress() throws Exception
|
public void testLocalRemoteAddress() throws Exception
|
||||||
{
|
{
|
||||||
WebSocketClient fact = new WebSocketClient();
|
JettyTrackingSocket wsocket = new JettyTrackingSocket();
|
||||||
fact.start();
|
|
||||||
try
|
|
||||||
{
|
|
||||||
JettyTrackingSocket wsocket = new JettyTrackingSocket();
|
|
||||||
|
|
||||||
URI wsUri = server.getWsUri();
|
URI wsUri = server.getWsUri();
|
||||||
Future<Session> future = fact.connect(wsocket,wsUri);
|
Future<Session> future = client.connect(wsocket,wsUri);
|
||||||
|
|
||||||
IBlockheadServerConnection ssocket = server.accept();
|
IBlockheadServerConnection ssocket = server.accept();
|
||||||
ssocket.upgrade();
|
ssocket.upgrade();
|
||||||
|
|
||||||
future.get(30,TimeUnit.SECONDS);
|
future.get(30,TimeUnit.SECONDS);
|
||||||
|
|
||||||
Assert.assertTrue(wsocket.openLatch.await(1,TimeUnit.SECONDS));
|
Assert.assertTrue(wsocket.openLatch.await(1,TimeUnit.SECONDS));
|
||||||
|
|
||||||
InetSocketAddress local = wsocket.getSession().getLocalAddress();
|
InetSocketAddress local = wsocket.getSession().getLocalAddress();
|
||||||
InetSocketAddress remote = wsocket.getSession().getRemoteAddress();
|
InetSocketAddress remote = wsocket.getSession().getRemoteAddress();
|
||||||
|
|
||||||
Assert.assertThat("Local Socket Address",local,notNullValue());
|
Assert.assertThat("Local Socket Address",local,notNullValue());
|
||||||
Assert.assertThat("Remote Socket Address",remote,notNullValue());
|
Assert.assertThat("Remote Socket Address",remote,notNullValue());
|
||||||
|
|
||||||
// Hard to validate (in a portable unit test) the local address that was used/bound in the low level Jetty Endpoint
|
// Hard to validate (in a portable unit test) the local address that was used/bound in the low level Jetty Endpoint
|
||||||
Assert.assertThat("Local Socket Address / Host",local.getAddress().getHostAddress(),notNullValue());
|
Assert.assertThat("Local Socket Address / Host",local.getAddress().getHostAddress(),notNullValue());
|
||||||
Assert.assertThat("Local Socket Address / Port",local.getPort(),greaterThan(0));
|
Assert.assertThat("Local Socket Address / Port",local.getPort(),greaterThan(0));
|
||||||
|
|
||||||
Assert.assertThat("Remote Socket Address / Host",remote.getAddress().getHostAddress(),is(wsUri.getHost()));
|
Assert.assertThat("Remote Socket Address / Host",remote.getAddress().getHostAddress(),is(wsUri.getHost()));
|
||||||
Assert.assertThat("Remote Socket Address / Port",remote.getPort(),greaterThan(0));
|
Assert.assertThat("Remote Socket Address / Port",remote.getPort(),greaterThan(0));
|
||||||
}
|
|
||||||
finally
|
|
||||||
{
|
|
||||||
fact.stop();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testMessageBiggerThanBufferSize() throws Exception
|
public void testMessageBiggerThanBufferSize() throws Exception
|
||||||
{
|
{
|
||||||
WebSocketClient factSmall = new WebSocketClient();
|
int bufferSize = 512;
|
||||||
factSmall.start();
|
|
||||||
try
|
JettyTrackingSocket wsocket = new JettyTrackingSocket();
|
||||||
|
|
||||||
|
URI wsUri = server.getWsUri();
|
||||||
|
Future<Session> future = client.connect(wsocket,wsUri);
|
||||||
|
|
||||||
|
IBlockheadServerConnection ssocket = server.accept();
|
||||||
|
ssocket.upgrade();
|
||||||
|
|
||||||
|
future.get(30,TimeUnit.SECONDS);
|
||||||
|
|
||||||
|
Assert.assertTrue(wsocket.openLatch.await(1,TimeUnit.SECONDS));
|
||||||
|
|
||||||
|
int length = bufferSize + (bufferSize / 2); // 1.5 times buffer size
|
||||||
|
ssocket.write(0x80 | 0x01); // FIN + TEXT
|
||||||
|
ssocket.write(0x7E); // No MASK and 2 bytes length
|
||||||
|
ssocket.write(length >> 8); // first length byte
|
||||||
|
ssocket.write(length & 0xFF); // second length byte
|
||||||
|
for (int i = 0; i < length; ++i)
|
||||||
{
|
{
|
||||||
int bufferSize = 512;
|
ssocket.write('x');
|
||||||
|
|
||||||
JettyTrackingSocket wsocket = new JettyTrackingSocket();
|
|
||||||
|
|
||||||
URI wsUri = server.getWsUri();
|
|
||||||
Future<Session> future = factSmall.connect(wsocket,wsUri);
|
|
||||||
|
|
||||||
IBlockheadServerConnection ssocket = server.accept();
|
|
||||||
ssocket.upgrade();
|
|
||||||
|
|
||||||
future.get(30,TimeUnit.SECONDS);
|
|
||||||
|
|
||||||
Assert.assertTrue(wsocket.openLatch.await(1,TimeUnit.SECONDS));
|
|
||||||
|
|
||||||
int length = bufferSize + (bufferSize / 2); // 1.5 times buffer size
|
|
||||||
ssocket.write(0x80 | 0x01); // FIN + TEXT
|
|
||||||
ssocket.write(0x7E); // No MASK and 2 bytes length
|
|
||||||
ssocket.write(length >> 8); // first length byte
|
|
||||||
ssocket.write(length & 0xFF); // second length byte
|
|
||||||
for (int i = 0; i < length; ++i)
|
|
||||||
{
|
|
||||||
ssocket.write('x');
|
|
||||||
}
|
|
||||||
ssocket.flush();
|
|
||||||
|
|
||||||
Assert.assertTrue(wsocket.dataLatch.await(1000,TimeUnit.SECONDS));
|
|
||||||
}
|
|
||||||
finally
|
|
||||||
{
|
|
||||||
factSmall.stop();
|
|
||||||
}
|
}
|
||||||
|
ssocket.flush();
|
||||||
|
|
||||||
|
Assert.assertTrue(wsocket.dataLatch.await(1000,TimeUnit.SECONDS));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Ensure that <code>@WebSocket(maxTextMessageSize = 100*1024)</code> behaves as expected.
|
||||||
|
*
|
||||||
|
* @throws Exception
|
||||||
|
* on test failure
|
||||||
|
*/
|
||||||
@Test
|
@Test
|
||||||
public void testMaxMessageSize() throws Exception
|
public void testMaxMessageSize() throws Exception
|
||||||
{
|
{
|
||||||
WebSocketClient client = new WebSocketClient();
|
MaxMessageSocket wsocket = new MaxMessageSocket();
|
||||||
client.start();
|
|
||||||
try
|
|
||||||
{
|
|
||||||
MaxMessageSocket wsocket = new MaxMessageSocket();
|
|
||||||
|
|
||||||
URI wsUri = server.getWsUri();
|
URI wsUri = server.getWsUri();
|
||||||
Future<Session> future = client.connect(wsocket,wsUri);
|
Future<Session> future = client.connect(wsocket,wsUri);
|
||||||
|
|
||||||
IBlockheadServerConnection ssocket = server.accept();
|
IBlockheadServerConnection ssocket = server.accept();
|
||||||
ssocket.upgrade();
|
ssocket.upgrade();
|
||||||
|
|
||||||
wsocket.awaitConnect(1,TimeUnit.SECONDS);
|
wsocket.awaitConnect(1,TimeUnit.SECONDS);
|
||||||
|
|
||||||
Session sess = future.get(30,TimeUnit.SECONDS);
|
Session sess = future.get(30,TimeUnit.SECONDS);
|
||||||
Assert.assertThat("Session",sess,notNullValue());
|
Assert.assertThat("Session",sess,notNullValue());
|
||||||
Assert.assertThat("Session.open",sess.isOpen(),is(true));
|
Assert.assertThat("Session.open",sess.isOpen(),is(true));
|
||||||
|
|
||||||
// Create string that is larger than default size of 64k
|
// Create string that is larger than default size of 64k
|
||||||
// but smaller than maxMessageSize of 100k
|
// but smaller than maxMessageSize of 100k
|
||||||
byte buf[] = new byte[80 * 1024];
|
byte buf[] = new byte[80 * 1024];
|
||||||
Arrays.fill(buf,(byte)'x');
|
Arrays.fill(buf,(byte)'x');
|
||||||
String msg = StringUtil.toUTF8String(buf,0,buf.length);
|
String msg = StringUtil.toUTF8String(buf,0,buf.length);
|
||||||
|
|
||||||
wsocket.getSession().getRemote().sendStringByFuture(msg);
|
wsocket.getSession().getRemote().sendStringByFuture(msg);
|
||||||
ssocket.echoMessage(1,2,TimeUnit.SECONDS);
|
ssocket.echoMessage(1,2,TimeUnit.SECONDS);
|
||||||
// wait for response from server
|
// wait for response from server
|
||||||
wsocket.waitForMessage(1,TimeUnit.SECONDS);
|
wsocket.waitForMessage(1,TimeUnit.SECONDS);
|
||||||
|
|
||||||
wsocket.assertMessage(msg);
|
wsocket.assertMessage(msg);
|
||||||
|
|
||||||
Assert.assertTrue(wsocket.dataLatch.await(2,TimeUnit.SECONDS));
|
Assert.assertTrue(wsocket.dataLatch.await(2,TimeUnit.SECONDS));
|
||||||
}
|
|
||||||
finally
|
|
||||||
{
|
|
||||||
client.stop();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testParameterMap() throws Exception
|
public void testParameterMap() throws Exception
|
||||||
{
|
{
|
||||||
WebSocketClient fact = new WebSocketClient();
|
JettyTrackingSocket wsocket = new JettyTrackingSocket();
|
||||||
fact.start();
|
|
||||||
try
|
|
||||||
{
|
|
||||||
JettyTrackingSocket wsocket = new JettyTrackingSocket();
|
|
||||||
|
|
||||||
URI wsUri = server.getWsUri().resolve("/test?snack=cashews&amount=handful&brand=off");
|
URI wsUri = server.getWsUri().resolve("/test?snack=cashews&amount=handful&brand=off");
|
||||||
Future<Session> future = fact.connect(wsocket,wsUri);
|
Future<Session> future = client.connect(wsocket,wsUri);
|
||||||
|
|
||||||
IBlockheadServerConnection ssocket = server.accept();
|
IBlockheadServerConnection ssocket = server.accept();
|
||||||
ssocket.upgrade();
|
ssocket.upgrade();
|
||||||
|
|
||||||
future.get(30,TimeUnit.SECONDS);
|
future.get(30,TimeUnit.SECONDS);
|
||||||
|
|
||||||
Assert.assertTrue(wsocket.openLatch.await(1,TimeUnit.SECONDS));
|
Assert.assertTrue(wsocket.openLatch.await(1,TimeUnit.SECONDS));
|
||||||
|
|
||||||
Session session = wsocket.getSession();
|
Session session = wsocket.getSession();
|
||||||
UpgradeRequest req = session.getUpgradeRequest();
|
UpgradeRequest req = session.getUpgradeRequest();
|
||||||
Assert.assertThat("Upgrade Request",req,notNullValue());
|
Assert.assertThat("Upgrade Request",req,notNullValue());
|
||||||
|
|
||||||
Map<String, List<String>> parameterMap = req.getParameterMap();
|
Map<String, List<String>> parameterMap = req.getParameterMap();
|
||||||
Assert.assertThat("Parameter Map",parameterMap,notNullValue());
|
Assert.assertThat("Parameter Map",parameterMap,notNullValue());
|
||||||
|
|
||||||
Assert.assertThat("Parameter[snack]",parameterMap.get("snack"),is(Arrays.asList(new String[]
|
Assert.assertThat("Parameter[snack]",parameterMap.get("snack"),is(Arrays.asList(new String[] { "cashews" })));
|
||||||
{ "cashews" })));
|
Assert.assertThat("Parameter[amount]",parameterMap.get("amount"),is(Arrays.asList(new String[] { "handful" })));
|
||||||
Assert.assertThat("Parameter[amount]",parameterMap.get("amount"),is(Arrays.asList(new String[]
|
Assert.assertThat("Parameter[brand]",parameterMap.get("brand"),is(Arrays.asList(new String[] { "off" })));
|
||||||
{ "handful" })));
|
|
||||||
Assert.assertThat("Parameter[brand]",parameterMap.get("brand"),is(Arrays.asList(new String[]
|
|
||||||
{ "off" })));
|
|
||||||
|
|
||||||
Assert.assertThat("Parameter[cost]",parameterMap.get("cost"),nullValue());
|
Assert.assertThat("Parameter[cost]",parameterMap.get("cost"),nullValue());
|
||||||
}
|
|
||||||
finally
|
|
||||||
{
|
|
||||||
fact.stop();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,21 +1,20 @@
|
||||||
org.eclipse.jetty.util.log.class=org.eclipse.jetty.util.log.StdErrLog
|
org.eclipse.jetty.util.log.class=org.eclipse.jetty.util.log.StdErrLog
|
||||||
org.eclipse.jetty.LEVEL=WARN
|
org.eclipse.jetty.LEVEL=WARN
|
||||||
# org.eclipse.jetty.LEVEL=DEBUG
|
# org.eclipse.jetty.LEVEL=DEBUG
|
||||||
# org.eclipse.jetty.io.LEVEL=DEBUG
|
# org.eclipse.jetty.io.LEVEL=INFO
|
||||||
# org.eclipse.jetty.io.ChannelEndPoint.LEVEL=DEBUG
|
# org.eclipse.jetty.client.LEVEL=DEBUG
|
||||||
# org.eclipse.jetty.io.SelectChannelEndPoint.LEVEL=DEBUG
|
# org.eclipse.jetty.util.LEVEL=INFO
|
||||||
# org.eclipse.jetty.io.IdleTimeout.LEVEL=DEBUG
|
|
||||||
# org.eclipse.jetty.io.FillInterest.LEVEL=DEBUG
|
|
||||||
# org.eclipse.jetty.io.AbstractConnection.LEVEL=DEBUG
|
|
||||||
# org.eclipse.jetty.websocket.LEVEL=WARN
|
# org.eclipse.jetty.websocket.LEVEL=WARN
|
||||||
# org.eclipse.jetty.websocket.LEVEL=DEBUG
|
# org.eclipse.jetty.websocket.LEVEL=DEBUG
|
||||||
# org.eclipse.jetty.websocket.client.LEVEL=DEBUG
|
# org.eclipse.jetty.websocket.client.LEVEL=DEBUG
|
||||||
|
# org.eclipse.jetty.websocket.client.ClientCloseTest.LEVEL=DEBUG
|
||||||
# org.eclipse.jetty.websocket.common.io.AbstractWebSocketConnection.LEVEL=DEBUG
|
# org.eclipse.jetty.websocket.common.io.AbstractWebSocketConnection.LEVEL=DEBUG
|
||||||
# org.eclipse.jetty.websocket.common.io.IOState.LEVEL=DEBUG
|
# org.eclipse.jetty.websocket.common.io.IOState.LEVEL=DEBUG
|
||||||
|
|
||||||
# org.eclipse.jetty.websocket.common.Generator.LEVEL=DEBUG
|
# org.eclipse.jetty.websocket.common.Generator.LEVEL=DEBUG
|
||||||
# org.eclipse.jetty.websocket.common.Parser.LEVEL=DEBUG
|
# org.eclipse.jetty.websocket.common.Parser.LEVEL=DEBUG
|
||||||
# org.eclipse.jetty.websocket.client.TrackingSocket.LEVEL=DEBUG
|
# org.eclipse.jetty.websocket.client.TrackingSocket.LEVEL=DEBUG
|
||||||
|
# org.eclipse.jetty.websocket.common.test.BlockheadServerConnection.LEVEL=DEBUG
|
||||||
|
|
||||||
### Hide the stacktraces during testing
|
### Hide the stacktraces during testing
|
||||||
org.eclipse.jetty.websocket.client.internal.io.UpgradeConnection.STACKS=false
|
org.eclipse.jetty.websocket.client.internal.io.UpgradeConnection.STACKS=false
|
||||||
|
|
|
@ -404,21 +404,42 @@ public class Parser
|
||||||
if (isRsv1InUse())
|
if (isRsv1InUse())
|
||||||
frame.setRsv1(true);
|
frame.setRsv1(true);
|
||||||
else
|
else
|
||||||
throw new ProtocolException("RSV1 not allowed to be set");
|
{
|
||||||
|
String err = "RSV1 not allowed to be set";
|
||||||
|
if(LOG.isDebugEnabled())
|
||||||
|
{
|
||||||
|
LOG.debug(err + ": Remaining buffer: {}", BufferUtil.toDetailString(buffer));
|
||||||
|
}
|
||||||
|
throw new ProtocolException(err);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if ((b & 0x20) != 0)
|
if ((b & 0x20) != 0)
|
||||||
{
|
{
|
||||||
if (isRsv2InUse())
|
if (isRsv2InUse())
|
||||||
frame.setRsv2(true);
|
frame.setRsv2(true);
|
||||||
else
|
else
|
||||||
throw new ProtocolException("RSV2 not allowed to be set");
|
{
|
||||||
|
String err = "RSV2 not allowed to be set";
|
||||||
|
if(LOG.isDebugEnabled())
|
||||||
|
{
|
||||||
|
LOG.debug(err + ": Remaining buffer: {}", BufferUtil.toDetailString(buffer));
|
||||||
|
}
|
||||||
|
throw new ProtocolException(err);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if ((b & 0x10) != 0)
|
if ((b & 0x10) != 0)
|
||||||
{
|
{
|
||||||
if (isRsv3InUse())
|
if (isRsv3InUse())
|
||||||
frame.setRsv3(true);
|
frame.setRsv3(true);
|
||||||
else
|
else
|
||||||
throw new ProtocolException("RSV3 not allowed to be set");
|
{
|
||||||
|
String err = "RSV3 not allowed to be set";
|
||||||
|
if(LOG.isDebugEnabled())
|
||||||
|
{
|
||||||
|
LOG.debug(err + ": Remaining buffer: {}", BufferUtil.toDetailString(buffer));
|
||||||
|
}
|
||||||
|
throw new ProtocolException(err);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,397 @@
|
||||||
|
//
|
||||||
|
// ========================================================================
|
||||||
|
// Copyright (c) 1995-2016 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.common;
|
||||||
|
|
||||||
|
import java.net.HttpCookie;
|
||||||
|
import java.net.URI;
|
||||||
|
import java.security.Principal;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.TreeMap;
|
||||||
|
|
||||||
|
import org.eclipse.jetty.websocket.api.UpgradeRequest;
|
||||||
|
import org.eclipse.jetty.websocket.api.extensions.ExtensionConfig;
|
||||||
|
import org.eclipse.jetty.websocket.api.util.QuoteUtil;
|
||||||
|
|
||||||
|
public class UpgradeRequestAdapter implements UpgradeRequest
|
||||||
|
{
|
||||||
|
private URI requestURI;
|
||||||
|
private List<String> subProtocols = new ArrayList<>(1);
|
||||||
|
private List<ExtensionConfig> extensions = new ArrayList<>(1);
|
||||||
|
private List<HttpCookie> cookies = new ArrayList<>(1);
|
||||||
|
private Map<String, List<String>> headers = new TreeMap<>(String.CASE_INSENSITIVE_ORDER);
|
||||||
|
private Map<String, List<String>> parameters = new HashMap<>(1);
|
||||||
|
private Object session;
|
||||||
|
private String httpVersion;
|
||||||
|
private String method;
|
||||||
|
private String host;
|
||||||
|
private boolean secure;
|
||||||
|
|
||||||
|
protected UpgradeRequestAdapter()
|
||||||
|
{
|
||||||
|
/* anonymous, no requestURI, upgrade request */
|
||||||
|
}
|
||||||
|
|
||||||
|
public UpgradeRequestAdapter(String requestURI)
|
||||||
|
{
|
||||||
|
this(URI.create(requestURI));
|
||||||
|
}
|
||||||
|
|
||||||
|
public UpgradeRequestAdapter(URI requestURI)
|
||||||
|
{
|
||||||
|
setRequestURI(requestURI);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void addExtensions(ExtensionConfig... configs)
|
||||||
|
{
|
||||||
|
Collections.addAll(extensions, configs);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void addExtensions(String... configs)
|
||||||
|
{
|
||||||
|
for (String config : configs)
|
||||||
|
{
|
||||||
|
extensions.add(ExtensionConfig.parse(config));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void clearHeaders()
|
||||||
|
{
|
||||||
|
headers.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<HttpCookie> getCookies()
|
||||||
|
{
|
||||||
|
return cookies;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<ExtensionConfig> getExtensions()
|
||||||
|
{
|
||||||
|
return extensions;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getHeader(String name)
|
||||||
|
{
|
||||||
|
List<String> values = headers.get(name);
|
||||||
|
// no value list
|
||||||
|
if (values == null)
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
int size = values.size();
|
||||||
|
// empty value list
|
||||||
|
if (size <= 0)
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
// simple return
|
||||||
|
if (size == 1)
|
||||||
|
{
|
||||||
|
return values.get(0);
|
||||||
|
}
|
||||||
|
// join it with commas
|
||||||
|
boolean needsDelim = false;
|
||||||
|
StringBuilder ret = new StringBuilder();
|
||||||
|
for (String value : values)
|
||||||
|
{
|
||||||
|
if (needsDelim)
|
||||||
|
{
|
||||||
|
ret.append(", ");
|
||||||
|
}
|
||||||
|
QuoteUtil.quoteIfNeeded(ret,value,QuoteUtil.ABNF_REQUIRED_QUOTING);
|
||||||
|
needsDelim = true;
|
||||||
|
}
|
||||||
|
return ret.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getHeaderInt(String name)
|
||||||
|
{
|
||||||
|
List<String> values = headers.get(name);
|
||||||
|
// no value list
|
||||||
|
if (values == null)
|
||||||
|
{
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
int size = values.size();
|
||||||
|
// empty value list
|
||||||
|
if (size <= 0)
|
||||||
|
{
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
// simple return
|
||||||
|
if (size == 1)
|
||||||
|
{
|
||||||
|
return Integer.parseInt(values.get(0));
|
||||||
|
}
|
||||||
|
throw new NumberFormatException("Cannot convert multi-value header into int");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Map<String, List<String>> getHeaders()
|
||||||
|
{
|
||||||
|
return headers;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<String> getHeaders(String name)
|
||||||
|
{
|
||||||
|
return headers.get(name);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getHost()
|
||||||
|
{
|
||||||
|
return host;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getHttpVersion()
|
||||||
|
{
|
||||||
|
return httpVersion;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getMethod()
|
||||||
|
{
|
||||||
|
return method;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getOrigin()
|
||||||
|
{
|
||||||
|
return getHeader("Origin");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a map of the query parameters of the request.
|
||||||
|
*
|
||||||
|
* @return a unmodifiable map of query parameters of the request.
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public Map<String, List<String>> getParameterMap()
|
||||||
|
{
|
||||||
|
return Collections.unmodifiableMap(parameters);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getProtocolVersion()
|
||||||
|
{
|
||||||
|
String version = getHeader("Sec-WebSocket-Version");
|
||||||
|
if (version == null)
|
||||||
|
{
|
||||||
|
return "13"; // Default
|
||||||
|
}
|
||||||
|
return version;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getQueryString()
|
||||||
|
{
|
||||||
|
return requestURI.getQuery();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public URI getRequestURI()
|
||||||
|
{
|
||||||
|
return requestURI;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Access the Servlet HTTP Session (if present)
|
||||||
|
* <p>
|
||||||
|
* Note: Never present on a Client UpgradeRequest.
|
||||||
|
*
|
||||||
|
* @return the Servlet HTTPSession on server side UpgradeRequests
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public Object getSession()
|
||||||
|
{
|
||||||
|
return session;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<String> getSubProtocols()
|
||||||
|
{
|
||||||
|
return subProtocols;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the User Principal for this request.
|
||||||
|
* <p>
|
||||||
|
* Only applicable when using UpgradeRequest from server side.
|
||||||
|
*
|
||||||
|
* @return the user principal
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public Principal getUserPrincipal()
|
||||||
|
{
|
||||||
|
// Server side should override to implement
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean hasSubProtocol(String test)
|
||||||
|
{
|
||||||
|
for (String protocol : subProtocols)
|
||||||
|
{
|
||||||
|
if (protocol.equalsIgnoreCase(test))
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isOrigin(String test)
|
||||||
|
{
|
||||||
|
return test.equalsIgnoreCase(getOrigin());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isSecure()
|
||||||
|
{
|
||||||
|
return secure;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setCookies(List<HttpCookie> cookies)
|
||||||
|
{
|
||||||
|
this.cookies.clear();
|
||||||
|
if (cookies != null && !cookies.isEmpty())
|
||||||
|
{
|
||||||
|
this.cookies.addAll(cookies);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setExtensions(List<ExtensionConfig> configs)
|
||||||
|
{
|
||||||
|
this.extensions.clear();
|
||||||
|
if (configs != null)
|
||||||
|
{
|
||||||
|
this.extensions.addAll(configs);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setHeader(String name, List<String> values)
|
||||||
|
{
|
||||||
|
headers.put(name,values);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setHeader(String name, String value)
|
||||||
|
{
|
||||||
|
List<String> values = new ArrayList<>();
|
||||||
|
values.add(value);
|
||||||
|
setHeader(name,values);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setHeaders(Map<String, List<String>> headers)
|
||||||
|
{
|
||||||
|
clearHeaders();
|
||||||
|
|
||||||
|
for (Map.Entry<String, List<String>> entry : headers.entrySet())
|
||||||
|
{
|
||||||
|
String name = entry.getKey();
|
||||||
|
List<String> values = entry.getValue();
|
||||||
|
setHeader(name,values);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setHttpVersion(String httpVersion)
|
||||||
|
{
|
||||||
|
this.httpVersion = httpVersion;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setMethod(String method)
|
||||||
|
{
|
||||||
|
this.method = method;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void setParameterMap(Map<String, List<String>> parameters)
|
||||||
|
{
|
||||||
|
this.parameters.clear();
|
||||||
|
this.parameters.putAll(parameters);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setRequestURI(URI uri)
|
||||||
|
{
|
||||||
|
this.requestURI = uri;
|
||||||
|
String scheme = uri.getScheme();
|
||||||
|
if ("ws".equalsIgnoreCase(scheme))
|
||||||
|
{
|
||||||
|
secure = false;
|
||||||
|
}
|
||||||
|
else if ("wss".equalsIgnoreCase(scheme))
|
||||||
|
{
|
||||||
|
secure = true;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
throw new IllegalArgumentException("URI scheme must be 'ws' or 'wss'");
|
||||||
|
}
|
||||||
|
this.host = this.requestURI.getHost();
|
||||||
|
this.parameters.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setSession(Object session)
|
||||||
|
{
|
||||||
|
this.session = session;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setSubProtocols(List<String> subProtocols)
|
||||||
|
{
|
||||||
|
this.subProtocols.clear();
|
||||||
|
if (subProtocols != null)
|
||||||
|
{
|
||||||
|
this.subProtocols.addAll(subProtocols);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set Sub Protocol request list.
|
||||||
|
*
|
||||||
|
* @param protocols
|
||||||
|
* the sub protocols desired
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public void setSubProtocols(String... protocols)
|
||||||
|
{
|
||||||
|
subProtocols.clear();
|
||||||
|
Collections.addAll(subProtocols, protocols);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,227 @@
|
||||||
|
//
|
||||||
|
// ========================================================================
|
||||||
|
// Copyright (c) 1995-2016 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.common;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Set;
|
||||||
|
import java.util.TreeMap;
|
||||||
|
|
||||||
|
import org.eclipse.jetty.websocket.api.UpgradeResponse;
|
||||||
|
import org.eclipse.jetty.websocket.api.WebSocketConstants;
|
||||||
|
import org.eclipse.jetty.websocket.api.extensions.ExtensionConfig;
|
||||||
|
import org.eclipse.jetty.websocket.api.util.QuoteUtil;
|
||||||
|
|
||||||
|
public class UpgradeResponseAdapter implements UpgradeResponse
|
||||||
|
{
|
||||||
|
public static final String SEC_WEBSOCKET_PROTOCOL = WebSocketConstants.SEC_WEBSOCKET_PROTOCOL;
|
||||||
|
private int statusCode;
|
||||||
|
private String statusReason;
|
||||||
|
private Map<String, List<String>> headers = new TreeMap<>(String.CASE_INSENSITIVE_ORDER);
|
||||||
|
private List<ExtensionConfig> extensions = new ArrayList<>();
|
||||||
|
private boolean success = false;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void addHeader(String name, String value)
|
||||||
|
{
|
||||||
|
String key = name;
|
||||||
|
List<String> values = headers.get(key);
|
||||||
|
if (values == null)
|
||||||
|
{
|
||||||
|
values = new ArrayList<>();
|
||||||
|
}
|
||||||
|
values.add(value);
|
||||||
|
headers.put(key,values);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the accepted WebSocket protocol.
|
||||||
|
*
|
||||||
|
* @return the accepted WebSocket protocol.
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public String getAcceptedSubProtocol()
|
||||||
|
{
|
||||||
|
return getHeader(SEC_WEBSOCKET_PROTOCOL);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the list of extensions that should be used for the websocket.
|
||||||
|
*
|
||||||
|
* @return the list of negotiated extensions to use.
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public List<ExtensionConfig> getExtensions()
|
||||||
|
{
|
||||||
|
return extensions;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getHeader(String name)
|
||||||
|
{
|
||||||
|
List<String> values = getHeaders(name);
|
||||||
|
// no value list
|
||||||
|
if (values == null)
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
int size = values.size();
|
||||||
|
// empty value list
|
||||||
|
if (size <= 0)
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
// simple return
|
||||||
|
if (size == 1)
|
||||||
|
{
|
||||||
|
return values.get(0);
|
||||||
|
}
|
||||||
|
// join it with commas
|
||||||
|
boolean needsDelim = false;
|
||||||
|
StringBuilder ret = new StringBuilder();
|
||||||
|
for (String value : values)
|
||||||
|
{
|
||||||
|
if (needsDelim)
|
||||||
|
{
|
||||||
|
ret.append(", ");
|
||||||
|
}
|
||||||
|
QuoteUtil.quoteIfNeeded(ret,value,QuoteUtil.ABNF_REQUIRED_QUOTING);
|
||||||
|
needsDelim = true;
|
||||||
|
}
|
||||||
|
return ret.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Set<String> getHeaderNames()
|
||||||
|
{
|
||||||
|
return headers.keySet();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Map<String, List<String>> getHeaders()
|
||||||
|
{
|
||||||
|
return headers;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<String> getHeaders(String name)
|
||||||
|
{
|
||||||
|
return headers.get(name);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getStatusCode()
|
||||||
|
{
|
||||||
|
return statusCode;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getStatusReason()
|
||||||
|
{
|
||||||
|
return statusReason;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isSuccess()
|
||||||
|
{
|
||||||
|
return success;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Issue a forbidden upgrade response.
|
||||||
|
* <p>
|
||||||
|
* This means that the websocket endpoint was valid, but the conditions to use a WebSocket resulted in a forbidden
|
||||||
|
* access.
|
||||||
|
* <p>
|
||||||
|
* Use this when the origin or authentication is invalid.
|
||||||
|
*
|
||||||
|
* @param message
|
||||||
|
* the short 1 line detail message about the forbidden response
|
||||||
|
* @throws IOException
|
||||||
|
* if unable to send the forbidden
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public void sendForbidden(String message) throws IOException
|
||||||
|
{
|
||||||
|
throw new UnsupportedOperationException("Not supported");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set the accepted WebSocket Protocol.
|
||||||
|
*
|
||||||
|
* @param protocol
|
||||||
|
* the protocol to list as accepted
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public void setAcceptedSubProtocol(String protocol)
|
||||||
|
{
|
||||||
|
setHeader(SEC_WEBSOCKET_PROTOCOL,protocol);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set the list of extensions that are approved for use with this websocket.
|
||||||
|
* <p>
|
||||||
|
* Notes:
|
||||||
|
* <ul>
|
||||||
|
* <li>Per the spec you cannot add extensions that have not been seen in the {@link UpgradeRequest}, just remove entries you don't want to use</li>
|
||||||
|
* <li>If this is unused, or a null is passed, then the list negotiation will follow default behavior and use the complete list of extensions that are
|
||||||
|
* available in this WebSocket server implementation.</li>
|
||||||
|
* </ul>
|
||||||
|
*
|
||||||
|
* @param extensions
|
||||||
|
* the list of extensions to use.
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public void setExtensions(List<ExtensionConfig> extensions)
|
||||||
|
{
|
||||||
|
this.extensions.clear();
|
||||||
|
if (extensions != null)
|
||||||
|
{
|
||||||
|
this.extensions.addAll(extensions);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setHeader(String name, String value)
|
||||||
|
{
|
||||||
|
List<String> values = new ArrayList<>();
|
||||||
|
values.add(value);
|
||||||
|
headers.put(name,values);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setStatusCode(int statusCode)
|
||||||
|
{
|
||||||
|
this.statusCode = statusCode;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setStatusReason(String statusReason)
|
||||||
|
{
|
||||||
|
this.statusReason = statusReason;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setSuccess(boolean success)
|
||||||
|
{
|
||||||
|
this.success = success;
|
||||||
|
}
|
||||||
|
}
|
|
@ -27,6 +27,7 @@ import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
import java.util.ServiceLoader;
|
import java.util.ServiceLoader;
|
||||||
|
import java.util.concurrent.CompletableFuture;
|
||||||
import java.util.concurrent.Executor;
|
import java.util.concurrent.Executor;
|
||||||
|
|
||||||
import org.eclipse.jetty.io.ByteBufferPool;
|
import org.eclipse.jetty.io.ByteBufferPool;
|
||||||
|
@ -70,6 +71,7 @@ public class WebSocketSession extends ContainerLifeCycle implements Session, Rem
|
||||||
private final LogicalConnection connection;
|
private final LogicalConnection connection;
|
||||||
private final EventDriver websocket;
|
private final EventDriver websocket;
|
||||||
private final Executor executor;
|
private final Executor executor;
|
||||||
|
private final WebSocketPolicy policy;
|
||||||
private ClassLoader classLoader;
|
private ClassLoader classLoader;
|
||||||
private ExtensionFactory extensionFactory;
|
private ExtensionFactory extensionFactory;
|
||||||
private RemoteEndpointFactory remoteEndpointFactory;
|
private RemoteEndpointFactory remoteEndpointFactory;
|
||||||
|
@ -78,9 +80,9 @@ public class WebSocketSession extends ContainerLifeCycle implements Session, Rem
|
||||||
private RemoteEndpoint remote;
|
private RemoteEndpoint remote;
|
||||||
private IncomingFrames incomingHandler;
|
private IncomingFrames incomingHandler;
|
||||||
private OutgoingFrames outgoingHandler;
|
private OutgoingFrames outgoingHandler;
|
||||||
private WebSocketPolicy policy;
|
|
||||||
private UpgradeRequest upgradeRequest;
|
private UpgradeRequest upgradeRequest;
|
||||||
private UpgradeResponse upgradeResponse;
|
private UpgradeResponse upgradeResponse;
|
||||||
|
private CompletableFuture<Session> openFuture;
|
||||||
|
|
||||||
public WebSocketSession(WebSocketContainerScope containerScope, URI requestURI, EventDriver websocket, LogicalConnection connection)
|
public WebSocketSession(WebSocketContainerScope containerScope, URI requestURI, EventDriver websocket, LogicalConnection connection)
|
||||||
{
|
{
|
||||||
|
@ -509,6 +511,11 @@ public class WebSocketSession extends ContainerLifeCycle implements Session, Rem
|
||||||
{
|
{
|
||||||
LOG.debug("open -> {}",dump());
|
LOG.debug("open -> {}",dump());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if(openFuture != null)
|
||||||
|
{
|
||||||
|
openFuture.complete(this);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
catch (CloseException ce)
|
catch (CloseException ce)
|
||||||
{
|
{
|
||||||
|
@ -534,6 +541,11 @@ public class WebSocketSession extends ContainerLifeCycle implements Session, Rem
|
||||||
this.extensionFactory = extensionFactory;
|
this.extensionFactory = extensionFactory;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void setFuture(CompletableFuture<Session> fut)
|
||||||
|
{
|
||||||
|
this.openFuture = fut;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Set the timeout in milliseconds
|
* Set the timeout in milliseconds
|
||||||
*/
|
*/
|
||||||
|
@ -548,9 +560,10 @@ public class WebSocketSession extends ContainerLifeCycle implements Session, Rem
|
||||||
this.outgoingHandler = outgoing;
|
this.outgoingHandler = outgoing;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Deprecated
|
||||||
public void setPolicy(WebSocketPolicy policy)
|
public void setPolicy(WebSocketPolicy policy)
|
||||||
{
|
{
|
||||||
this.policy = policy;
|
// do nothing
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setUpgradeRequest(UpgradeRequest request)
|
public void setUpgradeRequest(UpgradeRequest request)
|
||||||
|
|
|
@ -102,7 +102,7 @@ public class JettyListenerEventDriver extends AbstractEventDriver
|
||||||
public void onConnect()
|
public void onConnect()
|
||||||
{
|
{
|
||||||
if (LOG.isDebugEnabled())
|
if (LOG.isDebugEnabled())
|
||||||
LOG.debug("onConnect()");
|
LOG.debug("onConnect({})", session);
|
||||||
listener.onWebSocketConnect(session);
|
listener.onWebSocketConnect(session);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -75,10 +75,16 @@ public abstract class AbstractExtension extends AbstractLifeCycle implements Dum
|
||||||
out.append(bean.toString());
|
out.append(bean.toString());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Deprecated
|
||||||
public void init(WebSocketContainerScope container)
|
public void init(WebSocketContainerScope container)
|
||||||
{
|
{
|
||||||
this.policy = container.getPolicy();
|
init(container.getPolicy(),container.getBufferPool());
|
||||||
this.bufferPool = container.getBufferPool();
|
}
|
||||||
|
|
||||||
|
public void init(WebSocketPolicy policy, ByteBufferPool bufferPool)
|
||||||
|
{
|
||||||
|
this.policy = policy;
|
||||||
|
this.bufferPool = bufferPool;
|
||||||
}
|
}
|
||||||
|
|
||||||
public ByteBufferPool getBufferPool()
|
public ByteBufferPool getBufferPool()
|
||||||
|
|
|
@ -811,6 +811,11 @@ public abstract class AbstractWebSocketConnection extends AbstractConnection imp
|
||||||
@Override
|
@Override
|
||||||
public void onUpgradeTo(ByteBuffer prefilled)
|
public void onUpgradeTo(ByteBuffer prefilled)
|
||||||
{
|
{
|
||||||
|
if(LOG.isDebugEnabled())
|
||||||
|
{
|
||||||
|
LOG.debug("onUpgradeTo({})", BufferUtil.toDetailString(prefilled));
|
||||||
|
}
|
||||||
|
|
||||||
setInitialBuffer(prefilled);
|
setInitialBuffer(prefilled);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -422,7 +422,7 @@ public class IOState
|
||||||
public void onOpened()
|
public void onOpened()
|
||||||
{
|
{
|
||||||
if(LOG.isDebugEnabled())
|
if(LOG.isDebugEnabled())
|
||||||
LOG.debug(" onOpened()");
|
LOG.debug("onOpened()");
|
||||||
|
|
||||||
ConnectionState event = null;
|
ConnectionState event = null;
|
||||||
synchronized (this)
|
synchronized (this)
|
||||||
|
|
|
@ -40,6 +40,7 @@ public class SimpleContainerScope extends ContainerLifeCycle implements WebSocke
|
||||||
public SimpleContainerScope(WebSocketPolicy policy)
|
public SimpleContainerScope(WebSocketPolicy policy)
|
||||||
{
|
{
|
||||||
this(policy,new MappedByteBufferPool(),new DecoratedObjectFactory());
|
this(policy,new MappedByteBufferPool(),new DecoratedObjectFactory());
|
||||||
|
this.sslContextFactory = new SslContextFactory();
|
||||||
}
|
}
|
||||||
|
|
||||||
public SimpleContainerScope(WebSocketPolicy policy, ByteBufferPool bufferPool)
|
public SimpleContainerScope(WebSocketPolicy policy, ByteBufferPool bufferPool)
|
||||||
|
@ -106,14 +107,16 @@ public class SimpleContainerScope extends ContainerLifeCycle implements WebSocke
|
||||||
{
|
{
|
||||||
this.sslContextFactory = sslContextFactory;
|
this.sslContextFactory = sslContextFactory;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onSessionOpened(WebSocketSession session)
|
public void onSessionOpened(WebSocketSession session)
|
||||||
{
|
{
|
||||||
|
/* do nothing */
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onSessionClosed(WebSocketSession session)
|
public void onSessionClosed(WebSocketSession session)
|
||||||
{
|
{
|
||||||
|
/* do nothing */
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -542,6 +542,7 @@ public class BlockheadServerConnection implements IncomingFrames, OutgoingFrames
|
||||||
StringBuilder resp = new StringBuilder();
|
StringBuilder resp = new StringBuilder();
|
||||||
resp.append("HTTP/1.1 101 Upgrade\r\n");
|
resp.append("HTTP/1.1 101 Upgrade\r\n");
|
||||||
resp.append("Connection: upgrade\r\n");
|
resp.append("Connection: upgrade\r\n");
|
||||||
|
resp.append("Content-Length: 0\r\n");
|
||||||
resp.append("Sec-WebSocket-Accept: ");
|
resp.append("Sec-WebSocket-Accept: ");
|
||||||
resp.append(AcceptHash.hashKey(key)).append("\r\n");
|
resp.append(AcceptHash.hashKey(key)).append("\r\n");
|
||||||
if (extensionStack.hasNegotiatedExtensions())
|
if (extensionStack.hasNegotiatedExtensions())
|
||||||
|
@ -571,7 +572,7 @@ public class BlockheadServerConnection implements IncomingFrames, OutgoingFrames
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
resp.append("\r\n");
|
resp.append("\r\n");
|
||||||
|
|
||||||
// Write Response
|
// Write Response
|
||||||
LOG.debug("Response: {}",resp.toString());
|
LOG.debug("Response: {}",resp.toString());
|
||||||
write(resp.toString().getBytes());
|
write(resp.toString().getBytes());
|
||||||
|
@ -611,4 +612,4 @@ public class BlockheadServerConnection implements IncomingFrames, OutgoingFrames
|
||||||
getOutputStream().write(arr);
|
getOutputStream().write(arr);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -576,7 +576,6 @@ public class WebSocketServerFactory extends ContainerLifeCycle implements WebSoc
|
||||||
|
|
||||||
// Setup Session
|
// Setup Session
|
||||||
WebSocketSession session = createSession(request.getRequestURI(), driver, wsConnection);
|
WebSocketSession session = createSession(request.getRequestURI(), driver, wsConnection);
|
||||||
session.setPolicy(driver.getPolicy());
|
|
||||||
session.setUpgradeRequest(request);
|
session.setUpgradeRequest(request);
|
||||||
// set true negotiated extension list back to response
|
// set true negotiated extension list back to response
|
||||||
response.setExtensions(extensionStack.getNegotiatedExtensions());
|
response.setExtensions(extensionStack.getNegotiatedExtensions());
|
||||||
|
|
|
@ -28,6 +28,7 @@ import java.util.concurrent.TimeUnit;
|
||||||
|
|
||||||
import org.eclipse.jetty.websocket.api.UpgradeRequest;
|
import org.eclipse.jetty.websocket.api.UpgradeRequest;
|
||||||
import org.eclipse.jetty.websocket.api.UpgradeResponse;
|
import org.eclipse.jetty.websocket.api.UpgradeResponse;
|
||||||
|
import org.eclipse.jetty.websocket.common.UpgradeRequestAdapter;
|
||||||
import org.eclipse.jetty.websocket.common.test.BlockheadClient;
|
import org.eclipse.jetty.websocket.common.test.BlockheadClient;
|
||||||
import org.eclipse.jetty.websocket.server.helper.EchoSocket;
|
import org.eclipse.jetty.websocket.server.helper.EchoSocket;
|
||||||
import org.eclipse.jetty.websocket.servlet.ServletUpgradeRequest;
|
import org.eclipse.jetty.websocket.servlet.ServletUpgradeRequest;
|
||||||
|
|
|
@ -19,6 +19,8 @@
|
||||||
package org.eclipse.jetty.websocket.server;
|
package org.eclipse.jetty.websocket.server;
|
||||||
|
|
||||||
import static org.hamcrest.Matchers.is;
|
import static org.hamcrest.Matchers.is;
|
||||||
|
import static org.hamcrest.Matchers.notNullValue;
|
||||||
|
import static org.junit.Assert.assertThat;
|
||||||
|
|
||||||
import java.net.URI;
|
import java.net.URI;
|
||||||
import java.util.concurrent.Future;
|
import java.util.concurrent.Future;
|
||||||
|
|
|
@ -41,8 +41,6 @@ import org.eclipse.jetty.webapp.WebAppContext;
|
||||||
import org.eclipse.jetty.websocket.common.WebSocketFrame;
|
import org.eclipse.jetty.websocket.common.WebSocketFrame;
|
||||||
import org.eclipse.jetty.websocket.common.frames.TextFrame;
|
import org.eclipse.jetty.websocket.common.frames.TextFrame;
|
||||||
import org.eclipse.jetty.websocket.common.test.BlockheadClient;
|
import org.eclipse.jetty.websocket.common.test.BlockheadClient;
|
||||||
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.WebSocketCreator;
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
import org.junit.runner.RunWith;
|
import org.junit.runner.RunWith;
|
||||||
|
@ -59,212 +57,158 @@ public class WebSocketUpgradeFilterTest
|
||||||
@Parameterized.Parameters(name = "{0}")
|
@Parameterized.Parameters(name = "{0}")
|
||||||
public static List<Object[]> data()
|
public static List<Object[]> data()
|
||||||
{
|
{
|
||||||
/**
|
final WebSocketCreator infoCreator = (req, resp) -> new InfoSocket();
|
||||||
* Case A:
|
|
||||||
* 1. embedded-jetty WSUF.configureContext() / app-ws configured at ...
|
|
||||||
* a. during server construction / before server.start (might not be possible with current impl, native SCI not run (yet))
|
|
||||||
* might require NativeSCI.getDefaultFrom() first
|
|
||||||
* b. during server construction / after server.start
|
|
||||||
* c. during server start / via CustomServlet.init()
|
|
||||||
* 2. embedded-jetty WSUF addFilter / app-ws configured at server construction (before server.start)
|
|
||||||
* Case B:
|
|
||||||
* 1. web.xml WSUF / app-ws configured in CustomServlet.init() load-on-start
|
|
||||||
* Case C:
|
|
||||||
* 1. embedded-jetty WSUF.configureContext() / app-ws configured via ServletContextListener.contextInitialized
|
|
||||||
* 2. embedded-jetty WSUF addFilter / app-ws configured via ServletContextListener.contextInitialized
|
|
||||||
* Case D:
|
|
||||||
* 1. web.xml WSUF / app-ws configured via ServletContextListener.contextInitialized
|
|
||||||
*
|
|
||||||
* Every "app-ws configured" means it should access/set ws policy and add ws mappings
|
|
||||||
*/
|
|
||||||
|
|
||||||
final WebSocketCreator infoCreator = new WebSocketCreator()
|
|
||||||
{
|
|
||||||
@Override
|
|
||||||
public Object createWebSocket(ServletUpgradeRequest req, ServletUpgradeResponse resp)
|
|
||||||
{
|
|
||||||
return new InfoSocket();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
List<Object[]> cases = new ArrayList<>();
|
List<Object[]> cases = new ArrayList<>();
|
||||||
|
|
||||||
// Embedded WSUF.configureContext(), directly app-ws configuration
|
// Embedded WSUF.configureContext(), directly app-ws configuration
|
||||||
|
|
||||||
cases.add(new Object[]{"wsuf.configureContext/Direct configure", new ServerProvider()
|
cases.add(new Object[]{"wsuf.configureContext/Direct configure", (ServerProvider) () ->
|
||||||
{
|
{
|
||||||
@Override
|
Server server1 = new Server();
|
||||||
public Server newServer() throws Exception
|
ServerConnector connector = new ServerConnector(server1);
|
||||||
{
|
connector.setPort(0);
|
||||||
Server server = new Server();
|
server1.addConnector(connector);
|
||||||
ServerConnector connector = new ServerConnector(server);
|
|
||||||
connector.setPort(0);
|
ServletContextHandler context = new ServletContextHandler();
|
||||||
server.addConnector(connector);
|
context.setContextPath("/");
|
||||||
|
server1.setHandler(context);
|
||||||
ServletContextHandler context = new ServletContextHandler();
|
|
||||||
context.setContextPath("/");
|
WebSocketUpgradeFilter wsuf = WebSocketUpgradeFilter.configureContext(context);
|
||||||
server.setHandler(context);
|
|
||||||
|
// direct configuration via WSUF
|
||||||
WebSocketUpgradeFilter wsuf = WebSocketUpgradeFilter.configureContext(context);
|
wsuf.getFactory().getPolicy().setMaxTextMessageSize(10 * 1024 * 1024);
|
||||||
|
wsuf.addMapping(new ServletPathSpec("/info/*"), infoCreator);
|
||||||
// direct configuration via WSUF
|
|
||||||
wsuf.getFactory().getPolicy().setMaxTextMessageSize(10 * 1024 * 1024);
|
server1.start();
|
||||||
wsuf.addMapping(new ServletPathSpec("/info/*"), infoCreator);
|
return server1;
|
||||||
|
|
||||||
server.start();
|
|
||||||
return server;
|
|
||||||
}
|
|
||||||
}});
|
}});
|
||||||
|
|
||||||
// Embedded WSUF.configureContext(), apply app-ws configuration via attribute
|
// Embedded WSUF.configureContext(), apply app-ws configuration via attribute
|
||||||
|
|
||||||
cases.add(new Object[]{"wsuf.configureContext/Attribute based configure", new ServerProvider()
|
cases.add(new Object[]{"wsuf.configureContext/Attribute based configure", (ServerProvider) () ->
|
||||||
{
|
{
|
||||||
@Override
|
Server server12 = new Server();
|
||||||
public Server newServer() throws Exception
|
ServerConnector connector = new ServerConnector(server12);
|
||||||
{
|
connector.setPort(0);
|
||||||
Server server = new Server();
|
server12.addConnector(connector);
|
||||||
ServerConnector connector = new ServerConnector(server);
|
|
||||||
connector.setPort(0);
|
ServletContextHandler context = new ServletContextHandler();
|
||||||
server.addConnector(connector);
|
context.setContextPath("/");
|
||||||
|
server12.setHandler(context);
|
||||||
ServletContextHandler context = new ServletContextHandler();
|
|
||||||
context.setContextPath("/");
|
WebSocketUpgradeFilter.configureContext(context);
|
||||||
server.setHandler(context);
|
|
||||||
|
// configuration via attribute
|
||||||
WebSocketUpgradeFilter.configureContext(context);
|
NativeWebSocketConfiguration configuration = (NativeWebSocketConfiguration) context.getServletContext().getAttribute(NativeWebSocketConfiguration.class.getName());
|
||||||
|
assertThat("NativeWebSocketConfiguration", configuration, notNullValue());
|
||||||
// configuration via attribute
|
configuration.getFactory().getPolicy().setMaxTextMessageSize(10 * 1024 * 1024);
|
||||||
NativeWebSocketConfiguration configuration = (NativeWebSocketConfiguration) context.getServletContext().getAttribute(NativeWebSocketConfiguration.class.getName());
|
configuration.addMapping(new ServletPathSpec("/info/*"), infoCreator);
|
||||||
assertThat("NativeWebSocketConfiguration", configuration, notNullValue());
|
|
||||||
configuration.getFactory().getPolicy().setMaxTextMessageSize(10 * 1024 * 1024);
|
server12.start();
|
||||||
configuration.addMapping(new ServletPathSpec("/info/*"), infoCreator);
|
|
||||||
|
return server12;
|
||||||
server.start();
|
|
||||||
|
|
||||||
return server;
|
|
||||||
}
|
|
||||||
}});
|
}});
|
||||||
|
|
||||||
// Embedded WSUF, added as filter, apply app-ws configuration via attribute
|
// Embedded WSUF, added as filter, apply app-ws configuration via attribute
|
||||||
|
|
||||||
cases.add(new Object[]{"wsuf/addFilter/Attribute based configure", new ServerProvider()
|
cases.add(new Object[]{"wsuf/addFilter/Attribute based configure", (ServerProvider) () ->
|
||||||
{
|
{
|
||||||
@Override
|
Server server13 = new Server();
|
||||||
public Server newServer() throws Exception
|
ServerConnector connector = new ServerConnector(server13);
|
||||||
{
|
connector.setPort(0);
|
||||||
Server server = new Server();
|
server13.addConnector(connector);
|
||||||
ServerConnector connector = new ServerConnector(server);
|
|
||||||
connector.setPort(0);
|
ServletContextHandler context = new ServletContextHandler();
|
||||||
server.addConnector(connector);
|
context.setContextPath("/");
|
||||||
|
server13.setHandler(context);
|
||||||
ServletContextHandler context = new ServletContextHandler();
|
context.addFilter(WebSocketUpgradeFilter.class, "/*", EnumSet.of(DispatcherType.REQUEST));
|
||||||
context.setContextPath("/");
|
|
||||||
server.setHandler(context);
|
NativeWebSocketConfiguration configuration = new NativeWebSocketConfiguration(context.getServletContext());
|
||||||
context.addFilter(WebSocketUpgradeFilter.class, "/*", EnumSet.of(DispatcherType.REQUEST));
|
configuration.getFactory().getPolicy().setMaxTextMessageSize(10 * 1024 * 1024);
|
||||||
|
configuration.addMapping(new ServletPathSpec("/info/*"), infoCreator);
|
||||||
NativeWebSocketConfiguration configuration = new NativeWebSocketConfiguration(context.getServletContext());
|
context.getServletContext().setAttribute(NativeWebSocketConfiguration.class.getName(), configuration);
|
||||||
configuration.getFactory().getPolicy().setMaxTextMessageSize(10 * 1024 * 1024);
|
|
||||||
configuration.addMapping(new ServletPathSpec("/info/*"), infoCreator);
|
server13.start();
|
||||||
context.getServletContext().setAttribute(NativeWebSocketConfiguration.class.getName(), configuration);
|
|
||||||
|
return server13;
|
||||||
server.start();
|
|
||||||
|
|
||||||
return server;
|
|
||||||
}
|
|
||||||
}});
|
}});
|
||||||
|
|
||||||
// Embedded WSUF, added as filter, apply app-ws configuration via ServletContextListener
|
// Embedded WSUF, added as filter, apply app-ws configuration via ServletContextListener
|
||||||
|
|
||||||
cases.add(new Object[]{"wsuf.configureContext/ServletContextListener configure", new ServerProvider()
|
cases.add(new Object[]{"wsuf.configureContext/ServletContextListener configure", (ServerProvider) () ->
|
||||||
{
|
{
|
||||||
@Override
|
Server server14 = new Server();
|
||||||
public Server newServer() throws Exception
|
ServerConnector connector = new ServerConnector(server14);
|
||||||
{
|
connector.setPort(0);
|
||||||
Server server = new Server();
|
server14.addConnector(connector);
|
||||||
ServerConnector connector = new ServerConnector(server);
|
|
||||||
connector.setPort(0);
|
ServletContextHandler context = new ServletContextHandler();
|
||||||
server.addConnector(connector);
|
context.setContextPath("/");
|
||||||
|
server14.setHandler(context);
|
||||||
ServletContextHandler context = new ServletContextHandler();
|
context.addFilter(WebSocketUpgradeFilter.class, "/*", EnumSet.of(DispatcherType.REQUEST));
|
||||||
context.setContextPath("/");
|
context.addEventListener(new InfoContextListener());
|
||||||
server.setHandler(context);
|
|
||||||
context.addFilter(WebSocketUpgradeFilter.class, "/*", EnumSet.of(DispatcherType.REQUEST));
|
server14.start();
|
||||||
context.addEventListener(new InfoContextListener());
|
|
||||||
|
return server14;
|
||||||
server.start();
|
|
||||||
|
|
||||||
return server;
|
|
||||||
}
|
|
||||||
}});
|
}});
|
||||||
|
|
||||||
// WSUF from web.xml, SCI active, apply app-ws configuration via ServletContextListener
|
// WSUF from web.xml, SCI active, apply app-ws configuration via ServletContextListener
|
||||||
|
|
||||||
cases.add(new Object[]{"wsuf/WebAppContext/web.xml/ServletContextListener", new ServerProvider()
|
cases.add(new Object[]{"wsuf/WebAppContext/web.xml/ServletContextListener", (ServerProvider) () ->
|
||||||
{
|
{
|
||||||
@Override
|
File testDir = MavenTestingUtils.getTargetTestingDir("WSUF-webxml");
|
||||||
public Server newServer() throws Exception
|
|
||||||
{
|
WSServer server15 = new WSServer(testDir, "/");
|
||||||
File testDir = MavenTestingUtils.getTargetTestingDir("WSUF-webxml");
|
|
||||||
|
server15.copyWebInf("wsuf-config-via-listener.xml");
|
||||||
WSServer server = new WSServer(testDir, "/");
|
server15.copyClass(InfoSocket.class);
|
||||||
|
server15.copyClass(InfoContextAttributeListener.class);
|
||||||
server.copyWebInf("wsuf-config-via-listener.xml");
|
server15.start();
|
||||||
server.copyClass(InfoSocket.class);
|
|
||||||
server.copyClass(InfoContextAttributeListener.class);
|
WebAppContext webapp = server15.createWebAppContext();
|
||||||
server.start();
|
server15.deployWebapp(webapp);
|
||||||
|
|
||||||
WebAppContext webapp = server.createWebAppContext();
|
return server15.getServer();
|
||||||
server.deployWebapp(webapp);
|
|
||||||
|
|
||||||
return server.getServer();
|
|
||||||
}
|
|
||||||
}});
|
}});
|
||||||
|
|
||||||
// WSUF from web.xml, SCI active, apply app-ws configuration via Servlet.init
|
// WSUF from web.xml, SCI active, apply app-ws configuration via Servlet.init
|
||||||
|
|
||||||
cases.add(new Object[]{"wsuf/WebAppContext/web.xml/Servlet.init", new ServerProvider()
|
cases.add(new Object[]{"wsuf/WebAppContext/web.xml/Servlet.init", (ServerProvider) () ->
|
||||||
{
|
{
|
||||||
@Override
|
File testDir = MavenTestingUtils.getTargetTestingDir("WSUF-webxml");
|
||||||
public Server newServer() throws Exception
|
|
||||||
{
|
WSServer server16 = new WSServer(testDir, "/");
|
||||||
File testDir = MavenTestingUtils.getTargetTestingDir("WSUF-webxml");
|
|
||||||
|
server16.copyWebInf("wsuf-config-via-servlet-init.xml");
|
||||||
WSServer server = new WSServer(testDir, "/");
|
server16.copyClass(InfoSocket.class);
|
||||||
|
server16.copyClass(InfoServlet.class);
|
||||||
server.copyWebInf("wsuf-config-via-servlet-init.xml");
|
server16.start();
|
||||||
server.copyClass(InfoSocket.class);
|
|
||||||
server.copyClass(InfoServlet.class);
|
WebAppContext webapp = server16.createWebAppContext();
|
||||||
server.start();
|
server16.deployWebapp(webapp);
|
||||||
|
|
||||||
WebAppContext webapp = server.createWebAppContext();
|
return server16.getServer();
|
||||||
server.deployWebapp(webapp);
|
|
||||||
|
|
||||||
return server.getServer();
|
|
||||||
}
|
|
||||||
}});
|
}});
|
||||||
|
|
||||||
// xml based, wsuf, on alternate url-pattern and config attribute location
|
// xml based, wsuf, on alternate url-pattern and config attribute location
|
||||||
|
|
||||||
cases.add(new Object[]{"wsuf/WebAppContext/web.xml/ServletContextListener/alt-config", new ServerProvider()
|
cases.add(new Object[]{"wsuf/WebAppContext/web.xml/ServletContextListener/alt-config", (ServerProvider) () ->
|
||||||
{
|
{
|
||||||
@Override
|
File testDir = MavenTestingUtils.getTargetTestingDir("WSUF-webxml");
|
||||||
public Server newServer() throws Exception
|
|
||||||
{
|
WSServer server17 = new WSServer(testDir, "/");
|
||||||
File testDir = MavenTestingUtils.getTargetTestingDir("WSUF-webxml");
|
|
||||||
|
server17.copyWebInf("wsuf-alt-config-via-listener.xml");
|
||||||
WSServer server = new WSServer(testDir, "/");
|
server17.copyClass(InfoSocket.class);
|
||||||
|
server17.copyClass(InfoContextAltAttributeListener.class);
|
||||||
server.copyWebInf("wsuf-alt-config-via-listener.xml");
|
server17.start();
|
||||||
server.copyClass(InfoSocket.class);
|
|
||||||
server.copyClass(InfoContextAltAttributeListener.class);
|
WebAppContext webapp = server17.createWebAppContext();
|
||||||
server.start();
|
server17.deployWebapp(webapp);
|
||||||
|
|
||||||
WebAppContext webapp = server.createWebAppContext();
|
return server17.getServer();
|
||||||
server.deployWebapp(webapp);
|
|
||||||
|
|
||||||
return server.getServer();
|
|
||||||
}
|
|
||||||
}});
|
}});
|
||||||
|
|
||||||
return cases;
|
return cases;
|
||||||
|
|
|
@ -20,6 +20,7 @@ package org.eclipse.jetty.websocket.servlet;
|
||||||
|
|
||||||
import java.net.HttpCookie;
|
import java.net.HttpCookie;
|
||||||
import java.net.InetSocketAddress;
|
import java.net.InetSocketAddress;
|
||||||
|
import java.net.URI;
|
||||||
import java.net.URISyntaxException;
|
import java.net.URISyntaxException;
|
||||||
import java.security.Principal;
|
import java.security.Principal;
|
||||||
import java.security.cert.X509Certificate;
|
import java.security.cert.X509Certificate;
|
||||||
|
@ -37,68 +38,56 @@ import javax.servlet.http.HttpServletRequest;
|
||||||
import javax.servlet.http.HttpSession;
|
import javax.servlet.http.HttpSession;
|
||||||
|
|
||||||
import org.eclipse.jetty.websocket.api.UpgradeRequest;
|
import org.eclipse.jetty.websocket.api.UpgradeRequest;
|
||||||
|
import org.eclipse.jetty.websocket.api.WebSocketConstants;
|
||||||
import org.eclipse.jetty.websocket.api.extensions.ExtensionConfig;
|
import org.eclipse.jetty.websocket.api.extensions.ExtensionConfig;
|
||||||
import org.eclipse.jetty.websocket.api.util.WSURI;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Servlet specific {@link UpgradeRequest} implementation.
|
* Servlet specific {@link UpgradeRequest} implementation.
|
||||||
*/
|
*/
|
||||||
public class ServletUpgradeRequest extends UpgradeRequest
|
public class ServletUpgradeRequest implements UpgradeRequest
|
||||||
{
|
{
|
||||||
|
private static final String CANNOT_MODIFY_SERVLET_REQUEST = "Cannot modify Servlet Request";
|
||||||
|
private final URI requestURI;
|
||||||
private final UpgradeHttpServletRequest request;
|
private final UpgradeHttpServletRequest request;
|
||||||
|
private final boolean secure;
|
||||||
|
private List<HttpCookie> cookies;
|
||||||
|
private Map<String, List<String>> parameterMap;
|
||||||
|
private List<String> subprotocols;
|
||||||
|
|
||||||
public ServletUpgradeRequest(HttpServletRequest httpRequest) throws URISyntaxException
|
public ServletUpgradeRequest(HttpServletRequest httpRequest) throws URISyntaxException
|
||||||
{
|
{
|
||||||
super(WSURI.toWebsocket(httpRequest.getRequestURL(), httpRequest.getQueryString()));
|
URI servletURI = URI.create(httpRequest.getRequestURL().toString());
|
||||||
|
this.secure = httpRequest.isSecure();
|
||||||
|
String scheme = secure ? "wss" : "ws";
|
||||||
|
String authority = servletURI.getAuthority();
|
||||||
|
String path = servletURI.getPath();
|
||||||
|
String query = httpRequest.getQueryString();
|
||||||
|
String fragment = null;
|
||||||
|
this.requestURI = new URI(scheme,authority,path,query,fragment);
|
||||||
this.request = new UpgradeHttpServletRequest(httpRequest);
|
this.request = new UpgradeHttpServletRequest(httpRequest);
|
||||||
|
}
|
||||||
|
|
||||||
// Parse protocols.
|
@Override
|
||||||
Enumeration<String> requestProtocols = request.getHeaders("Sec-WebSocket-Protocol");
|
public void addExtensions(ExtensionConfig... configs)
|
||||||
if (requestProtocols != null)
|
{
|
||||||
{
|
throw new UnsupportedOperationException(CANNOT_MODIFY_SERVLET_REQUEST);
|
||||||
List<String> protocols = new ArrayList<>(2);
|
}
|
||||||
while (requestProtocols.hasMoreElements())
|
|
||||||
{
|
|
||||||
String candidate = requestProtocols.nextElement();
|
|
||||||
Collections.addAll(protocols, parseProtocols(candidate));
|
|
||||||
}
|
|
||||||
setSubProtocols(protocols);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Parse extensions.
|
@Override
|
||||||
Enumeration<String> e = request.getHeaders("Sec-WebSocket-Extensions");
|
public void addExtensions(String... configs)
|
||||||
setExtensions(ExtensionConfig.parseEnum(e));
|
{
|
||||||
|
throw new UnsupportedOperationException(CANNOT_MODIFY_SERVLET_REQUEST);
|
||||||
|
}
|
||||||
|
|
||||||
// Copy cookies.
|
@Override
|
||||||
Cookie[] requestCookies = request.getCookies();
|
public void clearHeaders()
|
||||||
if (requestCookies != null)
|
{
|
||||||
{
|
throw new UnsupportedOperationException(CANNOT_MODIFY_SERVLET_REQUEST);
|
||||||
List<HttpCookie> cookies = new ArrayList<>();
|
}
|
||||||
for (Cookie requestCookie : requestCookies)
|
|
||||||
{
|
|
||||||
HttpCookie cookie = new HttpCookie(requestCookie.getName(), requestCookie.getValue());
|
|
||||||
// No point handling domain/path/expires/secure/httponly on client request cookies
|
|
||||||
cookies.add(cookie);
|
|
||||||
}
|
|
||||||
setCookies(cookies);
|
|
||||||
}
|
|
||||||
|
|
||||||
setHeaders(request.getHeaders());
|
public void complete()
|
||||||
|
{
|
||||||
// Copy parameters.
|
request.complete();
|
||||||
Map<String, String[]> requestParams = request.getParameterMap();
|
|
||||||
if (requestParams != null)
|
|
||||||
{
|
|
||||||
Map<String, List<String>> params = new HashMap<>(requestParams.size());
|
|
||||||
for (Map.Entry<String, String[]> entry : requestParams.entrySet())
|
|
||||||
params.put(entry.getKey(), Arrays.asList(entry.getValue()));
|
|
||||||
setParameterMap(params);
|
|
||||||
}
|
|
||||||
|
|
||||||
setSession(request.getSession(false));
|
|
||||||
|
|
||||||
setHttpVersion(request.getProtocol());
|
|
||||||
setMethod(request.getMethod());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public X509Certificate[] getCertificates()
|
public X509Certificate[] getCertificates()
|
||||||
|
@ -106,6 +95,69 @@ public class ServletUpgradeRequest extends UpgradeRequest
|
||||||
return (X509Certificate[])request.getAttribute("javax.servlet.request.X509Certificate");
|
return (X509Certificate[])request.getAttribute("javax.servlet.request.X509Certificate");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<HttpCookie> getCookies()
|
||||||
|
{
|
||||||
|
if(cookies == null)
|
||||||
|
{
|
||||||
|
Cookie[] requestCookies = request.getCookies();
|
||||||
|
if (requestCookies != null)
|
||||||
|
{
|
||||||
|
cookies = new ArrayList<>();
|
||||||
|
for (Cookie requestCookie : requestCookies)
|
||||||
|
{
|
||||||
|
HttpCookie cookie = new HttpCookie(requestCookie.getName(), requestCookie.getValue());
|
||||||
|
// No point handling domain/path/expires/secure/httponly on client request cookies
|
||||||
|
cookies.add(cookie);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return cookies;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<ExtensionConfig> getExtensions()
|
||||||
|
{
|
||||||
|
Enumeration<String> e = request.getHeaders("Sec-WebSocket-Extensions");
|
||||||
|
return ExtensionConfig.parseEnum(e);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getHeader(String name)
|
||||||
|
{
|
||||||
|
return request.getHeader(name);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getHeaderInt(String name)
|
||||||
|
{
|
||||||
|
String val = request.getHeader(name);
|
||||||
|
if (val == null)
|
||||||
|
{
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
return Integer.parseInt(val);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Map<String, List<String>> getHeaders()
|
||||||
|
{
|
||||||
|
return request.getHeaders();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<String> getHeaders(String name)
|
||||||
|
{
|
||||||
|
return request.getHeaders().get(name);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getHost()
|
||||||
|
{
|
||||||
|
return requestURI.getHost();
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Return the underlying HttpServletRequest that existed at Upgrade time.
|
* Return the underlying HttpServletRequest that existed at Upgrade time.
|
||||||
* <p>
|
* <p>
|
||||||
|
@ -119,6 +171,12 @@ public class ServletUpgradeRequest extends UpgradeRequest
|
||||||
return request;
|
return request;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getHttpVersion()
|
||||||
|
{
|
||||||
|
return request.getProtocol();
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Equivalent to {@link HttpServletRequest#getLocalAddr()}
|
* Equivalent to {@link HttpServletRequest#getLocalAddr()}
|
||||||
*
|
*
|
||||||
|
@ -129,6 +187,26 @@ public class ServletUpgradeRequest extends UpgradeRequest
|
||||||
return request.getLocalAddr();
|
return request.getLocalAddr();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Equivalent to {@link HttpServletRequest#getLocale()}
|
||||||
|
*
|
||||||
|
* @return the preferred <code>Locale</code> for the client
|
||||||
|
*/
|
||||||
|
public Locale getLocale()
|
||||||
|
{
|
||||||
|
return request.getLocale();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Equivalent to {@link HttpServletRequest#getLocales()}
|
||||||
|
*
|
||||||
|
* @return an Enumeration of preferred Locale objects
|
||||||
|
*/
|
||||||
|
public Enumeration<Locale> getLocales()
|
||||||
|
{
|
||||||
|
return request.getLocales();
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Equivalent to {@link HttpServletRequest#getLocalName()}
|
* Equivalent to {@link HttpServletRequest#getLocalName()}
|
||||||
*
|
*
|
||||||
|
@ -161,26 +239,34 @@ public class ServletUpgradeRequest extends UpgradeRequest
|
||||||
return new InetSocketAddress(getLocalAddress(), getLocalPort());
|
return new InetSocketAddress(getLocalAddress(), getLocalPort());
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
@Override
|
||||||
* Equivalent to {@link HttpServletRequest#getLocale()}
|
public String getMethod()
|
||||||
*
|
|
||||||
* @return the preferred <code>Locale</code> for the client
|
|
||||||
*/
|
|
||||||
public Locale getLocale()
|
|
||||||
{
|
{
|
||||||
return request.getLocale();
|
return request.getMethod();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
@Override
|
||||||
* Equivalent to {@link HttpServletRequest#getLocales()}
|
public String getOrigin()
|
||||||
*
|
|
||||||
* @return an Enumeration of preferred Locale objects
|
|
||||||
*/
|
|
||||||
public Enumeration<Locale> getLocales()
|
|
||||||
{
|
{
|
||||||
return request.getLocales();
|
return getHeader("Origin");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Map<String, List<String>> getParameterMap()
|
||||||
|
{
|
||||||
|
if (parameterMap == null)
|
||||||
|
{
|
||||||
|
Map<String, String[]> requestParams = request.getParameterMap();
|
||||||
|
if (requestParams != null)
|
||||||
|
{
|
||||||
|
parameterMap = new HashMap<>(requestParams.size());
|
||||||
|
for (Map.Entry<String, String[]> entry : requestParams.entrySet())
|
||||||
|
parameterMap.put(entry.getKey(),Arrays.asList(entry.getValue()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return parameterMap;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @return the principal
|
* @return the principal
|
||||||
* @deprecated use {@link #getUserPrincipal()} instead
|
* @deprecated use {@link #getUserPrincipal()} instead
|
||||||
|
@ -191,12 +277,21 @@ public class ServletUpgradeRequest extends UpgradeRequest
|
||||||
return getUserPrincipal();
|
return getUserPrincipal();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
@Override
|
||||||
* Equivalent to {@link HttpServletRequest#getUserPrincipal()}
|
public String getProtocolVersion()
|
||||||
*/
|
|
||||||
public Principal getUserPrincipal()
|
|
||||||
{
|
{
|
||||||
return request.getUserPrincipal();
|
String version = request.getHeader(WebSocketConstants.SEC_WEBSOCKET_VERSION);
|
||||||
|
if(version == null)
|
||||||
|
{
|
||||||
|
return Integer.toString(WebSocketConstants.SPEC_VERSION);
|
||||||
|
}
|
||||||
|
return version;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getQueryString()
|
||||||
|
{
|
||||||
|
return requestURI.getQuery();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -241,11 +336,32 @@ public class ServletUpgradeRequest extends UpgradeRequest
|
||||||
return new InetSocketAddress(getRemoteAddress(), getRemotePort());
|
return new InetSocketAddress(getRemoteAddress(), getRemotePort());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public String getRequestPath()
|
||||||
|
{
|
||||||
|
// Since this can be called from a filter, we need to be smart about determining the target request path.
|
||||||
|
String contextPath = request.getContextPath();
|
||||||
|
String requestPath = request.getRequestURI();
|
||||||
|
if (requestPath.startsWith(contextPath))
|
||||||
|
requestPath = requestPath.substring(contextPath.length());
|
||||||
|
return requestPath;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public URI getRequestURI()
|
||||||
|
{
|
||||||
|
return requestURI;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Object getServletAttribute(String name)
|
||||||
|
{
|
||||||
|
return request.getAttribute(name);
|
||||||
|
}
|
||||||
|
|
||||||
public Map<String, Object> getServletAttributes()
|
public Map<String, Object> getServletAttributes()
|
||||||
{
|
{
|
||||||
return request.getAttributes();
|
return request.getAttributes();
|
||||||
}
|
}
|
||||||
|
|
||||||
public Map<String, List<String>> getServletParameters()
|
public Map<String, List<String>> getServletParameters()
|
||||||
{
|
{
|
||||||
return getParameterMap();
|
return getParameterMap();
|
||||||
|
@ -263,14 +379,56 @@ public class ServletUpgradeRequest extends UpgradeRequest
|
||||||
return request.getSession(false);
|
return request.getSession(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setServletAttribute(String name, Object value)
|
@Override
|
||||||
|
public List<String> getSubProtocols()
|
||||||
{
|
{
|
||||||
request.setAttribute(name, value);
|
if (subprotocols == null)
|
||||||
|
{
|
||||||
|
Enumeration<String> requestProtocols = request.getHeaders("Sec-WebSocket-Protocol");
|
||||||
|
if (requestProtocols != null)
|
||||||
|
{
|
||||||
|
subprotocols = new ArrayList<>(2);
|
||||||
|
while (requestProtocols.hasMoreElements())
|
||||||
|
{
|
||||||
|
String candidate = requestProtocols.nextElement();
|
||||||
|
Collections.addAll(subprotocols,parseProtocols(candidate));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return subprotocols;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Object getServletAttribute(String name)
|
/**
|
||||||
|
* Equivalent to {@link HttpServletRequest#getUserPrincipal()}
|
||||||
|
*/
|
||||||
|
public Principal getUserPrincipal()
|
||||||
{
|
{
|
||||||
return request.getAttribute(name);
|
return request.getUserPrincipal();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean hasSubProtocol(String test)
|
||||||
|
{
|
||||||
|
for (String protocol : getSubProtocols())
|
||||||
|
{
|
||||||
|
if (protocol.equalsIgnoreCase(test))
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isOrigin(String test)
|
||||||
|
{
|
||||||
|
return test.equalsIgnoreCase(getOrigin());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isSecure()
|
||||||
|
{
|
||||||
|
return this.secure;
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean isUserInRole(String role)
|
public boolean isUserInRole(String role)
|
||||||
|
@ -278,16 +436,6 @@ public class ServletUpgradeRequest extends UpgradeRequest
|
||||||
return request.isUserInRole(role);
|
return request.isUserInRole(role);
|
||||||
}
|
}
|
||||||
|
|
||||||
public String getRequestPath()
|
|
||||||
{
|
|
||||||
// Since this can be called from a filter, we need to be smart about determining the target request path.
|
|
||||||
String contextPath = request.getContextPath();
|
|
||||||
String requestPath = request.getRequestURI();
|
|
||||||
if (requestPath.startsWith(contextPath))
|
|
||||||
requestPath = requestPath.substring(contextPath.length());
|
|
||||||
return requestPath;
|
|
||||||
}
|
|
||||||
|
|
||||||
private String[] parseProtocols(String protocol)
|
private String[] parseProtocols(String protocol)
|
||||||
{
|
{
|
||||||
if (protocol == null)
|
if (protocol == null)
|
||||||
|
@ -298,8 +446,74 @@ public class ServletUpgradeRequest extends UpgradeRequest
|
||||||
return protocol.split("\\s*,\\s*");
|
return protocol.split("\\s*,\\s*");
|
||||||
}
|
}
|
||||||
|
|
||||||
public void complete()
|
@Override
|
||||||
|
public void setCookies(List<HttpCookie> cookies)
|
||||||
{
|
{
|
||||||
request.complete();
|
throw new UnsupportedOperationException(CANNOT_MODIFY_SERVLET_REQUEST);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setExtensions(List<ExtensionConfig> configs)
|
||||||
|
{
|
||||||
|
throw new UnsupportedOperationException(CANNOT_MODIFY_SERVLET_REQUEST);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setHeader(String name, List<String> values)
|
||||||
|
{
|
||||||
|
throw new UnsupportedOperationException(CANNOT_MODIFY_SERVLET_REQUEST);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setHeader(String name, String value)
|
||||||
|
{
|
||||||
|
throw new UnsupportedOperationException(CANNOT_MODIFY_SERVLET_REQUEST);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setHeaders(Map<String, List<String>> headers)
|
||||||
|
{
|
||||||
|
throw new UnsupportedOperationException(CANNOT_MODIFY_SERVLET_REQUEST);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setHttpVersion(String httpVersion)
|
||||||
|
{
|
||||||
|
throw new UnsupportedOperationException(CANNOT_MODIFY_SERVLET_REQUEST);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setMethod(String method)
|
||||||
|
{
|
||||||
|
throw new UnsupportedOperationException(CANNOT_MODIFY_SERVLET_REQUEST);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setRequestURI(URI uri)
|
||||||
|
{
|
||||||
|
throw new UnsupportedOperationException(CANNOT_MODIFY_SERVLET_REQUEST);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setServletAttribute(String name, Object value)
|
||||||
|
{
|
||||||
|
request.setAttribute(name, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setSession(Object session)
|
||||||
|
{
|
||||||
|
throw new UnsupportedOperationException(CANNOT_MODIFY_SERVLET_REQUEST);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setSubProtocols(List<String> subProtocols)
|
||||||
|
{
|
||||||
|
throw new UnsupportedOperationException(CANNOT_MODIFY_SERVLET_REQUEST);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setSubProtocols(String... protocols)
|
||||||
|
{
|
||||||
|
throw new UnsupportedOperationException(CANNOT_MODIFY_SERVLET_REQUEST);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -19,45 +19,112 @@
|
||||||
package org.eclipse.jetty.websocket.servlet;
|
package org.eclipse.jetty.websocket.servlet;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
import java.util.Set;
|
||||||
|
import java.util.TreeMap;
|
||||||
|
|
||||||
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.WebSocketConstants;
|
||||||
import org.eclipse.jetty.websocket.api.extensions.ExtensionConfig;
|
import org.eclipse.jetty.websocket.api.extensions.ExtensionConfig;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Servlet Specific UpgradeResponse implementation.
|
* Servlet Specific UpgradeResponse implementation.
|
||||||
*/
|
*/
|
||||||
public class ServletUpgradeResponse extends UpgradeResponse
|
public class ServletUpgradeResponse implements UpgradeResponse
|
||||||
{
|
{
|
||||||
private HttpServletResponse response;
|
private HttpServletResponse response;
|
||||||
private boolean extensionsNegotiated = false;
|
private boolean extensionsNegotiated = false;
|
||||||
private boolean subprotocolNegotiated = false;
|
private boolean subprotocolNegotiated = false;
|
||||||
|
private Map<String, List<String>> headers = new TreeMap<>(String.CASE_INSENSITIVE_ORDER);
|
||||||
|
private List<ExtensionConfig> extensions = new ArrayList<>();
|
||||||
|
private boolean success = false;
|
||||||
|
|
||||||
public ServletUpgradeResponse(HttpServletResponse response)
|
public ServletUpgradeResponse(HttpServletResponse response)
|
||||||
{
|
{
|
||||||
this.response = response;
|
this.response = response;
|
||||||
|
|
||||||
|
for (String name : response.getHeaderNames())
|
||||||
|
{
|
||||||
|
headers.put(name, new ArrayList<String>(response.getHeaders(name)));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void addHeader(String name, String value)
|
||||||
|
{
|
||||||
|
this.response.addHeader(name, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void commitHeaders()
|
||||||
|
{
|
||||||
|
// Transfer all headers to the real HTTP response
|
||||||
|
for (Map.Entry<String, List<String>> entry : getHeaders().entrySet())
|
||||||
|
{
|
||||||
|
for (String value : entry.getValue())
|
||||||
|
{
|
||||||
|
response.addHeader(entry.getKey(), value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void complete()
|
||||||
|
{
|
||||||
|
commitHeaders();
|
||||||
|
response = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getAcceptedSubProtocol()
|
||||||
|
{
|
||||||
|
return getHeader(WebSocketConstants.SEC_WEBSOCKET_PROTOCOL);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<ExtensionConfig> getExtensions()
|
||||||
|
{
|
||||||
|
return extensions;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getHeader(String name)
|
||||||
|
{
|
||||||
|
return response.getHeader(name);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Set<String> getHeaderNames()
|
||||||
|
{
|
||||||
|
return getHeaders().keySet();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Map<String, List<String>> getHeaders()
|
||||||
|
{
|
||||||
|
return headers;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<String> getHeaders(String name)
|
||||||
|
{
|
||||||
|
return getHeaders().get(name);
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int getStatusCode()
|
public int getStatusCode()
|
||||||
{
|
{
|
||||||
return response.getStatus();
|
return response.getStatus();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setStatus(int status)
|
|
||||||
{
|
|
||||||
response.setStatus(status);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String getStatusReason()
|
public String getStatusReason()
|
||||||
{
|
{
|
||||||
throw new UnsupportedOperationException("Server cannot get Status Reason Message");
|
throw new UnsupportedOperationException("Servlet's do not support Status Reason");
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean isCommitted()
|
public boolean isCommitted()
|
||||||
{
|
{
|
||||||
if (response != null)
|
if (response != null)
|
||||||
|
@ -67,17 +134,23 @@ public class ServletUpgradeResponse extends UpgradeResponse
|
||||||
// True in all other cases
|
// True in all other cases
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean isExtensionsNegotiated()
|
public boolean isExtensionsNegotiated()
|
||||||
{
|
{
|
||||||
return extensionsNegotiated;
|
return extensionsNegotiated;
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean isSubprotocolNegotiated()
|
public boolean isSubprotocolNegotiated()
|
||||||
{
|
{
|
||||||
return subprotocolNegotiated;
|
return subprotocolNegotiated;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isSuccess()
|
||||||
|
{
|
||||||
|
return success;
|
||||||
|
}
|
||||||
|
|
||||||
public void sendError(int statusCode, String message) throws IOException
|
public void sendError(int statusCode, String message) throws IOException
|
||||||
{
|
{
|
||||||
setSuccess(false);
|
setSuccess(false);
|
||||||
|
@ -86,7 +159,7 @@ public class ServletUpgradeResponse extends UpgradeResponse
|
||||||
response.flushBuffer(); // commit response
|
response.flushBuffer(); // commit response
|
||||||
response = null;
|
response = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void sendForbidden(String message) throws IOException
|
public void sendForbidden(String message) throws IOException
|
||||||
{
|
{
|
||||||
|
@ -96,27 +169,30 @@ public class ServletUpgradeResponse extends UpgradeResponse
|
||||||
response.flushBuffer(); // commit response
|
response.flushBuffer(); // commit response
|
||||||
response = null;
|
response = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void setAcceptedSubProtocol(String protocol)
|
public void setAcceptedSubProtocol(String protocol)
|
||||||
{
|
{
|
||||||
super.setAcceptedSubProtocol(protocol);
|
response.setHeader(WebSocketConstants.SEC_WEBSOCKET_PROTOCOL, protocol);
|
||||||
subprotocolNegotiated = true;
|
subprotocolNegotiated = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void setExtensions(List<ExtensionConfig> extensions)
|
public void setExtensions(List<ExtensionConfig> configs)
|
||||||
{
|
{
|
||||||
super.setExtensions(extensions);
|
this.extensions.clear();
|
||||||
|
this.extensions.addAll(configs);
|
||||||
|
String value = ExtensionConfig.toHeaderValue(configs);
|
||||||
|
response.setHeader(WebSocketConstants.SEC_WEBSOCKET_EXTENSIONS, value);
|
||||||
extensionsNegotiated = true;
|
extensionsNegotiated = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void complete()
|
@Override
|
||||||
|
public void setHeader(String name, String value)
|
||||||
{
|
{
|
||||||
applyHeaders();
|
response.setHeader(name, value);
|
||||||
response = null;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void applyHeaders()
|
private void applyHeaders()
|
||||||
{
|
{
|
||||||
// Transfer all headers to the real HTTP response
|
// Transfer all headers to the real HTTP response
|
||||||
|
@ -128,4 +204,27 @@ public class ServletUpgradeResponse extends UpgradeResponse
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void setStatus(int status)
|
||||||
|
{
|
||||||
|
response.setStatus(status);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setStatusCode(int statusCode)
|
||||||
|
{
|
||||||
|
response.setStatus(statusCode);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setStatusReason(String statusReason)
|
||||||
|
{
|
||||||
|
throw new UnsupportedOperationException("Servlet's do not support Status Reason");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setSuccess(boolean success)
|
||||||
|
{
|
||||||
|
this.success = success;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue