Reworking UpgradeRequest / UpgradeResponse APIs

+ 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.
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 3d28ecb..5e98bc6 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.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 79da9ec..38f013a 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 @@
 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;
+    /**
+     * 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);
 
-    protected UpgradeRequest()
-    {
-        /* anonymous, no requestURI, upgrade request */
-    }
+    /**
+     * 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);
 
-    public UpgradeRequest(String requestURI)
-    {
-        this(URI.create(requestURI));
-    }
+    /**
+     * Remove all headers from request.
+     * @deprecated (no longer supported, as this can undo the required upgrade request headers)
+     */
+    @Deprecated
+    void clearHeaders();
 
-    public UpgradeRequest(URI requestURI)
-    {
-        setRequestURI(requestURI);
-    }
+    /**
+     * Get the list of Cookies on the Upgrade request
+     *
+     * @return the list of Cookies
+     */
+    List<HttpCookie> getCookies();
 
-    public void addExtensions(ExtensionConfig... configs)
-    {
-        Collections.addAll(extensions, configs);
-    }
+    /**
+     * 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();
 
-    public void addExtensions(String... configs)
-    {
-        for (String config : configs)
-        {
-            extensions.add(ExtensionConfig.parse(config));
-        }
-    }
+    /**
+     * 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);
 
-    public void clearHeaders()
-    {
-        headers.clear();
-    }
+    /**
+     * 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);
 
-    public List<HttpCookie> getCookies()
-    {
-        return cookies;
-    }
+    /**
+     * Get the headers as a Map of keys to value lists.
+     *
+     * @return the headers
+     */
+    Map<String, List<String>> getHeaders();
 
-    public List<ExtensionConfig> getExtensions()
-    {
-        return extensions;
-    }
+    /**
+     * 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);
 
-    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();
-    }
+    /**
+     * The host of the Upgrade Request URI
+     * <p>
+     * Equivalent to {@link #getRequestURI()#getHost()}
+     *
+     * @return host of the request URI
+     */
+    String getHost();
 
-    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");
-    }
+    /**
+     * 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();
 
-    public Map<String, List<String>> getHeaders()
-    {
-        return headers;
-    }
+    /**
+     * 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();
 
-    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");
-    }
+    /**
+     * 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);
-        }
-    }
+    Principal getUserPrincipal();
 
     /**
-     * Set Sub Protocol request list.
-     * 
-     * @param protocols
-     *            the sub protocols desired
+     * Test if a specific sub-protocol is offered
+     *
+     * @param test the sub-protocol to test for
+     * @return true if sub-protocol exists on request
      */
-    public void setSubProtocols(String... protocols)
-    {
-        subProtocols.clear();
-        Collections.addAll(subProtocols, protocols);
-    }
-}
+    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 91df0d7..4f0b593 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;
-    }
+    List<ExtensionConfig> getExtensions();
 
-    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();
-    }
+    /**
+     * Get a header value
+     * 
+     * @param name the header name
+     * @return the value (null if header doesn't exist)
+     */
+    String getHeader(String name);
 
-    public Set<String> getHeaderNames()
-    {
-        return headers.keySet();
-    }
+    /**
+     * Get the header names
+     * 
+     * @return the set of header names
+     */
+    Set<String> getHeaderNames();
 
-    public Map<String, List<String>> getHeaders()
-    {
-        return headers;
-    }
+    /**
+     * Get the headers map
+     * 
+     * @return the map of headers
+     */
+    Map<String, List<String>> getHeaders();
 
-    public List<String> getHeaders(String name)
-    {
-        return headers.get(name);
-    }
+    /**
+     * 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);
 
-    public int getStatusCode()
-    {
-        return statusCode;
-    }
+    /**
+     * Get the HTTP Response Status Code
+     * 
+     * @return the status code
+     */
+    int getStatusCode();
 
-    public String getStatusReason()
-    {
-        return statusReason;
-    }
+    /**
+     * Get the HTTP Response Status Reason
+     * 
+     * @return the HTTP Response status reason
+     */
+    String getStatusReason();
 
-    public boolean isSuccess()
-    {
-        return success;
-    }
+    /**
+     * 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 @@
      * 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.
+     * the list of extensions to use.
      */
-    public void setExtensions(List<ExtensionConfig> extensions)
-    {
-        this.extensions.clear();
-        if (extensions != null)
-        {
-            this.extensions.addAll(extensions);
-        }
-    }
+    void setExtensions(List<ExtensionConfig> extensions);
 
-    public void setHeader(String name, String value)
-    {
-        List<String> values = new ArrayList<>();
-        values.add(value);
-        headers.put(name,values);
-    }
+    /**
+     * 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);
 
-    public void setStatusCode(int statusCode)
-    {
-        this.statusCode = statusCode;
-    }
+    /**
+     * Set the HTTP Response status code
+     * 
+     * @param statusCode the status code
+     */
+    void setStatusCode(int statusCode);
 
-    public void setStatusReason(String statusReason)
-    {
-        this.statusReason = statusReason;
-    }
+    /**
+     * 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);
 
-    public void setSuccess(boolean success)
-    {
-        this.success = success;
-    }
-}
+    /**
+     * 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 0000000..da81515
--- /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 d6abcb3..30c7dea 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.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 9ed2ab1..9103387 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 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 @@
             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 24c1302..2a23990 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 @@
      *            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
-    {
-        return connect(websocket,toUri,request);
-    }
-
     public Future<Session> connect(Object websocket, URI toUri, ClientUpgradeRequest request) throws IOException
     {
+        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
+    {
+        /* 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 @@
         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 c54be66..205e4b2 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 @@
 
 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.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 @@
 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 @@
      */
     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 @@
         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 @@
         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 @@
         {
             LOG.debug("onComplete() - {}",result);
         }
-        
+
         URI requestURI = result.getRequest().getURI();
         Response response = result.getResponse();
         int responseStatusCode = response.getStatus();
@@ -217,16 +488,15 @@
 
         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 @@
                 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 @@
         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 @@
             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 @@
         // 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 @@
         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 @@
 
         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 e3dcc8b..e5d7cb1 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 @@
 
 /**
  * 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 93e49d9..51a83a4 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 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 9b1cb0e..c4a2644 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.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 0000000..c9fdea3
--- /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 0000000..1043d70
--- /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 5fec26d..c92c585 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.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 @@
                         LOG.ignore(t);
                     }
                 }
-                if(openFuture != null)
-                {
-                    openFuture.complete(this);
-                }
                 break;
         }
     }
@@ -510,6 +506,11 @@
             {
                 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 760a5dc..734a59a 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 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 4dcd36f..21a9242 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 01c82c2..3622b07 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 @@
 
 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,68 +38,56 @@
 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)
-        {
-            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);
-        }
+    @Override
+    public void clearHeaders()
+    {
+        throw new UnsupportedOperationException(CANNOT_MODIFY_SERVLET_REQUEST);
+    }
 
-        setHeaders(request.getHeaders());
-
-        // Copy parameters.
-        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 void complete()
+    {
+        request.complete();
     }
 
     public X509Certificate[] getCertificates()
@@ -106,6 +95,69 @@
         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.
      * <p>
@@ -119,6 +171,12 @@
         return request;
     }
 
+    @Override
+    public String getHttpVersion()
+    {
+        return request.getProtocol();
+    }
+
     /**
      * Equivalent to {@link HttpServletRequest#getLocalAddr()}
      *
@@ -130,6 +188,26 @@
     }
 
     /**
+     * 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 @@
         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 @@
         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 @@
         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,14 +379,56 @@
         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)
@@ -278,16 +436,6 @@
         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)
     {
         if (protocol == null)
@@ -298,8 +446,74 @@
         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 a028284..c2fd478 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,26 +19,98 @@
 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 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
@@ -47,15 +119,10 @@
         return response.getStatus();
     }
 
-    public void setStatus(int status)
-    {
-        response.setStatus(status);
-    }
-
     @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 @@
         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 @@
     {
         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;
     }
 }