diff options
author | Joakim Erdfelt | 2015-11-12 00:00:54 +0000 |
---|---|---|
committer | Joakim Erdfelt | 2015-11-12 00:00:54 +0000 |
commit | 02b4ac669634dd244a507df4d493f7e2625c2c06 (patch) | |
tree | 7f78f9ff377c6cff8163676be9964c01cde813a6 | |
parent | 13f2f1b64c0b1cfa4e6dd521c16e85607b757ab5 (diff) | |
download | org.eclipse.jetty.project-feature/wsclient-httpclient.tar.gz org.eclipse.jetty.project-feature/wsclient-httpclient.tar.xz org.eclipse.jetty.project-feature/wsclient-httpclient.zip |
Reworking UpgradeRequest / UpgradeResponse APIsfeature/wsclient-httpclient
+ They are now interfaces (it was impossible to instantiate
them with the old API anyway)
+ The adapters have been broken out (so far, possibly remove in the
future)
+ Reworked WebSocketUpgradeRequest (HttpClient version) to use
new UpgradeRequest (websocket api version) interface
+ Reworked ServletUpgradeRequest and ServletUpgradeResponse to rely
on actual HttpServletRequest and HttpServletResponse objects more
for their data.
18 files changed, 1825 insertions, 637 deletions
diff --git a/jetty-websocket/websocket-api/src/main/java/org/eclipse/jetty/websocket/api/Session.java b/jetty-websocket/websocket-api/src/main/java/org/eclipse/jetty/websocket/api/Session.java index 3d28ecb290..5e98bc65c1 100644 --- a/jetty-websocket/websocket-api/src/main/java/org/eclipse/jetty/websocket/api/Session.java +++ b/jetty-websocket/websocket-api/src/main/java/org/eclipse/jetty/websocket/api/Session.java @@ -22,8 +22,6 @@ import java.io.Closeable; import java.io.IOException; import java.net.InetSocketAddress; -import org.eclipse.jetty.websocket.api.annotations.OnWebSocketClose; - /** * Session represents an active link of communications with a Remote WebSocket Endpoint. */ diff --git a/jetty-websocket/websocket-api/src/main/java/org/eclipse/jetty/websocket/api/UpgradeRequest.java b/jetty-websocket/websocket-api/src/main/java/org/eclipse/jetty/websocket/api/UpgradeRequest.java index 79da9ec20c..38f013ad25 100644 --- a/jetty-websocket/websocket-api/src/main/java/org/eclipse/jetty/websocket/api/UpgradeRequest.java +++ b/jetty-websocket/websocket-api/src/main/java/org/eclipse/jetty/websocket/api/UpgradeRequest.java @@ -21,342 +21,307 @@ package org.eclipse.jetty.websocket.api; 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.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); - 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 UpgradeRequest() - { - /* anonymous, no requestURI, upgrade request */ - } - - public UpgradeRequest(String requestURI) - { - this(URI.create(requestURI)); - } - - public UpgradeRequest(URI requestURI) - { - setRequestURI(requestURI); - } - - public void addExtensions(ExtensionConfig... configs) - { - Collections.addAll(extensions, configs); - } - - public void addExtensions(String... configs) - { - for (String config : configs) - { - extensions.add(ExtensionConfig.parse(config)); - } - } - - public void clearHeaders() - { - headers.clear(); - } - - public List<HttpCookie> getCookies() - { - return cookies; - } - - public List<ExtensionConfig> getExtensions() - { - return extensions; - } - - 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(); - } - - 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"); - } - - public Map<String, List<String>> getHeaders() - { - return headers; - } - - public List<String> getHeaders(String name) - { - return headers.get(name); - } - - public String getHost() - { - return host; - } - - public String getHttpVersion() - { - return httpVersion; - } - - public String getMethod() - { - return method; - } - - public String getOrigin() - { - return getHeader("Origin"); - } + /** + * Add WebSocket Extension Configuration(s) to 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(ExtensionConfig... configs); + + /** + * Add WebSocket Extension Configuration(s) to 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); + + /** + * Remove all headers from request. + * @deprecated (no longer supported, as this can undo the required upgrade request headers) + */ + @Deprecated + void clearHeaders(); + + /** + * Get the list of Cookies on the Upgrade request + * + * @return the list of Cookies + */ + List<HttpCookie> getCookies(); + + /** + * Get the list of WebSocket Extension Configurations for this Upgrade Request. + * <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(); + + /** + * Get a specific Header value from Upgrade Request + * + * @param name the name of the header + * @return the value of the header (null if header does not exist) + */ + String getHeader(String name); + + /** + * Get the specific Header value, as an <code>int</code>, from the Upgrade Request. + * + * @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); + + /** + * Get the headers as a Map of keys to value lists. + * + * @return the headers + */ + Map<String, List<String>> getHeaders(); + + /** + * Get the specific header values (for multi-value headers) + * + * @param name the header name + * @return the value list (null if no header exists) + */ + List<String> getHeaders(String name); + + /** + * The host of the Upgrade Request URI + * <p> + * Equivalent to {@link #getRequestURI()#getHost()} + * + * @return host of the request URI + */ + String getHost(); + + /** + * The HTTP version used for this Upgrade Request + * <p> + * As of <a href="http://tools.ietf.org/html/rfc6455">RFC6455 (December 2011)</a> this is always + * <code>HTTP/1.1</code> + * + * @return the HTTP Version used + */ + String getHttpVersion(); + + /** + * The HTTP method for this Upgrade Request. + * <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(); + + /** + * The WebSocket Origin of this Upgrade Request + * <p> + * See <a href="http://tools.ietf.org/html/rfc6455#section-10.2">RFC6455: Section 10.2</a> for details. + * <p> + * Equivalent to {@link #getHeader("Origin")} + * + * @return the Origin header + */ + String getOrigin(); /** * Returns a map of the query parameters of the request. - * + * * @return a unmodifiable map of query parameters of the request. */ - public Map<String, List<String>> getParameterMap() - { - return Collections.unmodifiableMap(parameters); - } + Map<String, List<String>> getParameterMap(); - public String getProtocolVersion() - { - String version = getHeader("Sec-WebSocket-Version"); - if (version == null) - { - return "13"; // Default - } - return version; - } + /** + * Get the WebSocket Protocol Version + * <p> + * As of <a href="http://tools.ietf.org/html/rfc6455#section-11.6">RFC6455</a>, Jetty only supports version + * <code>13</code> + * + * @return the WebSocket protocol version + */ + String getProtocolVersion(); - public String getQueryString() - { - return requestURI.getQuery(); - } + /** + * Get the Query String of the request URI. + * <p> + * Equivalent to {@link #getRequestURI()#getQueryString()} + * + * @return the request uri query string + */ + String getQueryString(); - public URI getRequestURI() - { - return requestURI; - } + /** + * Get the Request URI + * + * @return the request URI + */ + URI getRequestURI(); /** * Access the Servlet HTTP Session (if present) * <p> * Note: Never present on a Client UpgradeRequest. - * + * * @return the Servlet HTTPSession on server side UpgradeRequests */ - public Object getSession() - { - return session; - } + Object getSession(); - public List<String> getSubProtocols() - { - return subProtocols; - } + /** + * Get the list of offered WebSocket sub-protocols. + * + * @return the list of offered sub-protocols + */ + List<String> getSubProtocols(); /** * Get the User Principal for this request. * <p> * Only applicable when using UpgradeRequest from server side. - * + * * @return the user principal */ - public 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. - * - * @param protocols - * the sub protocols desired - */ - public void setSubProtocols(String... protocols) - { - subProtocols.clear(); - Collections.addAll(subProtocols, protocols); - } -} + Principal getUserPrincipal(); + + /** + * Test if a specific sub-protocol is offered + * + * @param test the sub-protocol to test for + * @return true if sub-protocol exists on request + */ + boolean hasSubProtocol(String test); + + /** + * 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); + +}
\ No newline at end of file diff --git a/jetty-websocket/websocket-api/src/main/java/org/eclipse/jetty/websocket/api/UpgradeResponse.java b/jetty-websocket/websocket-api/src/main/java/org/eclipse/jetty/websocket/api/UpgradeResponse.java index 91df0d723b..4f0b5932e7 100644 --- a/jetty-websocket/websocket-api/src/main/java/org/eclipse/jetty/websocket/api/UpgradeResponse.java +++ b/jetty-websocket/websocket-api/src/main/java/org/eclipse/jetty/websocket/api/UpgradeResponse.java @@ -19,119 +19,92 @@ package org.eclipse.jetty.websocket.api; 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.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; - 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; - - 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); - } + /** + * Add a header value to the response. + * + * @param name the header name + * @param value the header value + */ + void addHeader(String name, String value); /** * Get the accepted WebSocket protocol. * * @return the accepted WebSocket protocol. */ - public String getAcceptedSubProtocol() - { - return getHeader(SEC_WEBSOCKET_PROTOCOL); - } + String getAcceptedSubProtocol(); /** * Get the list of extensions that should be used for the websocket. * * @return the list of negotiated extensions to use. */ - public List<ExtensionConfig> getExtensions() - { - return extensions; - } - - 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(); - } - - public Set<String> getHeaderNames() - { - return headers.keySet(); - } - - public Map<String, List<String>> getHeaders() - { - return headers; - } - - public List<String> getHeaders(String name) - { - return headers.get(name); - } - - public int getStatusCode() - { - return statusCode; - } - - public String getStatusReason() - { - return statusReason; - } - - public boolean isSuccess() - { - return success; - } + List<ExtensionConfig> getExtensions(); + + /** + * Get a header value + * + * @param name the header name + * @return the value (null if header doesn't exist) + */ + String getHeader(String name); + + /** + * Get the header names + * + * @return the set of header names + */ + Set<String> getHeaderNames(); + + /** + * Get the headers map + * + * @return the map of headers + */ + Map<String, List<String>> getHeaders(); + + /** + * Get the multi-value header value + * + * @param name the header name + * @return the list of values (null if header doesn't exist) + */ + List<String> getHeaders(String name); + + /** + * Get the HTTP Response Status Code + * + * @return the status code + */ + int getStatusCode(); + + /** + * Get the HTTP Response Status Reason + * + * @return the HTTP Response status reason + */ + String getStatusReason(); + + /** + * Test if upgrade response is successful. + * <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. @@ -142,67 +115,69 @@ public class UpgradeResponse * Use this when the origin or authentication is invalid. * * @param message - * the short 1 line detail message about the forbidden response + * the short 1 line detail message about the forbidden response * @throws IOException - * if unable to send the forbidden + * if unable to send the forbidden */ - public void sendForbidden(String message) throws IOException - { - throw new UnsupportedOperationException("Not supported"); - } + void sendForbidden(String message) throws IOException; /** * Set the accepted WebSocket Protocol. * * @param protocol - * the protocol to list as accepted + * the protocol to list as accepted */ - public void setAcceptedSubProtocol(String protocol) - { - setHeader(SEC_WEBSOCKET_PROTOCOL,protocol); - } + void setAcceptedSubProtocol(String 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 + * <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. - */ - public void setExtensions(List<ExtensionConfig> extensions) - { - this.extensions.clear(); - if (extensions != null) - { - this.extensions.addAll(extensions); - } - } - - public void setHeader(String name, String value) - { - List<String> values = new ArrayList<>(); - values.add(value); - headers.put(name,values); - } - - public void setStatusCode(int statusCode) - { - this.statusCode = statusCode; - } - - public void setStatusReason(String statusReason) - { - this.statusReason = statusReason; - } - - public void setSuccess(boolean success) - { - this.success = success; - } -} + * the list of extensions to use. + */ + void setExtensions(List<ExtensionConfig> extensions); + + /** + * Set a header + * <p> + * Overrides previous value of header (if set) + * + * @param name the header name + * @param value the header value + */ + void setHeader(String name, String value); + + /** + * Set the HTTP Response status code + * + * @param statusCode the status code + */ + void setStatusCode(int statusCode); + + /** + * Set the HTTP Response status reason phrase + * <p> + * Note, not all implementation of UpgradeResponse can support this feature + * + * @param statusReason the status reason phrase + */ + void setStatusReason(String statusReason); + + /** + * Set the success of the upgrade response. + * <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); +}
\ No newline at end of file diff --git a/jetty-websocket/websocket-api/src/main/java/org/eclipse/jetty/websocket/api/WebSocketConstants.java b/jetty-websocket/websocket-api/src/main/java/org/eclipse/jetty/websocket/api/WebSocketConstants.java new file mode 100644 index 0000000000..da81515bbd --- /dev/null +++ b/jetty-websocket/websocket-api/src/main/java/org/eclipse/jetty/websocket/api/WebSocketConstants.java @@ -0,0 +1,27 @@ +// +// ======================================================================== +// Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd. +// ------------------------------------------------------------------------ +// All rights reserved. This program and the accompanying materials +// are made available under the terms of the Eclipse Public License v1.0 +// and Apache License v2.0 which accompanies this distribution. +// +// The Eclipse Public License is available at +// http://www.eclipse.org/legal/epl-v10.html +// +// The Apache License v2.0 is available at +// http://www.opensource.org/licenses/apache2.0.php +// +// You may elect to redistribute this code under either of these licenses. +// ======================================================================== +// + +package org.eclipse.jetty.websocket.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; +} diff --git a/jetty-websocket/websocket-client/src/main/java/org/eclipse/jetty/websocket/client/ClientUpgradeRequest.java b/jetty-websocket/websocket-client/src/main/java/org/eclipse/jetty/websocket/client/ClientUpgradeRequest.java index d6abcb330b..30c7dea476 100644 --- a/jetty-websocket/websocket-client/src/main/java/org/eclipse/jetty/websocket/client/ClientUpgradeRequest.java +++ b/jetty-websocket/websocket-client/src/main/java/org/eclipse/jetty/websocket/client/ClientUpgradeRequest.java @@ -37,13 +37,13 @@ import org.eclipse.jetty.util.B64Code; import org.eclipse.jetty.util.MultiMap; import org.eclipse.jetty.util.StringUtil; import org.eclipse.jetty.util.UrlEncoded; -import org.eclipse.jetty.websocket.api.UpgradeRequest; import org.eclipse.jetty.websocket.api.extensions.ExtensionConfig; +import org.eclipse.jetty.websocket.common.UpgradeRequestAdapter; /** * Allowing a generate from a UpgradeRequest */ -public class ClientUpgradeRequest extends UpgradeRequest +public class ClientUpgradeRequest extends UpgradeRequestAdapter { private static final Set<String> FORBIDDEN_HEADERS; diff --git a/jetty-websocket/websocket-client/src/main/java/org/eclipse/jetty/websocket/client/ClientUpgradeResponse.java b/jetty-websocket/websocket-client/src/main/java/org/eclipse/jetty/websocket/client/ClientUpgradeResponse.java index 9ed2ab1a72..9103387bdc 100644 --- a/jetty-websocket/websocket-client/src/main/java/org/eclipse/jetty/websocket/client/ClientUpgradeResponse.java +++ b/jetty-websocket/websocket-client/src/main/java/org/eclipse/jetty/websocket/client/ClientUpgradeResponse.java @@ -24,10 +24,11 @@ import java.util.List; import org.eclipse.jetty.client.HttpResponse; import org.eclipse.jetty.http.HttpField; import org.eclipse.jetty.http.HttpFields; -import org.eclipse.jetty.websocket.api.UpgradeResponse; +import org.eclipse.jetty.http.HttpHeader; import org.eclipse.jetty.websocket.api.extensions.ExtensionConfig; +import org.eclipse.jetty.websocket.common.UpgradeResponseAdapter; -public class ClientUpgradeResponse extends UpgradeResponse +public class ClientUpgradeResponse extends UpgradeResponseAdapter { private List<ExtensionConfig> extensions; @@ -48,8 +49,10 @@ public class ClientUpgradeResponse extends UpgradeResponse addHeader(field.getName(),field.getValue()); } - this.extensions = ExtensionConfig.parseEnum(fields.getValues("Sec-WebSocket-Extensions")); - setAcceptedSubProtocol(fields.get("Sec-WebSocket-Protocol")); + 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 diff --git a/jetty-websocket/websocket-client/src/main/java/org/eclipse/jetty/websocket/client/WebSocketClient.java b/jetty-websocket/websocket-client/src/main/java/org/eclipse/jetty/websocket/client/WebSocketClient.java index 24c1302b1d..2a23990b38 100644 --- a/jetty-websocket/websocket-client/src/main/java/org/eclipse/jetty/websocket/client/WebSocketClient.java +++ b/jetty-websocket/websocket-client/src/main/java/org/eclipse/jetty/websocket/client/WebSocketClient.java @@ -285,21 +285,35 @@ public class WebSocketClient extends ContainerLifeCycle implements SessionListen * the websocket uri to connect to * @param request * the upgrade request information - * @param upgradeListener - * does nothing * @return the future for the session, available on success of connect * @throws IOException * if unable to connect - * @deprecated {@link UpgradeListener} no longer supported, no alternative available */ - @Deprecated - public Future<Session> connect(Object websocket, URI toUri, ClientUpgradeRequest request, UpgradeListener upgradeListener) throws IOException + public Future<Session> connect(Object websocket, URI toUri, ClientUpgradeRequest request) throws IOException { - return connect(websocket,toUri,request); + return connect(websocket,toUri,request,(UpgradeListener)null); } - - public Future<Session> connect(Object websocket, URI toUri, ClientUpgradeRequest request) throws IOException + + /** + * 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 { + /* Note: UpgradeListener is used by javax.websocket.ClientEndpointConfig.Configurator + * See: org.eclipse.jetty.websocket.jsr356.JsrUpgradeListener + */ if (!isStarted()) { throw new IllegalStateException(WebSocketClient.class.getSimpleName() + "@" + this.hashCode() + " is not started"); @@ -340,6 +354,7 @@ public class WebSocketClient extends ContainerLifeCycle implements SessionListen init(); WebSocketUpgradeRequest wsReq = new WebSocketUpgradeRequest(httpClient,request); + wsReq.setUpgradeListener(upgradeListener); return wsReq.sendAsync(); } diff --git a/jetty-websocket/websocket-client/src/main/java/org/eclipse/jetty/websocket/client/WebSocketUpgradeRequest.java b/jetty-websocket/websocket-client/src/main/java/org/eclipse/jetty/websocket/client/WebSocketUpgradeRequest.java index c54be66eb7..205e4b2ed1 100644 --- a/jetty-websocket/websocket-client/src/main/java/org/eclipse/jetty/websocket/client/WebSocketUpgradeRequest.java +++ b/jetty-websocket/websocket-client/src/main/java/org/eclipse/jetty/websocket/client/WebSocketUpgradeRequest.java @@ -20,8 +20,13 @@ 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; @@ -39,20 +44,26 @@ 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; @@ -63,12 +74,290 @@ 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; - private List<ExtensionConfig> extensions; - private List<String> subProtocols; + /** WebSocket API UpgradeRequest Facade to HttpClient HttpRequest */ + private final ClientUpgradeRequestFacade apiRequestFacade; + private UpgradeListener upgradeListener; private static WebSocketClient getManagedWebSocketClient(HttpClient httpClient) { @@ -91,49 +380,41 @@ public class WebSocketUpgradeRequest extends HttpRequest implements CompleteList */ protected WebSocketUpgradeRequest(HttpClient httpClient, ClientUpgradeRequest request) { - this(httpClient, request.getRequestURI(), request.getLocalEndpoint()); - - // Copy values from ClientUpgradeRequest into place - this.extensions = new ArrayList<>(request.getExtensions()); - this.subProtocols = new ArrayList<>(request.getSubProtocols()); - if (StringUtil.isNotBlank(request.getOrigin())) - this.header(HttpHeader.ORIGIN,request.getOrigin()); - for (HttpCookie cookie : request.getCookies()) - { - this.cookie(cookie); - } + this(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 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(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"))) + if (scheme == null || !(scheme.equalsIgnoreCase("ws") || scheme.equalsIgnoreCase("wss"))) { throw new IllegalArgumentException("WebSocket URI must use 'ws' or 'wss' scheme: " + wsURI); } - + // WebSocketClient(HttpClient) -> WebSocketUpgradeRequest(HttpClient, WebSocketClient) - + this.wsClient = getManagedWebSocketClient(httpClient); this.localEndpoint = this.wsClient.getEventDriverFactory().wrap(localEndpoint); - + this.fut = new CompletableFuture<Session>(); - this.extensions = new ArrayList<>(); - this.subProtocols = new ArrayList<>(); } private final String genRandomKey() @@ -148,21 +429,11 @@ public class WebSocketUpgradeRequest extends HttpRequest implements CompleteList return this.wsClient.getExtensionFactory(); } - public List<ExtensionConfig> getExtensions() - { - return extensions; - } - private SessionFactory getSessionFactory() { return this.wsClient.getSessionFactory(); } - public List<String> getSubProtocols() - { - return subProtocols; - } - private void initWebSocketHeaders() { method(HttpMethod.GET); @@ -184,18 +455,18 @@ public class WebSocketUpgradeRequest extends HttpRequest implements CompleteList header(HttpHeader.CACHE_CONTROL,"no-cache"); // handle "Sec-WebSocket-Extensions" - if (!getExtensions().isEmpty()) + if (!apiRequestFacade.getExtensions().isEmpty()) { - for (ExtensionConfig ext : getExtensions()) + for (ExtensionConfig ext : apiRequestFacade.getExtensions()) { header(HttpHeader.SEC_WEBSOCKET_EXTENSIONS,ext.getParameterizedName()); } } // handle "Sec-WebSocket-Protocol" - if (!getSubProtocols().isEmpty()) + if (!apiRequestFacade.getSubProtocols().isEmpty()) { - for (String protocol : getSubProtocols()) + for (String protocol : apiRequestFacade.getSubProtocols()) { header(HttpHeader.SEC_WEBSOCKET_SUBPROTOCOL,protocol); } @@ -209,7 +480,7 @@ public class WebSocketUpgradeRequest extends HttpRequest implements CompleteList { LOG.debug("onComplete() - {}",result); } - + URI requestURI = result.getRequest().getURI(); Response response = result.getResponse(); int responseStatusCode = response.getStatus(); @@ -217,16 +488,15 @@ public class WebSocketUpgradeRequest extends HttpRequest implements CompleteList if (result.isFailed()) { - if(result.getFailure()!=null) - LOG.warn("General Failure", result.getFailure()); - if(result.getRequestFailure()!=null) - LOG.warn("Request Failure", result.getRequestFailure()); - if(result.getResponseFailure()!=null) - LOG.warn("Response Failure", result.getResponseFailure()); - + if (result.getFailure() != null) + LOG.warn("General Failure",result.getFailure()); + if (result.getRequestFailure() != null) + LOG.warn("Request Failure",result.getRequestFailure()); + if (result.getResponseFailure() != null) + LOG.warn("Response Failure",result.getResponseFailure()); + Throwable failure = result.getFailure(); - if ((failure instanceof java.net.ConnectException) || - (failure instanceof UpgradeException)) + if ((failure instanceof java.net.ConnectException) || (failure instanceof UpgradeException)) { // handle as-is handleException(failure); @@ -237,8 +507,8 @@ public class WebSocketUpgradeRequest extends HttpRequest implements CompleteList handleException(new UpgradeException(requestURI,responseStatusCode,responseLine,failure)); } } - - if(responseStatusCode != HttpStatus.SWITCHING_PROTOCOLS_101) + + if (responseStatusCode != HttpStatus.SWITCHING_PROTOCOLS_101) { // Failed to upgrade (other reason) handleException(new UpgradeException(requestURI,responseStatusCode,responseLine)); @@ -270,18 +540,6 @@ public class WebSocketUpgradeRequest extends HttpRequest implements CompleteList return fut; } - public WebSocketUpgradeRequest setExtensions(List<ExtensionConfig> extensions) - { - this.extensions = extensions; - return this; - } - - public WebSocketUpgradeRequest setSubProtocols(List<String> subprotocols) - { - this.subProtocols = subprotocols; - return this; - } - @Override public void upgrade(HttpResponse response, HttpConnectionOverHTTP oldConn) { @@ -291,6 +549,11 @@ public class WebSocketUpgradeRequest extends HttpRequest implements CompleteList 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); @@ -304,11 +567,7 @@ public class WebSocketUpgradeRequest extends HttpRequest implements CompleteList // We can upgrade EndPoint endp = oldConn.getEndPoint(); - WebSocketClientConnection connection = new WebSocketClientConnection( - endp, - wsClient.getExecutor(), - wsClient.getScheduler(), - localEndpoint.getPolicy(), + WebSocketClientConnection connection = new WebSocketClientConnection(endp,wsClient.getExecutor(),wsClient.getScheduler(),localEndpoint.getPolicy(), wsClient.getBufferPool()); URI requestURI = this.getURI(); @@ -321,7 +580,7 @@ public class WebSocketUpgradeRequest extends HttpRequest implements CompleteList ExtensionStack extensionStack = new ExtensionStack(getExtensionFactory()); List<ExtensionConfig> extensions = new ArrayList<>(); HttpField extField = response.getHeaders().getField(HttpHeader.SEC_WEBSOCKET_EXTENSIONS); - if(extField != null) + if (extField != null) { String[] extValues = extField.getValues(); if (extValues != null) @@ -354,7 +613,22 @@ public class WebSocketUpgradeRequest extends HttpRequest implements CompleteList 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(); + } } diff --git a/jetty-websocket/websocket-client/src/main/java/org/eclipse/jetty/websocket/client/io/UpgradeListener.java b/jetty-websocket/websocket-client/src/main/java/org/eclipse/jetty/websocket/client/io/UpgradeListener.java index e3dcc8b011..e5d7cb11f9 100644 --- a/jetty-websocket/websocket-client/src/main/java/org/eclipse/jetty/websocket/client/io/UpgradeListener.java +++ b/jetty-websocket/websocket-client/src/main/java/org/eclipse/jetty/websocket/client/io/UpgradeListener.java @@ -23,9 +23,7 @@ import org.eclipse.jetty.websocket.api.UpgradeResponse; /** * Listener for Handshake/Upgrade events. - * @deprecated no longer supported, no alternative available */ -@Deprecated public interface UpgradeListener { public void onHandshakeRequest(UpgradeRequest request); diff --git a/jetty-websocket/websocket-client/src/test/java/org/eclipse/jetty/websocket/client/JettyTrackingSocket.java b/jetty-websocket/websocket-client/src/test/java/org/eclipse/jetty/websocket/client/JettyTrackingSocket.java index 93e49d9c49..51a83a4be7 100644 --- a/jetty-websocket/websocket-client/src/test/java/org/eclipse/jetty/websocket/client/JettyTrackingSocket.java +++ b/jetty-websocket/websocket-client/src/test/java/org/eclipse/jetty/websocket/client/JettyTrackingSocket.java @@ -31,9 +31,9 @@ import java.util.concurrent.TimeoutException; import org.eclipse.jetty.toolchain.test.EventQueue; 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.UpgradeRequest; import org.eclipse.jetty.websocket.api.UpgradeResponse; +import org.eclipse.jetty.websocket.api.Session; import org.eclipse.jetty.websocket.api.WebSocketAdapter; import org.junit.Assert; diff --git a/jetty-websocket/websocket-client/src/test/java/org/eclipse/jetty/websocket/client/WebSocketClientTest.java b/jetty-websocket/websocket-client/src/test/java/org/eclipse/jetty/websocket/client/WebSocketClientTest.java index 9b1cb0e167..c4a26443b7 100644 --- a/jetty-websocket/websocket-client/src/test/java/org/eclipse/jetty/websocket/client/WebSocketClientTest.java +++ b/jetty-websocket/websocket-client/src/test/java/org/eclipse/jetty/websocket/client/WebSocketClientTest.java @@ -36,6 +36,7 @@ import org.eclipse.jetty.websocket.api.RemoteEndpoint; import org.eclipse.jetty.websocket.api.Session; import org.eclipse.jetty.websocket.api.UpgradeRequest; import org.eclipse.jetty.websocket.api.annotations.WebSocket; +import org.eclipse.jetty.websocket.common.UpgradeRequestAdapter; import org.eclipse.jetty.websocket.common.WebSocketSession; import org.eclipse.jetty.websocket.common.frames.TextFrame; import org.eclipse.jetty.websocket.common.io.FutureWriteCallback; diff --git a/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/UpgradeRequestAdapter.java b/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/UpgradeRequestAdapter.java new file mode 100644 index 0000000000..c9fdea3087 --- /dev/null +++ b/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/UpgradeRequestAdapter.java @@ -0,0 +1,397 @@ +// +// ======================================================================== +// Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd. +// ------------------------------------------------------------------------ +// All rights reserved. This program and the accompanying materials +// are made available under the terms of the Eclipse Public License v1.0 +// and Apache License v2.0 which accompanies this distribution. +// +// The Eclipse Public License is available at +// http://www.eclipse.org/legal/epl-v10.html +// +// The Apache License v2.0 is available at +// http://www.opensource.org/licenses/apache2.0.php +// +// You may elect to redistribute this code under either of these licenses. +// ======================================================================== +// + +package org.eclipse.jetty.websocket.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); + } +} diff --git a/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/UpgradeResponseAdapter.java b/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/UpgradeResponseAdapter.java new file mode 100644 index 0000000000..1043d706ed --- /dev/null +++ b/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/UpgradeResponseAdapter.java @@ -0,0 +1,227 @@ +// +// ======================================================================== +// Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd. +// ------------------------------------------------------------------------ +// All rights reserved. This program and the accompanying materials +// are made available under the terms of the Eclipse Public License v1.0 +// and Apache License v2.0 which accompanies this distribution. +// +// The Eclipse Public License is available at +// http://www.eclipse.org/legal/epl-v10.html +// +// The Apache License v2.0 is available at +// http://www.opensource.org/licenses/apache2.0.php +// +// You may elect to redistribute this code under either of these licenses. +// ======================================================================== +// + +package org.eclipse.jetty.websocket.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; + } +} diff --git a/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/WebSocketSession.java b/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/WebSocketSession.java index 5fec26d0b2..c92c58588c 100644 --- a/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/WebSocketSession.java +++ b/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/WebSocketSession.java @@ -40,12 +40,12 @@ import org.eclipse.jetty.util.thread.ThreadClassLoaderScope; import org.eclipse.jetty.websocket.api.BatchMode; import org.eclipse.jetty.websocket.api.CloseException; import org.eclipse.jetty.websocket.api.CloseStatus; +import org.eclipse.jetty.websocket.api.UpgradeResponse; import org.eclipse.jetty.websocket.api.RemoteEndpoint; import org.eclipse.jetty.websocket.api.Session; import org.eclipse.jetty.websocket.api.StatusCode; import org.eclipse.jetty.websocket.api.SuspendToken; import org.eclipse.jetty.websocket.api.UpgradeRequest; -import org.eclipse.jetty.websocket.api.UpgradeResponse; import org.eclipse.jetty.websocket.api.WebSocketBehavior; import org.eclipse.jetty.websocket.api.WebSocketException; import org.eclipse.jetty.websocket.api.WebSocketPolicy; @@ -468,10 +468,6 @@ public class WebSocketSession extends ContainerLifeCycle implements Session, Web LOG.ignore(t); } } - if(openFuture != null) - { - openFuture.complete(this); - } break; } } @@ -510,6 +506,11 @@ public class WebSocketSession extends ContainerLifeCycle implements Session, Web { LOG.debug("open -> {}",dump()); } + + if(openFuture != null) + { + openFuture.complete(this); + } } catch (CloseException ce) { diff --git a/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/RequestHeadersTest.java b/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/RequestHeadersTest.java index 760a5dc2c5..734a59a424 100644 --- a/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/RequestHeadersTest.java +++ b/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/RequestHeadersTest.java @@ -28,6 +28,7 @@ import java.util.concurrent.TimeUnit; import org.eclipse.jetty.websocket.api.UpgradeRequest; 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.server.helper.EchoSocket; import org.eclipse.jetty.websocket.servlet.ServletUpgradeRequest; diff --git a/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/WebSocketOverSSLTest.java b/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/WebSocketOverSSLTest.java index 4dcd36f0c4..21a9242317 100644 --- a/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/WebSocketOverSSLTest.java +++ b/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/WebSocketOverSSLTest.java @@ -19,6 +19,8 @@ package org.eclipse.jetty.websocket.server; import static org.hamcrest.Matchers.is; +import static org.hamcrest.Matchers.notNullValue; +import static org.junit.Assert.assertThat; import java.net.URI; import java.util.concurrent.Future; diff --git a/jetty-websocket/websocket-servlet/src/main/java/org/eclipse/jetty/websocket/servlet/ServletUpgradeRequest.java b/jetty-websocket/websocket-servlet/src/main/java/org/eclipse/jetty/websocket/servlet/ServletUpgradeRequest.java index 01c82c20e7..3622b07659 100644 --- a/jetty-websocket/websocket-servlet/src/main/java/org/eclipse/jetty/websocket/servlet/ServletUpgradeRequest.java +++ b/jetty-websocket/websocket-servlet/src/main/java/org/eclipse/jetty/websocket/servlet/ServletUpgradeRequest.java @@ -20,6 +20,7 @@ package org.eclipse.jetty.websocket.servlet; import java.net.HttpCookie; import java.net.InetSocketAddress; +import java.net.URI; import java.net.URISyntaxException; import java.security.Principal; import java.security.cert.X509Certificate; @@ -37,73 +38,124 @@ import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpSession; 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.util.WSURI; /** * 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 boolean secure; + private List<HttpCookie> cookies; + private Map<String, List<String>> parameterMap; + private List<String> subprotocols; 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); + } - // Parse protocols. - Enumeration<String> requestProtocols = request.getHeaders("Sec-WebSocket-Protocol"); - if (requestProtocols != null) - { - List<String> protocols = new ArrayList<>(2); - while (requestProtocols.hasMoreElements()) - { - String candidate = requestProtocols.nextElement(); - Collections.addAll(protocols, parseProtocols(candidate)); - } - setSubProtocols(protocols); - } + @Override + public void addExtensions(ExtensionConfig... configs) + { + throw new UnsupportedOperationException(CANNOT_MODIFY_SERVLET_REQUEST); + } - // Parse extensions. - Enumeration<String> e = request.getHeaders("Sec-WebSocket-Extensions"); - setExtensions(ExtensionConfig.parseEnum(e)); + @Override + public void addExtensions(String... configs) + { + throw new UnsupportedOperationException(CANNOT_MODIFY_SERVLET_REQUEST); + } - // Copy cookies. - Cookie[] requestCookies = request.getCookies(); - if (requestCookies != null) + @Override + public void clearHeaders() + { + throw new UnsupportedOperationException(CANNOT_MODIFY_SERVLET_REQUEST); + } + + public void complete() + { + request.complete(); + } + + public X509Certificate[] getCertificates() + { + return (X509Certificate[])request.getAttribute("javax.servlet.request.X509Certificate"); + } + + @Override + public List<HttpCookie> getCookies() + { + if(cookies == null) { - List<HttpCookie> cookies = new ArrayList<>(); - for (Cookie requestCookie : requestCookies) + Cookie[] requestCookies = request.getCookies(); + if (requestCookies != null) { - HttpCookie cookie = new HttpCookie(requestCookie.getName(), requestCookie.getValue()); - // No point handling domain/path/expires/secure/httponly on client request cookies - cookies.add(cookie); + 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); } + + return cookies; + } - setHeaders(request.getHeaders()); + @Override + public List<ExtensionConfig> getExtensions() + { + Enumeration<String> e = request.getHeaders("Sec-WebSocket-Extensions"); + return ExtensionConfig.parseEnum(e); + } - // Copy parameters. - Map<String, String[]> requestParams = request.getParameterMap(); - if (requestParams != null) + @Override + public String getHeader(String name) + { + return request.getHeader(name); + } + + @Override + public int getHeaderInt(String name) + { + String val = request.getHeader(name); + if (val == 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); + return -1; } + return Integer.parseInt(val); + } - setSession(request.getSession(false)); + @Override + public Map<String, List<String>> getHeaders() + { + return request.getHeaders(); + } - setHttpVersion(request.getProtocol()); - setMethod(request.getMethod()); + @Override + public List<String> getHeaders(String name) + { + return request.getHeaders().get(name); } - public X509Certificate[] getCertificates() + @Override + public String getHost() { - return (X509Certificate[])request.getAttribute("javax.servlet.request.X509Certificate"); + return requestURI.getHost(); } /** @@ -119,6 +171,12 @@ public class ServletUpgradeRequest extends UpgradeRequest return request; } + @Override + public String getHttpVersion() + { + return request.getProtocol(); + } + /** * Equivalent to {@link HttpServletRequest#getLocalAddr()} * @@ -130,6 +188,26 @@ public class ServletUpgradeRequest extends UpgradeRequest } /** + * 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()} * * @return the local host name @@ -161,26 +239,34 @@ public class ServletUpgradeRequest extends UpgradeRequest return new InetSocketAddress(getLocalAddress(), getLocalPort()); } - /** - * Equivalent to {@link HttpServletRequest#getLocale()} - * - * @return the preferred <code>Locale</code> for the client - */ - public Locale getLocale() + @Override + public String getMethod() { - return request.getLocale(); + return request.getMethod(); } - /** - * Equivalent to {@link HttpServletRequest#getLocales()} - * - * @return an Enumeration of preferred Locale objects - */ - public Enumeration<Locale> getLocales() + @Override + public String getOrigin() { - 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 * @deprecated use {@link #getUserPrincipal()} instead @@ -191,12 +277,21 @@ public class ServletUpgradeRequest extends UpgradeRequest return getUserPrincipal(); } - /** - * Equivalent to {@link HttpServletRequest#getUserPrincipal()} - */ - public Principal getUserPrincipal() + @Override + public String getProtocolVersion() { - 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()); } + 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() { return request.getAttributes(); } - + public Map<String, List<String>> getServletParameters() { return getParameterMap(); @@ -263,29 +379,61 @@ public class ServletUpgradeRequest extends UpgradeRequest 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(); } - public boolean isUserInRole(String role) + @Override + public boolean hasSubProtocol(String test) { - return request.isUserInRole(role); + for (String protocol : getSubProtocols()) + { + if (protocol.equalsIgnoreCase(test)) + { + return true; + } + } + return false; + } + + @Override + public boolean isOrigin(String test) + { + return test.equalsIgnoreCase(getOrigin()); } - public String getRequestPath() + @Override + public boolean isSecure() { - // 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; + return this.secure; + } + + public boolean isUserInRole(String role) + { + return request.isUserInRole(role); } private String[] parseProtocols(String protocol) @@ -298,8 +446,74 @@ public class ServletUpgradeRequest extends UpgradeRequest 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); } } diff --git a/jetty-websocket/websocket-servlet/src/main/java/org/eclipse/jetty/websocket/servlet/ServletUpgradeResponse.java b/jetty-websocket/websocket-servlet/src/main/java/org/eclipse/jetty/websocket/servlet/ServletUpgradeResponse.java index a028284dc0..c2fd478ed6 100644 --- a/jetty-websocket/websocket-servlet/src/main/java/org/eclipse/jetty/websocket/servlet/ServletUpgradeResponse.java +++ b/jetty-websocket/websocket-servlet/src/main/java/org/eclipse/jetty/websocket/servlet/ServletUpgradeResponse.java @@ -19,43 +19,110 @@ package org.eclipse.jetty.websocket.servlet; 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 javax.servlet.http.HttpServletResponse; import org.eclipse.jetty.websocket.api.UpgradeResponse; +import org.eclipse.jetty.websocket.api.WebSocketConstants; import org.eclipse.jetty.websocket.api.extensions.ExtensionConfig; /** * Servlet Specific UpgradeResponse implementation. */ -public class ServletUpgradeResponse extends UpgradeResponse +public class ServletUpgradeResponse implements UpgradeResponse { private HttpServletResponse response; private boolean extensionsNegotiated = 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) { this.response = response; + + for (String name : response.getHeaderNames()) + { + headers.put(name,new ArrayList<String>(response.getHeaders(name))); + } } @Override - public int getStatusCode() + public void addHeader(String name, String value) { - return response.getStatus(); + this.response.addHeader(name,value); } - public void setStatus(int status) + private void commitHeaders() { - response.setStatus(status); + // 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 + public int getStatusCode() + { + return response.getStatus(); } @Override 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() @@ -78,11 +145,17 @@ public class ServletUpgradeResponse extends UpgradeResponse return subprotocolNegotiated; } + @Override + public boolean isSuccess() + { + return success; + } + public void sendError(int statusCode, String message) throws IOException { setSuccess(false); commitHeaders(); - response.sendError(statusCode, message); + response.sendError(statusCode,message); response = null; } @@ -91,39 +164,56 @@ public class ServletUpgradeResponse extends UpgradeResponse { setSuccess(false); commitHeaders(); - response.sendError(HttpServletResponse.SC_FORBIDDEN, message); + response.sendError(HttpServletResponse.SC_FORBIDDEN,message); response = null; } @Override public void setAcceptedSubProtocol(String protocol) { - super.setAcceptedSubProtocol(protocol); + response.setHeader(WebSocketConstants.SEC_WEBSOCKET_PROTOCOL,protocol); subprotocolNegotiated = true; } @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; } - public void complete() + @Override + public void setHeader(String name, String value) { - commitHeaders(); - response = null; + List<String> values = new ArrayList<>(); + values.add(value); + headers.put(name,values); + response.setHeader(name,value); } - private void commitHeaders() + public void setStatus(int status) { - // 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); - } - } + 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; } } |