diff --git a/jetty-websocket/javax-websocket-server-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/server/MemoryUsageTest.java b/jetty-websocket/javax-websocket-server-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/server/MemoryUsageTest.java new file mode 100644 index 00000000000..29c5fee44c9 --- /dev/null +++ b/jetty-websocket/javax-websocket-server-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/server/MemoryUsageTest.java @@ -0,0 +1,130 @@ +// +// ======================================================================== +// Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd. +// ------------------------------------------------------------------------ +// All rights reserved. This program and the accompanying materials +// are made available under the terms of the Eclipse Public License v1.0 +// and Apache License v2.0 which accompanies this distribution. +// +// The Eclipse Public License is available at +// http://www.eclipse.org/legal/epl-v10.html +// +// The Apache License v2.0 is available at +// http://www.opensource.org/licenses/apache2.0.php +// +// You may elect to redistribute this code under either of these licenses. +// ======================================================================== +// + +package org.eclipse.jetty.websocket.jsr356.server; + +import java.lang.management.ManagementFactory; +import java.lang.management.MemoryMXBean; +import java.lang.management.MemoryUsage; +import java.net.URI; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.TimeUnit; +import javax.websocket.ContainerProvider; +import javax.websocket.Endpoint; +import javax.websocket.EndpointConfig; +import javax.websocket.MessageHandler; +import javax.websocket.Session; +import javax.websocket.WebSocketContainer; +import javax.websocket.server.ServerEndpointConfig; + +import org.eclipse.jetty.server.Server; +import org.eclipse.jetty.server.ServerConnector; +import org.eclipse.jetty.servlet.ServletContextHandler; +import org.eclipse.jetty.websocket.jsr356.server.deploy.WebSocketServerContainerInitializer; +import org.eclipse.jetty.websocket.jsr356.server.samples.echo.BasicEchoEndpoint; +import org.junit.After; +import org.junit.Assert; +import org.junit.Before; +import org.junit.Test; + +public class MemoryUsageTest +{ + private Server server; + private ServerConnector connector; + private WebSocketContainer client; + + @Before + public void prepare() throws Exception + { + server = new Server(); + connector = new ServerConnector(server); + server.addConnector(connector); + + ServletContextHandler context = new ServletContextHandler(server, "/", true, false); + ServerContainer container = WebSocketServerContainerInitializer.configureContext(context); + ServerEndpointConfig config = ServerEndpointConfig.Builder.create(BasicEchoEndpoint.class, "/").build(); + container.addEndpoint(config); + + server.start(); + + client = ContainerProvider.getWebSocketContainer(); + server.addBean(client, true); + } + + @After + public void dispose() throws Exception + { + server.stop(); + } + + @Test + public void testMemoryUsage() throws Exception + { + int sessionCount = 1000; + Session[] sessions = new Session[sessionCount]; + + MemoryMXBean memoryMXBean = ManagementFactory.getMemoryMXBean(); + + System.gc(); + MemoryUsage heapBefore = memoryMXBean.getHeapMemoryUsage(); + MemoryUsage nonHeapBefore = memoryMXBean.getNonHeapMemoryUsage(); + + URI uri = URI.create("ws://localhost:" + connector.getLocalPort()); + final CountDownLatch latch = new CountDownLatch(sessionCount); + for (int i = 0; i < sessionCount; ++i) + { + sessions[i] = client.connectToServer(new EndpointAdapter() + { + @Override + public void onMessage(String message) + { + latch.countDown(); + } + }, uri); + } + for (int i = 0; i < sessionCount; ++i) + { + sessions[i].getBasicRemote().sendText("OK"); + } + latch.await(5 * sessionCount, TimeUnit.MILLISECONDS); + + System.gc(); + MemoryUsage heapAfter = memoryMXBean.getHeapMemoryUsage(); + MemoryUsage nonHeapAfter = memoryMXBean.getNonHeapMemoryUsage(); + + long heapUsed = heapAfter.getUsed() - heapBefore.getUsed(); + long nonHeapUsed = nonHeapAfter.getUsed() - nonHeapBefore.getUsed(); + +// System.out.println("heapUsed = " + heapUsed); +// System.out.println("nonHeapUsed = " + nonHeapUsed); +// new CountDownLatch(1).await(); + + // Assume no more than 25 KiB per session pair (client and server). + long expected = 25 * 1024 * sessionCount; + Assert.assertTrue(heapUsed < expected); + } + + private static abstract class EndpointAdapter extends Endpoint implements MessageHandler.Whole + { + @Override + public void onOpen(Session session, EndpointConfig config) + { + session.addMessageHandler(this); + } + } +} 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 c76dd5419ac..81059efbc39 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 @@ -34,16 +34,16 @@ import org.eclipse.jetty.websocket.api.util.QuoteUtil; public class UpgradeRequest { private URI requestURI; - private List subProtocols = new ArrayList<>(); - private List extensions = new ArrayList<>(); - private List cookies = new ArrayList<>(); + private List subProtocols = new ArrayList<>(1); + private List extensions = new ArrayList<>(1); + private List cookies = new ArrayList<>(1); private Map> headers = new TreeMap<>(String.CASE_INSENSITIVE_ORDER); - private Map> parameters = new HashMap<>(); + private Map> parameters = new HashMap<>(1); private Object session; private String httpVersion; private String method; private String host; - private boolean secure = false; + private boolean secure; protected UpgradeRequest() { @@ -57,16 +57,12 @@ public class UpgradeRequest public UpgradeRequest(URI requestURI) { - this(); setRequestURI(requestURI); } public void addExtensions(ExtensionConfig... configs) { - for (ExtensionConfig config : configs) - { - extensions.add(config); - } + Collections.addAll(extensions, configs); } public void addExtensions(String... configs) @@ -357,10 +353,7 @@ public class UpgradeRequest */ public void setSubProtocols(String... protocols) { - this.subProtocols.clear(); - for (String protocol : protocols) - { - this.subProtocols.add(protocol); - } + subProtocols.clear(); + Collections.addAll(subProtocols, protocols); } } diff --git a/jetty-websocket/websocket-server/src/main/java/org/eclipse/jetty/websocket/server/HandshakeRFC6455.java b/jetty-websocket/websocket-server/src/main/java/org/eclipse/jetty/websocket/server/HandshakeRFC6455.java index 189e2843776..7f5730099ae 100644 --- a/jetty-websocket/websocket-server/src/main/java/org/eclipse/jetty/websocket/server/HandshakeRFC6455.java +++ b/jetty-websocket/websocket-server/src/main/java/org/eclipse/jetty/websocket/server/HandshakeRFC6455.java @@ -58,6 +58,8 @@ public class HandshakeRFC6455 implements WebSocketHandshake } } + request.complete(); + response.setStatus(HttpServletResponse.SC_SWITCHING_PROTOCOLS); response.complete(); } diff --git a/jetty-websocket/websocket-servlet/src/main/java/org/eclipse/jetty/websocket/servlet/PostUpgradedHttpServletRequest.java b/jetty-websocket/websocket-servlet/src/main/java/org/eclipse/jetty/websocket/servlet/PostUpgradedHttpServletRequest.java deleted file mode 100644 index 5caca7d508b..00000000000 --- a/jetty-websocket/websocket-servlet/src/main/java/org/eclipse/jetty/websocket/servlet/PostUpgradedHttpServletRequest.java +++ /dev/null @@ -1,190 +0,0 @@ -// -// ======================================================================== -// Copyright (c) 1995-2014 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.servlet; - -import java.io.BufferedReader; -import java.io.IOException; -import java.io.UnsupportedEncodingException; -import java.util.Collection; - -import javax.servlet.AsyncContext; -import javax.servlet.RequestDispatcher; -import javax.servlet.ServletException; -import javax.servlet.ServletInputStream; -import javax.servlet.ServletRequest; -import javax.servlet.ServletResponse; -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletRequestWrapper; -import javax.servlet.http.HttpServletResponse; -import javax.servlet.http.HttpUpgradeHandler; -import javax.servlet.http.Part; - -public class PostUpgradedHttpServletRequest extends HttpServletRequestWrapper -{ - private static final String UNSUPPORTED_WITH_WEBSOCKET_UPGRADE = "Feature unsupported with a Upgraded to WebSocket HttpServletRequest"; - - public PostUpgradedHttpServletRequest(HttpServletRequest request) - { - super(request); - } - - @Override - public boolean authenticate(HttpServletResponse response) throws IOException, ServletException - { - throw new UnsupportedOperationException(UNSUPPORTED_WITH_WEBSOCKET_UPGRADE); - } - - @Override - public String changeSessionId() - { - throw new UnsupportedOperationException(UNSUPPORTED_WITH_WEBSOCKET_UPGRADE); - } - - @Override - public AsyncContext getAsyncContext() - { - throw new UnsupportedOperationException(UNSUPPORTED_WITH_WEBSOCKET_UPGRADE); - } - - @Override - public String getCharacterEncoding() - { - throw new UnsupportedOperationException(UNSUPPORTED_WITH_WEBSOCKET_UPGRADE); - } - - @Override - public int getContentLength() - { - throw new UnsupportedOperationException(UNSUPPORTED_WITH_WEBSOCKET_UPGRADE); - } - - @Override - public long getContentLengthLong() - { - throw new UnsupportedOperationException(UNSUPPORTED_WITH_WEBSOCKET_UPGRADE); - } - - @Override - public String getContentType() - { - throw new UnsupportedOperationException(UNSUPPORTED_WITH_WEBSOCKET_UPGRADE); - } - - @Override - public ServletInputStream getInputStream() throws IOException - { - throw new UnsupportedOperationException(UNSUPPORTED_WITH_WEBSOCKET_UPGRADE); - } - - @Override - public Part getPart(String name) throws IOException, ServletException - { - throw new UnsupportedOperationException(UNSUPPORTED_WITH_WEBSOCKET_UPGRADE); - } - - @Override - public Collection getParts() throws IOException, ServletException - { - throw new UnsupportedOperationException(UNSUPPORTED_WITH_WEBSOCKET_UPGRADE); - } - - @Override - public BufferedReader getReader() throws IOException - { - throw new UnsupportedOperationException(UNSUPPORTED_WITH_WEBSOCKET_UPGRADE); - } - - @Override - public ServletRequest getRequest() - { - throw new UnsupportedOperationException(UNSUPPORTED_WITH_WEBSOCKET_UPGRADE); - } - - @Override - public RequestDispatcher getRequestDispatcher(String path) - { - throw new UnsupportedOperationException(UNSUPPORTED_WITH_WEBSOCKET_UPGRADE); - } - - @Override - public boolean isAsyncStarted() - { - return false; - } - - @Override - public boolean isAsyncSupported() - { - return false; - } - - @Override - public boolean isWrapperFor(Class wrappedType) - { - throw new UnsupportedOperationException(UNSUPPORTED_WITH_WEBSOCKET_UPGRADE); - } - - @Override - public boolean isWrapperFor(ServletRequest wrapped) - { - throw new UnsupportedOperationException(UNSUPPORTED_WITH_WEBSOCKET_UPGRADE); - } - - @Override - public void login(String username, String password) throws ServletException - { - throw new UnsupportedOperationException(UNSUPPORTED_WITH_WEBSOCKET_UPGRADE); - } - - @Override - public void logout() throws ServletException - { - throw new UnsupportedOperationException(UNSUPPORTED_WITH_WEBSOCKET_UPGRADE); - } - - @Override - public void setCharacterEncoding(String enc) throws UnsupportedEncodingException - { - throw new UnsupportedOperationException(UNSUPPORTED_WITH_WEBSOCKET_UPGRADE); - } - - @Override - public void setRequest(ServletRequest request) - { - throw new UnsupportedOperationException(UNSUPPORTED_WITH_WEBSOCKET_UPGRADE); - } - - @Override - public AsyncContext startAsync() throws IllegalStateException - { - throw new UnsupportedOperationException(UNSUPPORTED_WITH_WEBSOCKET_UPGRADE); - } - - @Override - public AsyncContext startAsync(ServletRequest servletRequest, ServletResponse servletResponse) throws IllegalStateException - { - throw new UnsupportedOperationException(UNSUPPORTED_WITH_WEBSOCKET_UPGRADE); - } - - @Override - public T upgrade(Class handlerClass) throws IOException, ServletException - { - throw new UnsupportedOperationException(UNSUPPORTED_WITH_WEBSOCKET_UPGRADE); - } -} 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 986239e712b..35a7832b666 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 @@ -31,7 +31,6 @@ import java.util.HashMap; import java.util.List; import java.util.Locale; import java.util.Map; - import javax.servlet.http.Cookie; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpSession; @@ -45,148 +44,140 @@ import org.eclipse.jetty.websocket.api.util.WSURI; */ public class ServletUpgradeRequest extends UpgradeRequest { - private final HttpServletRequest req; + private final UpgradeHttpServletRequest request; - public ServletUpgradeRequest(HttpServletRequest request) throws URISyntaxException + public ServletUpgradeRequest(HttpServletRequest httpRequest) throws URISyntaxException { - super(WSURI.toWebsocket(request.getRequestURL(),request.getQueryString())); - this.req = new PostUpgradedHttpServletRequest(request); + super(WSURI.toWebsocket(httpRequest.getRequestURL(), httpRequest.getQueryString())); + this.request = new UpgradeHttpServletRequest(httpRequest); - // Copy Request Line Details - setMethod(request.getMethod()); - setHttpVersion(request.getProtocol()); - - // Copy parameters - Map> pmap = new HashMap<>(); - if (request.getParameterMap() != null) + // Parse protocols. + Enumeration requestProtocols = request.getHeaders("Sec-WebSocket-Protocol"); + if (requestProtocols != null) { - for (Map.Entry entry : request.getParameterMap().entrySet()) + List protocols = new ArrayList<>(2); + while (requestProtocols.hasMoreElements()) { - pmap.put(entry.getKey(),Arrays.asList(entry.getValue())); + String candidate = requestProtocols.nextElement(); + Collections.addAll(protocols, parseProtocols(candidate)); } - } - super.setParameterMap(pmap); - - // Copy Cookies - Cookie rcookies[] = request.getCookies(); - if (rcookies != null) - { - List cookies = new ArrayList<>(); - for (Cookie rcookie : rcookies) - { - HttpCookie hcookie = new HttpCookie(rcookie.getName(),rcookie.getValue()); - // no point handling domain/path/expires/secure/httponly on client request cookies - cookies.add(hcookie); - } - super.setCookies(cookies); + setSubProtocols(protocols); } - // Copy Headers - Enumeration headerNames = request.getHeaderNames(); - while (headerNames.hasMoreElements()) - { - String name = headerNames.nextElement(); - List values = Collections.list(request.getHeaders(name)); - setHeader(name,values); - } - - // Parse Sub Protocols - Enumeration protocols = request.getHeaders("Sec-WebSocket-Protocol"); - List subProtocols = new ArrayList<>(); - String protocol = null; - while ((protocol == null) && (protocols != null) && protocols.hasMoreElements()) - { - String candidate = protocols.nextElement(); - for (String p : parseProtocols(candidate)) - { - subProtocols.add(p); - } - } - setSubProtocols(subProtocols); - - // Parse Extension Configurations + // Parse extensions. Enumeration e = request.getHeaders("Sec-WebSocket-Extensions"); setExtensions(ExtensionConfig.parseEnum(e)); + + // Copy cookies. + Cookie[] requestCookies = request.getCookies(); + if (requestCookies != null) + { + List cookies = new ArrayList<>(); + for (Cookie requestCookie : requestCookies) + { + HttpCookie cookie = new HttpCookie(requestCookie.getName(), requestCookie.getValue()); + // No point handling domain/path/expires/secure/httponly on client request cookies + cookies.add(cookie); + } + setCookies(cookies); + } + + setHeaders(request.getHeaders()); + + // Copy parameters. + Map requestParams = request.getParameterMap(); + if (requestParams != null) + { + Map> params = new HashMap<>(requestParams.size()); + for (Map.Entry entry : requestParams.entrySet()) + params.put(entry.getKey(), Arrays.asList(entry.getValue())); + setParameterMap(params); + } + + setSession(request.getSession(false)); + + setHttpVersion(request.getProtocol()); + setMethod(request.getMethod()); } public X509Certificate[] getCertificates() { - return (X509Certificate[])req.getAttribute("javax.servlet.request.X509Certificate"); + return (X509Certificate[])request.getAttribute("javax.servlet.request.X509Certificate"); } - + /** * Return the underlying HttpServletRequest that existed at Upgrade time. - *

+ *

* Note: many features of the HttpServletRequest are invalid when upgraded, * especially ones that deal with body content, streams, readers, and responses. - * + * * @return a limited version of the underlying HttpServletRequest */ public HttpServletRequest getHttpServletRequest() { - return req; + return request; } /** * Equivalent to {@link HttpServletRequest#getLocalAddr()} - * + * * @return the local address */ public String getLocalAddress() { - return req.getLocalAddr(); + return request.getLocalAddr(); } /** * Equivalent to {@link HttpServletRequest#getLocalName()} - * + * * @return the local host name */ public String getLocalHostName() { - return req.getLocalName(); + return request.getLocalName(); } /** * Equivalent to {@link HttpServletRequest#getLocalPort()} - * + * * @return the local port */ public int getLocalPort() { - return req.getLocalPort(); - } - - /** - * Equivalent to {@link HttpServletRequest#getLocale()} - * - * @return the preferred Locale for the client - */ - public Locale getLocale() - { - return req.getLocale(); - } - - /** - * Equivalent to {@link HttpServletRequest#getLocales()} - * - * @return an Enumeration of preferred Locale objects - */ - public Enumeration getLocales() - { - return req.getLocales(); + return request.getLocalPort(); } /** * Return a {@link InetSocketAddress} for the local socket. - *

+ *

* Warning: this can cause a DNS lookup - * + * * @return the local socket address */ public InetSocketAddress getLocalSocketAddress() { - return new InetSocketAddress(req.getLocalAddr(),req.getLocalPort()); + return new InetSocketAddress(getLocalAddress(), getLocalPort()); + } + + /** + * Equivalent to {@link HttpServletRequest#getLocale()} + * + * @return the preferred Locale for the client + */ + public Locale getLocale() + { + return request.getLocale(); + } + + /** + * Equivalent to {@link HttpServletRequest#getLocales()} + * + * @return an Enumeration of preferred Locale objects + */ + public Enumeration getLocales() + { + return request.getLocales(); } /** @@ -195,136 +186,118 @@ public class ServletUpgradeRequest extends UpgradeRequest @Deprecated public Principal getPrincipal() { - return req.getUserPrincipal(); + return getUserPrincipal(); } - + /** * Equivalent to {@link HttpServletRequest#getUserPrincipal()} */ public Principal getUserPrincipal() { - return req.getUserPrincipal(); + return request.getUserPrincipal(); } /** * Equivalent to {@link HttpServletRequest#getRemoteAddr()} - * + * * @return the remote address */ public String getRemoteAddress() { - return req.getRemoteAddr(); + return request.getRemoteAddr(); } /** * Equivalent to {@link HttpServletRequest#getRemoteHost()} - * + * * @return the remote host name */ public String getRemoteHostName() { - return req.getRemoteHost(); + return request.getRemoteHost(); } /** * Equivalent to {@link HttpServletRequest#getRemotePort()} - * + * * @return the remote port */ public int getRemotePort() { - return req.getRemotePort(); + return request.getRemotePort(); } /** * Return a {@link InetSocketAddress} for the remote socket. - *

+ *

* Warning: this can cause a DNS lookup - * + * * @return the remote socket address */ public InetSocketAddress getRemoteSocketAddress() { - return new InetSocketAddress(req.getRemoteAddr(),req.getRemotePort()); + return new InetSocketAddress(getRemoteAddress(), getRemotePort()); } public Map getServletAttributes() { - Map attributes = new HashMap(); - - for (String name : Collections.list(req.getAttributeNames())) - { - attributes.put(name,req.getAttribute(name)); - } - - return attributes; + return request.getAttributes(); } public Map> getServletParameters() { - Map> parameters = new HashMap>(); - - for (String name : Collections.list(req.getParameterNames())) - { - parameters.put(name,Collections.unmodifiableList(Arrays.asList(req.getParameterValues(name)))); - } - - return parameters; + return getParameterMap(); } /** * Return the HttpSession if it exists. - *

- * Note: this is equivalent to {@link HttpServletRequest#getSession()} and will not create a new HttpSession. + *

+ * Note: this is equivalent to {@link HttpServletRequest#getSession(boolean)} + * and will not create a new HttpSession. */ @Override public HttpSession getSession() { - return this.req.getSession(false); + return request.getSession(false); } - protected String[] parseProtocols(String protocol) + public void setServletAttribute(String name, Object value) { - if (protocol == null) - { - return new String[] {}; - } - protocol = protocol.trim(); - if ((protocol == null) || (protocol.length() == 0)) - { - return new String[] {}; - } - String[] passed = protocol.split("\\s*,\\s*"); - String[] protocols = new String[passed.length]; - System.arraycopy(passed,0,protocols,0,passed.length); - return protocols; - } - - public void setServletAttribute(String name, Object o) - { - this.req.setAttribute(name,o); + request.setAttribute(name, value); } public Object getServletAttribute(String name) { - return req.getAttribute(name); + return request.getAttribute(name); } public boolean isUserInRole(String role) { - return req.isUserInRole(role); + return request.isUserInRole(role); } public String getRequestPath() { - // Since this can be called from a filter, we need to be smart about determining the target request path - String contextPath = req.getContextPath(); - String requestPath = req.getRequestURI(); + // 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) + return new String[0]; + protocol = protocol.trim(); + if (protocol.length() == 0) + return new String[0]; + return protocol.split("\\s*,\\s*"); + } + + public void complete() + { + request.complete(); + } } 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 450376d5c12..91043660bfc 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 @@ -75,16 +75,18 @@ public class ServletUpgradeResponse extends UpgradeResponse public void sendError(int statusCode, String message) throws IOException { setSuccess(false); - complete(); + commitHeaders(); response.sendError(statusCode, message); + response = null; } @Override public void sendForbidden(String message) throws IOException { setSuccess(false); - complete(); + commitHeaders(); response.sendError(HttpServletResponse.SC_FORBIDDEN, message); + response = null; } @Override @@ -102,14 +104,20 @@ public class ServletUpgradeResponse extends UpgradeResponse } public void complete() + { + commitHeaders(); + response = null; + } + + private void commitHeaders() { // Transfer all headers to the real HTTP response - for (Map.Entry> entry : getHeaders().entrySet()) - { - for (String value : entry.getValue()) - { - response.addHeader(entry.getKey(), value); - } - } + for (Map.Entry> entry : getHeaders().entrySet()) + { + for (String value : entry.getValue()) + { + response.addHeader(entry.getKey(), value); + } + } } } diff --git a/jetty-websocket/websocket-servlet/src/main/java/org/eclipse/jetty/websocket/servlet/UpgradeHttpServletRequest.java b/jetty-websocket/websocket-servlet/src/main/java/org/eclipse/jetty/websocket/servlet/UpgradeHttpServletRequest.java new file mode 100644 index 00000000000..cc672d8f2b6 --- /dev/null +++ b/jetty-websocket/websocket-servlet/src/main/java/org/eclipse/jetty/websocket/servlet/UpgradeHttpServletRequest.java @@ -0,0 +1,589 @@ +// +// ======================================================================== +// Copyright (c) 1995-2014 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.servlet; + +import java.io.BufferedReader; +import java.io.IOException; +import java.io.UnsupportedEncodingException; +import java.net.InetSocketAddress; +import java.security.Principal; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.Enumeration; +import java.util.HashMap; +import java.util.List; +import java.util.Locale; +import java.util.Map; +import javax.servlet.AsyncContext; +import javax.servlet.DispatcherType; +import javax.servlet.RequestDispatcher; +import javax.servlet.ServletContext; +import javax.servlet.ServletException; +import javax.servlet.ServletInputStream; +import javax.servlet.ServletRequest; +import javax.servlet.ServletResponse; +import javax.servlet.http.Cookie; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import javax.servlet.http.HttpSession; +import javax.servlet.http.HttpUpgradeHandler; +import javax.servlet.http.Part; + +public class UpgradeHttpServletRequest implements HttpServletRequest +{ + private static final String UNSUPPORTED_WITH_WEBSOCKET_UPGRADE = "Feature unsupported with a Upgraded to WebSocket HttpServletRequest"; + + private HttpServletRequest request; + private final ServletContext context; + private final DispatcherType dispatcher; + private final String method; + private final String protocol; + private final String scheme; + private final boolean secure; + private final String requestURI; + private final StringBuffer requestURL; + private final String pathInfo; + private final String pathTranslated; + private final String servletPath; + private final String query; + private final String authType; + private final Cookie[] cookies; + private final String remoteUser; + private final Principal principal; + + private final Map> headers = new HashMap<>(8); + private final Map parameters = new HashMap<>(2); + private final Map attributes = new HashMap<>(2); + private final List locales = new ArrayList<>(2); + + private HttpSession session; + + private final InetSocketAddress localAddress; + private final String localName; + private final InetSocketAddress remoteAddress; + private final String remoteName; + private final InetSocketAddress serverAddress; + + public UpgradeHttpServletRequest(HttpServletRequest httpRequest) + { + // The original request object must be held temporarily for the duration of the handshake + // in order to be able to implement methods such as isUserInRole() and setAttribute(). + request = httpRequest; + context = httpRequest.getServletContext(); + dispatcher = httpRequest.getDispatcherType(); + + method = httpRequest.getMethod(); + protocol = httpRequest.getProtocol(); + scheme = httpRequest.getScheme(); + secure = httpRequest.isSecure(); + requestURI = httpRequest.getRequestURI(); + requestURL = httpRequest.getRequestURL(); + pathInfo = httpRequest.getPathInfo(); + pathTranslated = httpRequest.getPathTranslated(); + servletPath = httpRequest.getServletPath(); + query = httpRequest.getQueryString(); + authType = httpRequest.getAuthType(); + cookies = request.getCookies(); + + remoteUser = httpRequest.getRemoteUser(); + principal = httpRequest.getUserPrincipal(); + + Enumeration headerNames = httpRequest.getHeaderNames(); + while (headerNames.hasMoreElements()) + { + String name = headerNames.nextElement(); + headers.put(name, Collections.list(httpRequest.getHeaders(name))); + } + + parameters.putAll(request.getParameterMap()); + + Enumeration attributeNames = httpRequest.getAttributeNames(); + while (attributeNames.hasMoreElements()) + { + String name = attributeNames.nextElement(); + attributes.put(name, httpRequest.getAttribute(name)); + } + + localAddress = InetSocketAddress.createUnresolved(httpRequest.getLocalAddr(), httpRequest.getLocalPort()); + localName = httpRequest.getLocalName(); + remoteAddress = InetSocketAddress.createUnresolved(httpRequest.getRemoteAddr(), httpRequest.getRemotePort()); + remoteName = httpRequest.getRemoteHost(); + serverAddress = InetSocketAddress.createUnresolved(httpRequest.getServerName(), httpRequest.getServerPort()); + } + + public HttpServletRequest getHttpServletRequest() + { + return request; + } + + @Override + public String getAuthType() + { + return authType; + } + + @Override + public Cookie[] getCookies() + { + return cookies; + } + + @Override + public String getHeader(String name) + { + List values = headers.get(name); + if (values == null || values.isEmpty()) + return null; + return values.get(0); + } + + @Override + public Enumeration getHeaders(String name) + { + List values = headers.get(name); + if (values == null) + return Collections.emptyEnumeration(); + return Collections.enumeration(values); + } + + @Override + public Enumeration getHeaderNames() + { + return Collections.enumeration(headers.keySet()); + } + + public Map> getHeaders() + { + return Collections.unmodifiableMap(headers); + } + + @Override + public long getDateHeader(String name) + { + // TODO + throw new UnsupportedOperationException(UNSUPPORTED_WITH_WEBSOCKET_UPGRADE); + } + + @Override + public int getIntHeader(String name) + { + // TODO + throw new UnsupportedOperationException(UNSUPPORTED_WITH_WEBSOCKET_UPGRADE); + } + + @Override + public String getMethod() + { + return method; + } + + @Override + public String getPathInfo() + { + return pathInfo; + } + + @Override + public String getPathTranslated() + { + return pathTranslated; + } + + @Override + public String getContextPath() + { + return context.getContextPath(); + } + + @Override + public String getQueryString() + { + return query; + } + + @Override + public String getRemoteUser() + { + return remoteUser; + } + + @Override + public boolean isUserInRole(String role) + { + HttpServletRequest request = getHttpServletRequest(); + if (request != null) + return request.isUserInRole(role); + throw new UnsupportedOperationException(UNSUPPORTED_WITH_WEBSOCKET_UPGRADE); + } + + @Override + public Principal getUserPrincipal() + { + return principal; + } + + @Override + public String getRequestURI() + { + return requestURI; + } + + @Override + public StringBuffer getRequestURL() + { + return requestURL; + } + + @Override + public String getServletPath() + { + return servletPath; + } + + @Override + public HttpSession getSession(boolean create) + { + HttpServletRequest request = getHttpServletRequest(); + if (request != null) + return session = request.getSession(create); + return session; + } + + @Override + public HttpSession getSession() + { + return getSession(true); + } + + @Override + public String getRequestedSessionId() + { + throw new UnsupportedOperationException(UNSUPPORTED_WITH_WEBSOCKET_UPGRADE); + } + + @Override + public boolean isRequestedSessionIdValid() + { + throw new UnsupportedOperationException(UNSUPPORTED_WITH_WEBSOCKET_UPGRADE); + } + + @Override + public boolean isRequestedSessionIdFromCookie() + { + throw new UnsupportedOperationException(UNSUPPORTED_WITH_WEBSOCKET_UPGRADE); + } + + @Override + public boolean isRequestedSessionIdFromURL() + { + throw new UnsupportedOperationException(UNSUPPORTED_WITH_WEBSOCKET_UPGRADE); + } + + @Override + public boolean isRequestedSessionIdFromUrl() + { + throw new UnsupportedOperationException(UNSUPPORTED_WITH_WEBSOCKET_UPGRADE); + } + + @Override + public Object getAttribute(String name) + { + return attributes.get(name); + } + + @Override + public Enumeration getAttributeNames() + { + return Collections.enumeration(attributes.keySet()); + } + + public Map getAttributes() + { + return Collections.unmodifiableMap(attributes); + } + + @Override + public String getParameter(String name) + { + String[] values = parameters.get(name); + if (values == null || values.length == 0) + return null; + return values[0]; + } + + @Override + public Enumeration getParameterNames() + { + return Collections.enumeration(parameters.keySet()); + } + + @Override + public String[] getParameterValues(String name) + { + return parameters.get(name); + } + + @Override + public Map getParameterMap() + { + return parameters; + } + + @Override + public String getProtocol() + { + return protocol; + } + + @Override + public String getScheme() + { + return scheme; + } + + @Override + public String getServerName() + { + return serverAddress.getHostString(); + } + + @Override + public int getServerPort() + { + return serverAddress.getPort(); + } + + @Override + public String getRemoteAddr() + { + return remoteAddress.getHostString(); + } + + @Override + public int getRemotePort() + { + return remoteAddress.getPort(); + } + + @Override + public String getRemoteHost() + { + return remoteName; + } + + @Override + public void setAttribute(String name, Object value) + { + HttpServletRequest request = getHttpServletRequest(); + if (request != null) + request.setAttribute(name, value); + attributes.put(name, value); + } + + @Override + public void removeAttribute(String name) + { + HttpServletRequest request = getHttpServletRequest(); + if (request != null) + request.removeAttribute(name); + attributes.remove(name); + } + + @Override + public Locale getLocale() + { + if (locales.isEmpty()) + return Locale.getDefault(); + return locales.get(0); + } + + @Override + public Enumeration getLocales() + { + return Collections.enumeration(locales); + } + + @Override + public boolean isSecure() + { + return secure; + } + + @Override + public String getRealPath(String path) + { + return context.getRealPath(path); + } + + @Override + public String getLocalName() + { + return localName; + } + + @Override + public String getLocalAddr() + { + return localAddress.getHostString(); + } + + @Override + public int getLocalPort() + { + return localAddress.getPort(); + } + + @Override + public ServletContext getServletContext() + { + return context; + } + + @Override + public DispatcherType getDispatcherType() + { + return dispatcher; + } + + @Override + public boolean authenticate(HttpServletResponse response) throws IOException, ServletException + { + throw new UnsupportedOperationException(UNSUPPORTED_WITH_WEBSOCKET_UPGRADE); + } + + @Override + public String changeSessionId() + { + throw new UnsupportedOperationException(UNSUPPORTED_WITH_WEBSOCKET_UPGRADE); + } + + @Override + public AsyncContext getAsyncContext() + { + throw new UnsupportedOperationException(UNSUPPORTED_WITH_WEBSOCKET_UPGRADE); + } + + @Override + public String getCharacterEncoding() + { + throw new UnsupportedOperationException(UNSUPPORTED_WITH_WEBSOCKET_UPGRADE); + } + + @Override + public int getContentLength() + { + throw new UnsupportedOperationException(UNSUPPORTED_WITH_WEBSOCKET_UPGRADE); + } + + @Override + public long getContentLengthLong() + { + throw new UnsupportedOperationException(UNSUPPORTED_WITH_WEBSOCKET_UPGRADE); + } + + @Override + public String getContentType() + { + throw new UnsupportedOperationException(UNSUPPORTED_WITH_WEBSOCKET_UPGRADE); + } + + @Override + public ServletInputStream getInputStream() throws IOException + { + throw new UnsupportedOperationException(UNSUPPORTED_WITH_WEBSOCKET_UPGRADE); + } + + @Override + public Part getPart(String name) throws IOException, ServletException + { + throw new UnsupportedOperationException(UNSUPPORTED_WITH_WEBSOCKET_UPGRADE); + } + + @Override + public Collection getParts() throws IOException, ServletException + { + throw new UnsupportedOperationException(UNSUPPORTED_WITH_WEBSOCKET_UPGRADE); + } + + @Override + public BufferedReader getReader() throws IOException + { + throw new UnsupportedOperationException(UNSUPPORTED_WITH_WEBSOCKET_UPGRADE); + } + + @Override + public RequestDispatcher getRequestDispatcher(String path) + { + throw new UnsupportedOperationException(UNSUPPORTED_WITH_WEBSOCKET_UPGRADE); + } + + @Override + public boolean isAsyncStarted() + { + return false; + } + + @Override + public boolean isAsyncSupported() + { + return false; + } + + @Override + public void login(String username, String password) throws ServletException + { + throw new UnsupportedOperationException(UNSUPPORTED_WITH_WEBSOCKET_UPGRADE); + } + + @Override + public void logout() throws ServletException + { + throw new UnsupportedOperationException(UNSUPPORTED_WITH_WEBSOCKET_UPGRADE); + } + + @Override + public void setCharacterEncoding(String enc) throws UnsupportedEncodingException + { + throw new UnsupportedOperationException(UNSUPPORTED_WITH_WEBSOCKET_UPGRADE); + } + + @Override + public AsyncContext startAsync() throws IllegalStateException + { + throw new UnsupportedOperationException(UNSUPPORTED_WITH_WEBSOCKET_UPGRADE); + } + + @Override + public AsyncContext startAsync(ServletRequest servletRequest, ServletResponse servletResponse) throws IllegalStateException + { + throw new UnsupportedOperationException(UNSUPPORTED_WITH_WEBSOCKET_UPGRADE); + } + + @Override + public T upgrade(Class handlerClass) throws IOException, ServletException + { + throw new UnsupportedOperationException(UNSUPPORTED_WITH_WEBSOCKET_UPGRADE); + } + + public void complete() + { + request = null; + } +}