From 416b6a8a15ed109bf7d2539225dd008a72357d0e Mon Sep 17 00:00:00 2001 From: Simone Bordet Date: Mon, 12 Dec 2011 16:57:14 +0100 Subject: [PATCH 1/7] Added guard against multiple closes: if the endPoint is already closed, skip updating the SSLEngine and/or the endPoint. --- .../java/org/eclipse/jetty/io/nio/SslConnection.java | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) diff --git a/jetty-io/src/main/java/org/eclipse/jetty/io/nio/SslConnection.java b/jetty-io/src/main/java/org/eclipse/jetty/io/nio/SslConnection.java index 9bda79b9191..82d6fbeceb7 100644 --- a/jetty-io/src/main/java/org/eclipse/jetty/io/nio/SslConnection.java +++ b/jetty-io/src/main/java/org/eclipse/jetty/io/nio/SslConnection.java @@ -16,7 +16,6 @@ package org.eclipse.jetty.io.nio; import java.io.IOException; import java.nio.ByteBuffer; import java.util.concurrent.atomic.AtomicBoolean; - import javax.net.ssl.SSLEngine; import javax.net.ssl.SSLEngineResult; import javax.net.ssl.SSLEngineResult.HandshakeStatus; @@ -381,9 +380,10 @@ public class SslConnection extends AbstractConnection implements AsyncConnection } // pass on ishut/oshut state - if (!_inbound.hasContent() && _endp.isInputShutdown()) + if (_endp.isOpen() && _endp.isInputShutdown() && !_inbound.hasContent()) _engine.closeInbound(); - if (!_outbound.hasContent() && _engine.isOutboundDone()) + + if (_endp.isOpen() && _engine.isOutboundDone() && !_outbound.hasContent()) _endp.shutdownOutput(); some_progress|=progress; @@ -570,11 +570,7 @@ public class SslConnection extends AbstractConnection implements AsyncConnection public String toString() { - Buffer i=_inbound; - Buffer o=_outbound; - Buffer u=_unwrapBuf; - - return super.toString()+"|"+_engine.getHandshakeStatus()+" i/u/o="+(i==null?0:i.length())+"/"+(u==null?0:u.length())+"/"+(o==null?0:o.length()); + return String.format("%s | %s", super.toString(), _sslEndPoint); } /* ------------------------------------------------------------ */ From c1dac27886c606110856bdf9f685c8f7dd6b6724 Mon Sep 17 00:00:00 2001 From: Jesse McConnell Date: Mon, 12 Dec 2011 16:17:30 -0600 Subject: [PATCH 2/7] Bug 366362 - JAX-WS over https returns the wrong request address. Applied patch by Kenny Stridh . Thanks! --- .../jetty/http/spi/HttpSpiContextHandler.java | 65 ++- .../eclipse/jetty/http/spi/JettyExchange.java | 28 + .../jetty/http/spi/JettyHttpExchange.java | 479 +++++++++--------- .../http/spi/JettyHttpExchangeDelegate.java | 228 +++++++++ .../jetty/http/spi/JettyHttpsExchange.java | 179 +++++++ 5 files changed, 728 insertions(+), 251 deletions(-) create mode 100644 jetty-http-spi/src/main/java/org/eclipse/jetty/http/spi/JettyExchange.java create mode 100644 jetty-http-spi/src/main/java/org/eclipse/jetty/http/spi/JettyHttpExchangeDelegate.java create mode 100644 jetty-http-spi/src/main/java/org/eclipse/jetty/http/spi/JettyHttpsExchange.java diff --git a/jetty-http-spi/src/main/java/org/eclipse/jetty/http/spi/HttpSpiContextHandler.java b/jetty-http-spi/src/main/java/org/eclipse/jetty/http/spi/HttpSpiContextHandler.java index 9f6198e8598..62c30cac169 100644 --- a/jetty-http-spi/src/main/java/org/eclipse/jetty/http/spi/HttpSpiContextHandler.java +++ b/jetty-http-spi/src/main/java/org/eclipse/jetty/http/spi/HttpSpiContextHandler.java @@ -1,4 +1,5 @@ package org.eclipse.jetty.http.spi; + //======================================================================== //Copyright (c) 2004-2009 Mort Bay Consulting Pty. Ltd. //------------------------------------------------------------------------ @@ -12,19 +13,22 @@ package org.eclipse.jetty.http.spi; //You may elect to redistribute this code under either of these licenses. //======================================================================== -import com.sun.net.httpserver.Authenticator; -import com.sun.net.httpserver.Authenticator.Result; -import com.sun.net.httpserver.HttpContext; -import com.sun.net.httpserver.HttpHandler; -import com.sun.net.httpserver.HttpPrincipal; -import org.eclipse.jetty.server.Request; -import org.eclipse.jetty.server.handler.ContextHandler; +import java.io.IOException; +import java.io.PrintWriter; import javax.servlet.ServletException; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; -import java.io.IOException; -import java.io.PrintWriter; + +import org.eclipse.jetty.server.Request; +import org.eclipse.jetty.server.handler.ContextHandler; + +import com.sun.net.httpserver.Authenticator; +import com.sun.net.httpserver.Authenticator.Result; +import com.sun.net.httpserver.HttpContext; +import com.sun.net.httpserver.HttpExchange; +import com.sun.net.httpserver.HttpHandler; +import com.sun.net.httpserver.HttpPrincipal; /** * Jetty handler that bridges requests to {@link HttpHandler}. @@ -36,7 +40,6 @@ public class HttpSpiContextHandler extends ContextHandler private HttpHandler _httpHandler; - public HttpSpiContextHandler(HttpContext httpContext, HttpHandler httpHandler) { this._httpContext = httpContext; @@ -46,9 +49,20 @@ public class HttpSpiContextHandler extends ContextHandler @Override public void doScope(String target, Request baseRequest, HttpServletRequest req, HttpServletResponse resp) throws IOException, ServletException { - if (!target.startsWith(getContextPath())) return; + if (!target.startsWith(getContextPath())) + { + return; + } - JettyHttpExchange jettyHttpExchange = new JettyHttpExchange(_httpContext, req, resp); + HttpExchange jettyHttpExchange; + if (baseRequest.isSecure()) + { + jettyHttpExchange = new JettyHttpsExchange(_httpContext,req,resp); + } + else + { + jettyHttpExchange = new JettyHttpExchange(_httpContext,req,resp); + } // TODO: add filters processing @@ -56,38 +70,41 @@ public class HttpSpiContextHandler extends ContextHandler { Authenticator auth = _httpContext.getAuthenticator(); if (auth != null) - handleAuthentication(resp, jettyHttpExchange, auth); + { + handleAuthentication(resp,jettyHttpExchange,auth); + } else + { _httpHandler.handle(jettyHttpExchange); + } } - catch(Exception ex) + catch (Exception ex) { PrintWriter writer = new PrintWriter(jettyHttpExchange.getResponseBody()); - + resp.setStatus(500); writer.println("

HTTP ERROR: 500

"); writer.println("
INTERNAL_SERVER_ERROR
"); writer.println("

RequestURI=" + req.getRequestURI() + "

"); - + writer.println("
");
             ex.printStackTrace(writer);
             writer.println("
"); - + writer.println("

Powered by jetty://

"); - + writer.close(); } finally { baseRequest.setHandled(true); } - + } - - private void handleAuthentication(HttpServletResponse resp, JettyHttpExchange jettyHttpExchange, Authenticator auth) throws IOException + private void handleAuthentication(HttpServletResponse resp, HttpExchange httpExchange, Authenticator auth) throws IOException { - Result result = auth.authenticate(jettyHttpExchange); + Result result = auth.authenticate(httpExchange); if (result instanceof Authenticator.Failure) { int rc = ((Authenticator.Failure)result).getResponseCode(); @@ -101,8 +118,8 @@ public class HttpSpiContextHandler extends ContextHandler else if (result instanceof Authenticator.Success) { HttpPrincipal principal = ((Authenticator.Success)result).getPrincipal(); - jettyHttpExchange.setPrincipal(principal); - _httpHandler.handle(jettyHttpExchange); + ((JettyExchange)httpExchange).setPrincipal(principal); + _httpHandler.handle(httpExchange); } } diff --git a/jetty-http-spi/src/main/java/org/eclipse/jetty/http/spi/JettyExchange.java b/jetty-http-spi/src/main/java/org/eclipse/jetty/http/spi/JettyExchange.java new file mode 100644 index 00000000000..f448c118185 --- /dev/null +++ b/jetty-http-spi/src/main/java/org/eclipse/jetty/http/spi/JettyExchange.java @@ -0,0 +1,28 @@ +// ======================================================================== +// Copyright (c) 2009-2009 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.http.spi; + +import com.sun.net.httpserver.HttpPrincipal; + +/* ------------------------------------------------------------ */ +/** + */ +public interface JettyExchange +{ + + HttpPrincipal getPrincipal(); + + void setPrincipal(HttpPrincipal principal); + +} \ No newline at end of file diff --git a/jetty-http-spi/src/main/java/org/eclipse/jetty/http/spi/JettyHttpExchange.java b/jetty-http-spi/src/main/java/org/eclipse/jetty/http/spi/JettyHttpExchange.java index 9ce8e959d83..41809d036e3 100644 --- a/jetty-http-spi/src/main/java/org/eclipse/jetty/http/spi/JettyHttpExchange.java +++ b/jetty-http-spi/src/main/java/org/eclipse/jetty/http/spi/JettyHttpExchange.java @@ -1,227 +1,252 @@ -package org.eclipse.jetty.http.spi; - -//======================================================================== -//Copyright (c) 2004-2009 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. -//======================================================================== - -import java.io.IOException; -import java.io.InputStream; -import java.io.OutputStream; -import java.net.InetSocketAddress; -import java.net.URI; -import java.net.URISyntaxException; -import java.util.Enumeration; -import java.util.List; -import java.util.Map; - -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; - -import com.sun.net.httpserver.Headers; -import com.sun.net.httpserver.HttpContext; -import com.sun.net.httpserver.HttpExchange; -import com.sun.net.httpserver.HttpPrincipal; - -/** - * Jetty implementation of {@link com.sun.net.httpserver.HttpExchange} - */ -public class JettyHttpExchange extends HttpExchange -{ - - private HttpContext _httpContext; - - private HttpServletRequest _req; - - private HttpServletResponse _resp; - - private Headers _responseHeaders = new Headers(); - - private int _responseCode = 0; - - private InputStream _is; - - private OutputStream _os; - - private HttpPrincipal _httpPrincipal; - - - public JettyHttpExchange(HttpContext jaxWsContext , HttpServletRequest req, - HttpServletResponse resp) - { - this._httpContext = jaxWsContext; - this._req = req; - this._resp = resp; - try - { - this._is = req.getInputStream(); - this._os = resp.getOutputStream(); - } - catch (IOException ex) - { - throw new RuntimeException(ex); - } - } - - @Override - public Headers getRequestHeaders() - { - Headers headers = new Headers(); - Enumeration en = _req.getHeaderNames(); - while (en.hasMoreElements()) - { - String name = (String) en.nextElement(); - Enumeration en2 = _req.getHeaders(name); - while (en2.hasMoreElements()) - { - String value = (String) en2.nextElement(); - headers.add(name, value); - } - } - return headers; - } - - @Override - public Headers getResponseHeaders() - { - return _responseHeaders; - } - - @Override - public URI getRequestURI() - { - try - { - String uriAsString = _req.getRequestURI(); - if (_req.getQueryString() != null) - uriAsString += "?" + _req.getQueryString(); - - return new URI(uriAsString); - } - catch (URISyntaxException ex) - { - throw new RuntimeException(ex); - } - } - - @Override - public String getRequestMethod() - { - return _req.getMethod(); - } - - @Override - public HttpContext getHttpContext() - { - return _httpContext; - } - - @Override - public void close() - { - try - { - _resp.getOutputStream().close(); - } - catch (IOException ex) - { - throw new RuntimeException(ex); - } - } - - @Override - public InputStream getRequestBody() - { - return _is; - } - - @Override - public OutputStream getResponseBody() - { - return _os; - } - - @Override - public void sendResponseHeaders(int rCode, long responseLength) - throws IOException - { - this._responseCode = rCode; - - for (Map.Entry> stringListEntry : _responseHeaders.entrySet()) - { - String name = stringListEntry.getKey(); - List values = stringListEntry.getValue(); - - for (String value : values) - { - _resp.setHeader(name, value); - } - } - if (responseLength > 0) - _resp.setHeader("content-length", "" + responseLength); - _resp.setStatus(rCode); - } - - @Override - public InetSocketAddress getRemoteAddress() - { - return new InetSocketAddress(_req.getRemoteAddr(), _req.getRemotePort()); - } - - @Override - public int getResponseCode() - { - return _responseCode; - } - - @Override - public InetSocketAddress getLocalAddress() - { - return new InetSocketAddress(_req.getLocalAddr(), _req.getLocalPort()); - } - - @Override - public String getProtocol() - { - return _req.getProtocol(); - } - - @Override - public Object getAttribute(String name) - { - return _req.getAttribute(name); - } - - @Override - public void setAttribute(String name, Object value) - { - _req.setAttribute(name, value); - } - - @Override - public void setStreams(InputStream i, OutputStream o) - { - _is = i; - _os = o; - } - - @Override - public HttpPrincipal getPrincipal() - { - return _httpPrincipal; - } - - public void setPrincipal(HttpPrincipal principal) - { - this._httpPrincipal = principal; - } - -} +// ======================================================================== +// Copyright (c) 2009-2009 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.http.spi; + +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.net.InetSocketAddress; +import java.net.URI; + +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +import com.sun.net.httpserver.Headers; +import com.sun.net.httpserver.HttpContext; +import com.sun.net.httpserver.HttpExchange; +import com.sun.net.httpserver.HttpPrincipal; + +/* ------------------------------------------------------------ */ +/** + */ +public class JettyHttpExchange extends HttpExchange implements JettyExchange +{ + private JettyHttpExchangeDelegate _delegate; + + public JettyHttpExchange(HttpContext jaxWsContext, HttpServletRequest req, HttpServletResponse resp) + { + super(); + _delegate = new JettyHttpExchangeDelegate(jaxWsContext,req,resp); + } + + /* ------------------------------------------------------------ */ + /** + * @see org.eclipse.jetty.http.spi.JettyExchange#hashCode() + */ + @Override + public int hashCode() + { + return _delegate.hashCode(); + } + + /* ------------------------------------------------------------ */ + /** + * @see org.eclipse.jetty.http.spi.JettyExchange#getRequestHeaders() + */ + @Override + public Headers getRequestHeaders() + { + return _delegate.getRequestHeaders(); + } + + /* ------------------------------------------------------------ */ + /** + * @see org.eclipse.jetty.http.spi.JettyExchange#getResponseHeaders() + */ + @Override + public Headers getResponseHeaders() + { + return _delegate.getResponseHeaders(); + } + + /* ------------------------------------------------------------ */ + /** + * @see org.eclipse.jetty.http.spi.JettyExchange#getRequestURI() + */ + @Override + public URI getRequestURI() + { + return _delegate.getRequestURI(); + } + + /* ------------------------------------------------------------ */ + /** + * @see org.eclipse.jetty.http.spi.JettyExchange#getRequestMethod() + */ + @Override + public String getRequestMethod() + { + return _delegate.getRequestMethod(); + } + + /* ------------------------------------------------------------ */ + /** + * @see org.eclipse.jetty.http.spi.JettyExchange#getHttpContext() + */ + @Override + public HttpContext getHttpContext() + { + return _delegate.getHttpContext(); + } + + /* ------------------------------------------------------------ */ + /** + * @see org.eclipse.jetty.http.spi.JettyExchange#close() + */ + @Override + public void close() + { + _delegate.close(); + } + + /* ------------------------------------------------------------ */ + /** + * @see org.eclipse.jetty.http.spi.JettyExchange#equals(java.lang.Object) + */ + @Override + public boolean equals(Object obj) + { + return _delegate.equals(obj); + } + + /* ------------------------------------------------------------ */ + /** + * @see org.eclipse.jetty.http.spi.JettyExchange#getRequestBody() + */ + @Override + public InputStream getRequestBody() + { + return _delegate.getRequestBody(); + } + + /* ------------------------------------------------------------ */ + /** + * @see org.eclipse.jetty.http.spi.JettyExchange#getResponseBody() + */ + @Override + public OutputStream getResponseBody() + { + return _delegate.getResponseBody(); + } + + /* ------------------------------------------------------------ */ + /** + * @see org.eclipse.jetty.http.spi.JettyExchange#sendResponseHeaders(int, long) + */ + @Override + public void sendResponseHeaders(int rCode, long responseLength) throws IOException + { + _delegate.sendResponseHeaders(rCode,responseLength); + } + + /* ------------------------------------------------------------ */ + /** + * @see org.eclipse.jetty.http.spi.JettyExchange#getRemoteAddress() + */ + @Override + public InetSocketAddress getRemoteAddress() + { + return _delegate.getRemoteAddress(); + } + + /* ------------------------------------------------------------ */ + /** + * @see org.eclipse.jetty.http.spi.JettyExchange#getResponseCode() + */ + @Override + public int getResponseCode() + { + return _delegate.getResponseCode(); + } + + /* ------------------------------------------------------------ */ + /** + * @see org.eclipse.jetty.http.spi.JettyExchange#getLocalAddress() + */ + @Override + public InetSocketAddress getLocalAddress() + { + return _delegate.getLocalAddress(); + } + + /* ------------------------------------------------------------ */ + /** + * @see org.eclipse.jetty.http.spi.JettyExchange#getProtocol() + */ + @Override + public String getProtocol() + { + return _delegate.getProtocol(); + } + + /* ------------------------------------------------------------ */ + /** + * @see org.eclipse.jetty.http.spi.JettyExchange#getAttribute(java.lang.String) + */ + @Override + public Object getAttribute(String name) + { + return _delegate.getAttribute(name); + } + + /* ------------------------------------------------------------ */ + /** + * @see org.eclipse.jetty.http.spi.JettyExchange#setAttribute(java.lang.String, java.lang.Object) + */ + @Override + public void setAttribute(String name, Object value) + { + _delegate.setAttribute(name,value); + } + + /* ------------------------------------------------------------ */ + /** + * @see org.eclipse.jetty.http.spi.JettyExchange#setStreams(java.io.InputStream, java.io.OutputStream) + */ + @Override + public void setStreams(InputStream i, OutputStream o) + { + _delegate.setStreams(i,o); + } + + /* ------------------------------------------------------------ */ + /** + * @see org.eclipse.jetty.http.spi.JettyExchange#getPrincipal() + */ + @Override + public HttpPrincipal getPrincipal() + { + return _delegate.getPrincipal(); + } + + /* ------------------------------------------------------------ */ + /** + * @see org.eclipse.jetty.http.spi.JettyExchange#setPrincipal(com.sun.net.httpserver.HttpPrincipal) + */ + public void setPrincipal(HttpPrincipal principal) + { + _delegate.setPrincipal(principal); + } + + /* ------------------------------------------------------------ */ + /** + * @see org.eclipse.jetty.http.spi.JettyExchange#toString() + */ + @Override + public String toString() + { + return _delegate.toString(); + } + +} diff --git a/jetty-http-spi/src/main/java/org/eclipse/jetty/http/spi/JettyHttpExchangeDelegate.java b/jetty-http-spi/src/main/java/org/eclipse/jetty/http/spi/JettyHttpExchangeDelegate.java new file mode 100644 index 00000000000..b772afbe598 --- /dev/null +++ b/jetty-http-spi/src/main/java/org/eclipse/jetty/http/spi/JettyHttpExchangeDelegate.java @@ -0,0 +1,228 @@ +package org.eclipse.jetty.http.spi; + +//======================================================================== +//Copyright (c) 2004-2009 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. +//======================================================================== + +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.net.InetSocketAddress; +import java.net.URI; +import java.net.URISyntaxException; +import java.util.Enumeration; +import java.util.List; +import java.util.Map; + +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +import com.sun.net.httpserver.Headers; +import com.sun.net.httpserver.HttpContext; +import com.sun.net.httpserver.HttpExchange; +import com.sun.net.httpserver.HttpPrincipal; + +/** + * Jetty implementation of {@link com.sun.net.httpserver.HttpExchange} + */ +public class JettyHttpExchangeDelegate extends HttpExchange +{ + + private HttpContext _httpContext; + + private HttpServletRequest _req; + + private HttpServletResponse _resp; + + private Headers _responseHeaders = new Headers(); + + private int _responseCode = 0; + + private InputStream _is; + + private OutputStream _os; + + private HttpPrincipal _httpPrincipal; + + JettyHttpExchangeDelegate(HttpContext jaxWsContext, HttpServletRequest req, HttpServletResponse resp) + { + this._httpContext = jaxWsContext; + this._req = req; + this._resp = resp; + try + { + this._is = req.getInputStream(); + this._os = resp.getOutputStream(); + } + catch (IOException ex) + { + throw new RuntimeException(ex); + } + } + + @Override + public Headers getRequestHeaders() + { + Headers headers = new Headers(); + Enumeration en = _req.getHeaderNames(); + while (en.hasMoreElements()) + { + String name = (String)en.nextElement(); + Enumeration en2 = _req.getHeaders(name); + while (en2.hasMoreElements()) + { + String value = (String)en2.nextElement(); + headers.add(name,value); + } + } + return headers; + } + + @Override + public Headers getResponseHeaders() + { + return _responseHeaders; + } + + @Override + public URI getRequestURI() + { + try + { + String uriAsString = _req.getRequestURI(); + if (_req.getQueryString() != null) + { + uriAsString += "?" + _req.getQueryString(); + } + + return new URI(uriAsString); + } + catch (URISyntaxException ex) + { + throw new RuntimeException(ex); + } + } + + @Override + public String getRequestMethod() + { + return _req.getMethod(); + } + + @Override + public HttpContext getHttpContext() + { + return _httpContext; + } + + @Override + public void close() + { + try + { + _resp.getOutputStream().close(); + } + catch (IOException ex) + { + throw new RuntimeException(ex); + } + } + + @Override + public InputStream getRequestBody() + { + return _is; + } + + @Override + public OutputStream getResponseBody() + { + return _os; + } + + @Override + public void sendResponseHeaders(int rCode, long responseLength) throws IOException + { + this._responseCode = rCode; + + for (Map.Entry> stringListEntry : _responseHeaders.entrySet()) + { + String name = stringListEntry.getKey(); + List values = stringListEntry.getValue(); + + for (String value : values) + { + _resp.setHeader(name,value); + } + } + if (responseLength > 0) + { + _resp.setHeader("content-length","" + responseLength); + } + _resp.setStatus(rCode); + } + + @Override + public InetSocketAddress getRemoteAddress() + { + return new InetSocketAddress(_req.getRemoteAddr(),_req.getRemotePort()); + } + + @Override + public int getResponseCode() + { + return _responseCode; + } + + @Override + public InetSocketAddress getLocalAddress() + { + return new InetSocketAddress(_req.getLocalAddr(),_req.getLocalPort()); + } + + @Override + public String getProtocol() + { + return _req.getProtocol(); + } + + @Override + public Object getAttribute(String name) + { + return _req.getAttribute(name); + } + + @Override + public void setAttribute(String name, Object value) + { + _req.setAttribute(name,value); + } + + @Override + public void setStreams(InputStream i, OutputStream o) + { + _is = i; + _os = o; + } + + @Override + public HttpPrincipal getPrincipal() + { + return _httpPrincipal; + } + + public void setPrincipal(HttpPrincipal principal) + { + this._httpPrincipal = principal; + } + +} diff --git a/jetty-http-spi/src/main/java/org/eclipse/jetty/http/spi/JettyHttpsExchange.java b/jetty-http-spi/src/main/java/org/eclipse/jetty/http/spi/JettyHttpsExchange.java new file mode 100644 index 00000000000..f4af2b99ce9 --- /dev/null +++ b/jetty-http-spi/src/main/java/org/eclipse/jetty/http/spi/JettyHttpsExchange.java @@ -0,0 +1,179 @@ +// ======================================================================== +// Copyright (c) 2009-2009 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.http.spi; + +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.net.InetSocketAddress; +import java.net.URI; + +import javax.net.ssl.SSLSession; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +import com.sun.net.httpserver.Headers; +import com.sun.net.httpserver.HttpContext; +import com.sun.net.httpserver.HttpPrincipal; +import com.sun.net.httpserver.HttpsExchange; + +/* ------------------------------------------------------------ */ +/** + */ +public class JettyHttpsExchange extends HttpsExchange implements JettyExchange +{ + private JettyHttpExchangeDelegate _delegate; + + public JettyHttpsExchange(HttpContext jaxWsContext, HttpServletRequest req, HttpServletResponse resp) + { + super(); + _delegate = new JettyHttpExchangeDelegate(jaxWsContext,req,resp); + } + + @Override + public int hashCode() + { + return _delegate.hashCode(); + } + + @Override + public Headers getRequestHeaders() + { + return _delegate.getRequestHeaders(); + } + + @Override + public Headers getResponseHeaders() + { + return _delegate.getResponseHeaders(); + } + + @Override + public URI getRequestURI() + { + return _delegate.getRequestURI(); + } + + @Override + public String getRequestMethod() + { + return _delegate.getRequestMethod(); + } + + @Override + public HttpContext getHttpContext() + { + return _delegate.getHttpContext(); + } + + @Override + public void close() + { + _delegate.close(); + } + + @Override + public boolean equals(Object obj) + { + return _delegate.equals(obj); + } + + @Override + public InputStream getRequestBody() + { + return _delegate.getRequestBody(); + } + + @Override + public OutputStream getResponseBody() + { + return _delegate.getResponseBody(); + } + + @Override + public void sendResponseHeaders(int rCode, long responseLength) throws IOException + { + _delegate.sendResponseHeaders(rCode,responseLength); + } + + @Override + public InetSocketAddress getRemoteAddress() + { + return _delegate.getRemoteAddress(); + } + + @Override + public int getResponseCode() + { + return _delegate.getResponseCode(); + } + + @Override + public InetSocketAddress getLocalAddress() + { + return _delegate.getLocalAddress(); + } + + @Override + public String getProtocol() + { + return _delegate.getProtocol(); + } + + @Override + public Object getAttribute(String name) + { + return _delegate.getAttribute(name); + } + + @Override + public void setAttribute(String name, Object value) + { + _delegate.setAttribute(name,value); + } + + @Override + public void setStreams(InputStream i, OutputStream o) + { + _delegate.setStreams(i,o); + } + + @Override + public HttpPrincipal getPrincipal() + { + return _delegate.getPrincipal(); + } + + public void setPrincipal(HttpPrincipal principal) + { + _delegate.setPrincipal(principal); + } + + @Override + public String toString() + { + return _delegate.toString(); + } + + /* ------------------------------------------------------------ */ + /** + * @see com.sun.net.httpserver.HttpsExchange#getSSLSession() + */ + @Override + public SSLSession getSSLSession() + { + return null; + } + +} From dfb96dfeeb7dbbdafae87496da671cb0ee5b71e4 Mon Sep 17 00:00:00 2001 From: Jan Bartel Date: Tue, 13 Dec 2011 11:07:26 +1100 Subject: [PATCH 3/7] 366342 Dont persist DosFilter trackers in http session --- .../org/eclipse/jetty/servlets/DoSFilter.java | 63 ++++++++++++++----- 1 file changed, 47 insertions(+), 16 deletions(-) diff --git a/jetty-servlets/src/main/java/org/eclipse/jetty/servlets/DoSFilter.java b/jetty-servlets/src/main/java/org/eclipse/jetty/servlets/DoSFilter.java index 69bd7d89265..6c12e2e75b3 100644 --- a/jetty-servlets/src/main/java/org/eclipse/jetty/servlets/DoSFilter.java +++ b/jetty-servlets/src/main/java/org/eclipse/jetty/servlets/DoSFilter.java @@ -14,7 +14,9 @@ package org.eclipse.jetty.servlets; import java.io.IOException; +import java.io.Serializable; import java.util.HashSet; +import java.util.Map; import java.util.Queue; import java.util.StringTokenizer; import java.util.concurrent.ConcurrentHashMap; @@ -32,8 +34,10 @@ import javax.servlet.ServletResponse; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import javax.servlet.http.HttpSession; +import javax.servlet.http.HttpSessionActivationListener; import javax.servlet.http.HttpSessionBindingEvent; import javax.servlet.http.HttpSessionBindingListener; +import javax.servlet.http.HttpSessionEvent; import org.eclipse.jetty.continuation.Continuation; import org.eclipse.jetty.continuation.ContinuationListener; @@ -908,13 +912,14 @@ public class DoSFilter implements Filter * A RateTracker is associated with a connection, and stores request rate * data. */ - class RateTracker extends Timeout.Task implements HttpSessionBindingListener + class RateTracker extends Timeout.Task implements HttpSessionBindingListener, HttpSessionActivationListener { - protected final String _id; - protected final int _type; - protected final long[] _timestamps; - protected int _next; - + transient protected final String _id; + transient protected final int _type; + transient protected final long[] _timestamps; + transient protected int _next; + + public RateTracker(String id, int type,int maxRequestsPerSecond) { _id = id; @@ -953,25 +958,49 @@ public class DoSFilter implements Filter public void valueBound(HttpSessionBindingEvent event) - { + { + if (LOG.isDebugEnabled()) + LOG.debug("Value bound:"+_id); } public void valueUnbound(HttpSessionBindingEvent event) { - _rateTrackers.remove(_id); + //take the tracker out of the list of trackers + if (_rateTrackers != null) + _rateTrackers.remove(_id); + if (LOG.isDebugEnabled()) LOG.debug("Tracker removed: "+_id); } + public void sessionWillPassivate(HttpSessionEvent se) + { + //take the tracker of the list of trackers (if its still there) + //and ensure that we take ourselves out of the session so we are not saved + if (_rateTrackers != null) + _rateTrackers.remove(_id); + se.getSession().removeAttribute(__TRACKER); + if (LOG.isDebugEnabled()) LOG.debug("Value removed: "+_id); + } + + public void sessionDidActivate(HttpSessionEvent se) + { + LOG.warn("Unexpected session activation"); + } + + public void expired() { - long now = _trackerTimeoutQ.getNow(); - int latestIndex = _next == 0 ? 3 : (_next - 1 ) % _timestamps.length; - long last=_timestamps[latestIndex]; - boolean hasRecentRequest = last != 0 && (now-last)<1000L; + if (_rateTrackers != null && _trackerTimeoutQ != null) + { + long now = _trackerTimeoutQ.getNow(); + int latestIndex = _next == 0 ? 3 : (_next - 1 ) % _timestamps.length; + long last=_timestamps[latestIndex]; + boolean hasRecentRequest = last != 0 && (now-last)<1000L; - if (hasRecentRequest) - reschedule(); - else - _rateTrackers.remove(_id); + if (hasRecentRequest) + reschedule(); + else + _rateTrackers.remove(_id); + } } @Override @@ -979,6 +1008,8 @@ public class DoSFilter implements Filter { return "RateTracker/"+_id+"/"+_type; } + + } class FixedRateTracker extends RateTracker From fa0e06d186723b95cc70602f34ae15364771fc61 Mon Sep 17 00:00:00 2001 From: Simone Bordet Date: Tue, 13 Dec 2011 11:14:25 +0100 Subject: [PATCH 4/7] Removed guard whether the channel is closed in checkIdleTimestamp(). This follows commit ff29a1cc51325ebf4f95feaa4bed847023a7e3fc for JETTY-1322. The reason to remove the guard is that the channel may be closed multiple times, and if for any reason a check to the idle timestamp triggers, then the idle callback is invoked, but it's not really idle: it is already closed. When used with SSL, this causes a truncation attack exception thrown by SSLEngine, because the idle callback causes a shutdown of the SSLEngine without having received a SSL close alert. --- .../org/eclipse/jetty/io/nio/SelectChannelEndPoint.java | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/jetty-io/src/main/java/org/eclipse/jetty/io/nio/SelectChannelEndPoint.java b/jetty-io/src/main/java/org/eclipse/jetty/io/nio/SelectChannelEndPoint.java index cf69878278a..cc698d5e4e7 100644 --- a/jetty-io/src/main/java/org/eclipse/jetty/io/nio/SelectChannelEndPoint.java +++ b/jetty-io/src/main/java/org/eclipse/jetty/io/nio/SelectChannelEndPoint.java @@ -262,25 +262,25 @@ public class SelectChannelEndPoint extends ChannelEndPoint implements AsyncEndPo { _idleTimestamp=check?System.currentTimeMillis():0; } - + /* ------------------------------------------------------------ */ public boolean isCheckForIdle() { return _idleTimestamp!=0; } - + /* ------------------------------------------------------------ */ protected void notIdle() { if (_idleTimestamp!=0) _idleTimestamp=System.currentTimeMillis(); } - + /* ------------------------------------------------------------ */ public void checkIdleTimestamp(long now) { long idleTimestamp=_idleTimestamp; - if (!getChannel().isOpen() || idleTimestamp!=0 && _maxIdleTime>0 && now>(idleTimestamp+_maxIdleTime)) + if (idleTimestamp!=0 && _maxIdleTime>0 && now>(idleTimestamp+_maxIdleTime)) { onIdleExpired(); _idleTimestamp=now; From bcbcbafb14bdefd43ffb28c033acffe0dde33dbf Mon Sep 17 00:00:00 2001 From: Simone Bordet Date: Tue, 13 Dec 2011 11:15:04 +0100 Subject: [PATCH 5/7] More tests for WebSocket over SSL. --- .../jetty/websocket/WebSocketOverSSLTest.java | 60 +++++++++++++++++++ 1 file changed, 60 insertions(+) diff --git a/jetty-websocket/src/test/java/org/eclipse/jetty/websocket/WebSocketOverSSLTest.java b/jetty-websocket/src/test/java/org/eclipse/jetty/websocket/WebSocketOverSSLTest.java index b78baf44de9..a9e07a0b60d 100644 --- a/jetty-websocket/src/test/java/org/eclipse/jetty/websocket/WebSocketOverSSLTest.java +++ b/jetty-websocket/src/test/java/org/eclipse/jetty/websocket/WebSocketOverSSLTest.java @@ -2,6 +2,7 @@ package org.eclipse.jetty.websocket; import java.io.IOException; import java.net.URI; +import java.util.Arrays; import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; import javax.servlet.http.HttpServletRequest; @@ -121,4 +122,63 @@ public class WebSocketOverSSLTest Assert.assertTrue(serverLatch.await(5, TimeUnit.SECONDS)); Assert.assertTrue(clientLatch.await(5, TimeUnit.SECONDS)); } + + @Test + public void testManyMessages() throws Exception + { + startServer(new WebSocket.OnTextMessage() + { + private Connection connection; + + public void onOpen(Connection connection) + { + this.connection = connection; + } + + public void onMessage(String data) + { + try + { + connection.sendMessage(data); + } + catch (IOException x) + { + x.printStackTrace(); + } + } + + public void onClose(int closeCode, String message) + { + } + }); + int count = 1000; + final CountDownLatch clientLatch = new CountDownLatch(count); + startClient(new WebSocket.OnTextMessage() + { + public void onOpen(Connection connection) + { + } + + public void onMessage(String data) + { + clientLatch.countDown(); + } + + public void onClose(int closeCode, String message) + { + } + }); + + char[] chars = new char[256]; + Arrays.fill(chars, 'x'); + String message = new String(chars); + for (int i = 0; i < count; ++i) + _connection.sendMessage(message); + + Assert.assertTrue(clientLatch.await(5, TimeUnit.SECONDS)); + + // While messages may have all arrived, the SSL close alert + // may be in the way so give some time for it to be processed. + TimeUnit.SECONDS.sleep(1); + } } From 42816041c08de44852d4987329b2ab4a05003612 Mon Sep 17 00:00:00 2001 From: Joakim Erdfelt Date: Tue, 13 Dec 2011 11:26:30 -0700 Subject: [PATCH 6/7] JETTY-1463 - WebSockets with Safari gets messages stuck as if in a buffer that needs to be flushed. + Adding unit test to attempt to replicate behavior (test passes) --- jetty-websocket/pom.xml | 6 + .../jetty/websocket/WebSocketCommTest.java | 217 ++++++++++++++++++ 2 files changed, 223 insertions(+) create mode 100644 jetty-websocket/src/test/java/org/eclipse/jetty/websocket/WebSocketCommTest.java diff --git a/jetty-websocket/pom.xml b/jetty-websocket/pom.xml index 1bc5214b513..cfb4b91e581 100644 --- a/jetty-websocket/pom.xml +++ b/jetty-websocket/pom.xml @@ -46,6 +46,12 @@ jetty-test-helper test + + org.eclipse.jetty + jetty-servlet + ${project.version} + test + diff --git a/jetty-websocket/src/test/java/org/eclipse/jetty/websocket/WebSocketCommTest.java b/jetty-websocket/src/test/java/org/eclipse/jetty/websocket/WebSocketCommTest.java new file mode 100644 index 00000000000..88f311d6527 --- /dev/null +++ b/jetty-websocket/src/test/java/org/eclipse/jetty/websocket/WebSocketCommTest.java @@ -0,0 +1,217 @@ +package org.eclipse.jetty.websocket; + +import static org.hamcrest.Matchers.*; + +import java.io.IOException; +import java.net.URI; +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.TimeUnit; + +import javax.servlet.ServletException; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +import org.eclipse.jetty.server.Connector; +import org.eclipse.jetty.server.Server; +import org.eclipse.jetty.servlet.ServletContextHandler; +import org.eclipse.jetty.servlet.ServletHolder; +import org.eclipse.jetty.util.log.StdErrLog; +import org.junit.Assert; +import org.junit.Before; +import org.junit.BeforeClass; +import org.junit.Test; + +/** + * WebSocketCommTest - to test reported undelivered messages in bug JETTY-1463 + */ +public class WebSocketCommTest +{ + @SuppressWarnings("serial") + private static class WebSocketCaptureServlet extends WebSocketServlet + { + public List captures = new ArrayList();; + + @Override + protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException + { + resp.sendError(404); + } + + public WebSocket doWebSocketConnect(HttpServletRequest request, String protocol) + { + CaptureSocket capture = new CaptureSocket(); + captures.add(capture); + return capture; + } + } + + private static class CaptureSocket implements WebSocket, WebSocket.OnTextMessage + { + private Connection conn; + public List messages; + + public CaptureSocket() + { + messages = new ArrayList(); + } + + public boolean isConnected() + { + if (conn == null) + { + return false; + } + return conn.isOpen(); + } + + public void onMessage(String data) + { + System.out.printf("Received Message \"%s\" [size %d]%n", data, data.length()); + messages.add(data); + } + + public void onOpen(Connection connection) + { + this.conn = connection; + } + + public void onClose(int closeCode, String message) + { + this.conn = null; + } + } + + public static class MessageSender implements WebSocket + { + private Connection conn; + private CountDownLatch connectLatch = new CountDownLatch(1); + + public void onOpen(Connection connection) + { + this.conn = connection; + connectLatch.countDown(); + } + + public void onClose(int closeCode, String message) + { + this.conn = null; + } + + public boolean isConnected() + { + if (this.conn == null) + { + return false; + } + return this.conn.isOpen(); + } + + public void sendMessage(String format, Object... args) throws IOException + { + this.conn.sendMessage(String.format(format,args)); + } + + public void awaitConnect() throws InterruptedException + { + connectLatch.await(1,TimeUnit.SECONDS); + } + + public void close() + { + if (this.conn == null) + { + return; + } + this.conn.close(); + } + } + + private Server server; + private WebSocketCaptureServlet servlet; + private URI serverUri; + + @BeforeClass + public static void initLogging() + { + // Configure Logging + System.setProperty("org.eclipse.jetty.util.log.class",StdErrLog.class.getName()); + System.setProperty("org.eclipse.jetty.LEVEL","DEBUG"); + } + + @Before + public void startServer() throws Exception + { + // Configure Server + server = new Server(0); + + ServletContextHandler context = new ServletContextHandler(); + context.setContextPath("/"); + server.setHandler(context); + + // Serve capture servlet + servlet = new WebSocketCaptureServlet(); + context.addServlet(new ServletHolder(servlet),"/"); + + // Start Server + server.start(); + + Connector conn = server.getConnectors()[0]; + String host = conn.getHost(); + if (host == null) + { + host = "localhost"; + } + int port = conn.getLocalPort(); + serverUri = new URI(String.format("ws://%s:%d/",host,port)); + System.out.printf("Server URI: %s%n",serverUri); + } + + @Test + public void testSendTextMessages() throws Exception + { + WebSocketClientFactory clientFactory = new WebSocketClientFactory(); + clientFactory.start(); + + WebSocketClient wsc = clientFactory.newWebSocketClient(); + MessageSender sender = new MessageSender(); + wsc.open(serverUri,sender); + + try + { + sender.awaitConnect(); + + // Send 5 short messages + for (int i = 0; i < 5; i++) + { + System.out.printf("Sending msg-%d%n",i); + sender.sendMessage("msg-%d",i); + } + + // Servlet should show only 1 connection. + Assert.assertThat("Servlet.captureSockets.size",servlet.captures.size(),is(1)); + + CaptureSocket socket = servlet.captures.get(0); + Assert.assertThat("CaptureSocket",socket,notNullValue()); + Assert.assertThat("CaptureSocket.isConnected", socket.isConnected(), is(true)); + + // Give servlet 500 millisecond to process messages + threadSleep(500,TimeUnit.MILLISECONDS); + // Should have captured 5 messages. + Assert.assertThat("CaptureSocket.messages.size",socket.messages.size(),is(5)); + } + finally + { + System.out.println("Closing client socket"); + sender.close(); + } + } + + public static void threadSleep(int dur, TimeUnit unit) throws InterruptedException + { + long ms = TimeUnit.MILLISECONDS.convert(dur,unit); + Thread.sleep(ms); + } +} From 6b42a1c45d6c62f9eedd72e547b62f93bac7a253 Mon Sep 17 00:00:00 2001 From: Joakim Erdfelt Date: Wed, 14 Dec 2011 10:23:40 -0700 Subject: [PATCH 7/7] JETTY-1463 - WebSockets with Safari gets messages stuck as if in a buffer that needs to be flushed. + Implementation of Safari WebSocket Draft-0 behavior in a unit test. (Test fails, and is currently set as @Ignore) --- .../websocket/SafariWebsocketDraft0Test.java | 110 +++++++++++++++ .../jetty/websocket/WebSocketCommTest.java | 111 +-------------- .../jetty/websocket/helper/CaptureSocket.java | 42 ++++++ .../jetty/websocket/helper/MessageSender.java | 52 +++++++ .../jetty/websocket/helper/SafariD00.java | 129 ++++++++++++++++++ .../helper/WebSocketCaptureServlet.java | 31 +++++ 6 files changed, 367 insertions(+), 108 deletions(-) create mode 100644 jetty-websocket/src/test/java/org/eclipse/jetty/websocket/SafariWebsocketDraft0Test.java create mode 100644 jetty-websocket/src/test/java/org/eclipse/jetty/websocket/helper/CaptureSocket.java create mode 100644 jetty-websocket/src/test/java/org/eclipse/jetty/websocket/helper/MessageSender.java create mode 100644 jetty-websocket/src/test/java/org/eclipse/jetty/websocket/helper/SafariD00.java create mode 100644 jetty-websocket/src/test/java/org/eclipse/jetty/websocket/helper/WebSocketCaptureServlet.java diff --git a/jetty-websocket/src/test/java/org/eclipse/jetty/websocket/SafariWebsocketDraft0Test.java b/jetty-websocket/src/test/java/org/eclipse/jetty/websocket/SafariWebsocketDraft0Test.java new file mode 100644 index 00000000000..3d7f2864143 --- /dev/null +++ b/jetty-websocket/src/test/java/org/eclipse/jetty/websocket/SafariWebsocketDraft0Test.java @@ -0,0 +1,110 @@ +package org.eclipse.jetty.websocket; + +import static org.hamcrest.Matchers.*; + +import java.net.URI; +import java.util.concurrent.TimeUnit; + +import org.eclipse.jetty.server.Connector; +import org.eclipse.jetty.server.Server; +import org.eclipse.jetty.servlet.ServletContextHandler; +import org.eclipse.jetty.servlet.ServletHolder; +import org.eclipse.jetty.util.log.StdErrLog; +import org.eclipse.jetty.websocket.helper.CaptureSocket; +import org.eclipse.jetty.websocket.helper.SafariD00; +import org.eclipse.jetty.websocket.helper.WebSocketCaptureServlet; +import org.junit.After; +import org.junit.Assert; +import org.junit.Before; +import org.junit.BeforeClass; +import org.junit.Ignore; +import org.junit.Test; + +public class SafariWebsocketDraft0Test +{ + private Server server; + private WebSocketCaptureServlet servlet; + private URI serverUri; + + @BeforeClass + public static void initLogging() + { + // Configure Logging + System.setProperty("org.eclipse.jetty.util.log.class",StdErrLog.class.getName()); + System.setProperty("org.eclipse.jetty.LEVEL","DEBUG"); + } + + @Before + public void startServer() throws Exception + { + // Configure Server + server = new Server(0); + + ServletContextHandler context = new ServletContextHandler(); + context.setContextPath("/"); + server.setHandler(context); + + // Serve capture servlet + servlet = new WebSocketCaptureServlet(); + context.addServlet(new ServletHolder(servlet),"/"); + + // Start Server + server.start(); + + Connector conn = server.getConnectors()[0]; + String host = conn.getHost(); + if (host == null) + { + host = "localhost"; + } + int port = conn.getLocalPort(); + serverUri = new URI(String.format("ws://%s:%d/",host,port)); + System.out.printf("Server URI: %s%n",serverUri); + } + + @Test + @Ignore + public void testSendTextMessages() throws Exception + { + SafariD00 safari = new SafariD00(serverUri); + + try + { + safari.connect(); + safari.issueHandshake(); + + // Send 5 short messages, using technique seen in Safari. + safari.sendMessage("aa-0"); // single msg + safari.sendMessage("aa-1", "aa-2", "aa-3", "aa-4"); + + // Servlet should show only 1 connection. + Assert.assertThat("Servlet.captureSockets.size",servlet.captures.size(),is(1)); + + CaptureSocket socket = servlet.captures.get(0); + Assert.assertThat("CaptureSocket",socket,notNullValue()); + Assert.assertThat("CaptureSocket.isConnected", socket.isConnected(), is(true)); + + // Give servlet 500 millisecond to process messages + threadSleep(1,TimeUnit.SECONDS); + // Should have captured 5 messages. + Assert.assertThat("CaptureSocket.messages.size",socket.messages.size(),is(5)); + } + finally + { + System.out.println("Closing client socket"); + safari.disconnect(); + } + } + + public static void threadSleep(int dur, TimeUnit unit) throws InterruptedException + { + long ms = TimeUnit.MILLISECONDS.convert(dur,unit); + Thread.sleep(ms); + } + + @After + public void stopServer() throws Exception + { + server.stop(); + } +} diff --git a/jetty-websocket/src/test/java/org/eclipse/jetty/websocket/WebSocketCommTest.java b/jetty-websocket/src/test/java/org/eclipse/jetty/websocket/WebSocketCommTest.java index 88f311d6527..c2017f7c0a9 100644 --- a/jetty-websocket/src/test/java/org/eclipse/jetty/websocket/WebSocketCommTest.java +++ b/jetty-websocket/src/test/java/org/eclipse/jetty/websocket/WebSocketCommTest.java @@ -2,22 +2,17 @@ package org.eclipse.jetty.websocket; import static org.hamcrest.Matchers.*; -import java.io.IOException; import java.net.URI; -import java.util.ArrayList; -import java.util.List; -import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; -import javax.servlet.ServletException; -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; - import org.eclipse.jetty.server.Connector; import org.eclipse.jetty.server.Server; import org.eclipse.jetty.servlet.ServletContextHandler; import org.eclipse.jetty.servlet.ServletHolder; import org.eclipse.jetty.util.log.StdErrLog; +import org.eclipse.jetty.websocket.helper.CaptureSocket; +import org.eclipse.jetty.websocket.helper.MessageSender; +import org.eclipse.jetty.websocket.helper.WebSocketCaptureServlet; import org.junit.Assert; import org.junit.Before; import org.junit.BeforeClass; @@ -29,106 +24,6 @@ import org.junit.Test; */ public class WebSocketCommTest { - @SuppressWarnings("serial") - private static class WebSocketCaptureServlet extends WebSocketServlet - { - public List captures = new ArrayList();; - - @Override - protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException - { - resp.sendError(404); - } - - public WebSocket doWebSocketConnect(HttpServletRequest request, String protocol) - { - CaptureSocket capture = new CaptureSocket(); - captures.add(capture); - return capture; - } - } - - private static class CaptureSocket implements WebSocket, WebSocket.OnTextMessage - { - private Connection conn; - public List messages; - - public CaptureSocket() - { - messages = new ArrayList(); - } - - public boolean isConnected() - { - if (conn == null) - { - return false; - } - return conn.isOpen(); - } - - public void onMessage(String data) - { - System.out.printf("Received Message \"%s\" [size %d]%n", data, data.length()); - messages.add(data); - } - - public void onOpen(Connection connection) - { - this.conn = connection; - } - - public void onClose(int closeCode, String message) - { - this.conn = null; - } - } - - public static class MessageSender implements WebSocket - { - private Connection conn; - private CountDownLatch connectLatch = new CountDownLatch(1); - - public void onOpen(Connection connection) - { - this.conn = connection; - connectLatch.countDown(); - } - - public void onClose(int closeCode, String message) - { - this.conn = null; - } - - public boolean isConnected() - { - if (this.conn == null) - { - return false; - } - return this.conn.isOpen(); - } - - public void sendMessage(String format, Object... args) throws IOException - { - this.conn.sendMessage(String.format(format,args)); - } - - public void awaitConnect() throws InterruptedException - { - connectLatch.await(1,TimeUnit.SECONDS); - } - - public void close() - { - if (this.conn == null) - { - return; - } - this.conn.close(); - } - } - private Server server; private WebSocketCaptureServlet servlet; private URI serverUri; diff --git a/jetty-websocket/src/test/java/org/eclipse/jetty/websocket/helper/CaptureSocket.java b/jetty-websocket/src/test/java/org/eclipse/jetty/websocket/helper/CaptureSocket.java new file mode 100644 index 00000000000..45842596af3 --- /dev/null +++ b/jetty-websocket/src/test/java/org/eclipse/jetty/websocket/helper/CaptureSocket.java @@ -0,0 +1,42 @@ +package org.eclipse.jetty.websocket.helper; + +import java.util.ArrayList; +import java.util.List; + +import org.eclipse.jetty.websocket.WebSocket; + +public class CaptureSocket implements WebSocket, WebSocket.OnTextMessage +{ + private Connection conn; + public List messages; + + public CaptureSocket() + { + messages = new ArrayList(); + } + + public boolean isConnected() + { + if (conn == null) + { + return false; + } + return conn.isOpen(); + } + + public void onMessage(String data) + { + System.out.printf("Received Message \"%s\" [size %d]%n", data, data.length()); + messages.add(data); + } + + public void onOpen(Connection connection) + { + this.conn = connection; + } + + public void onClose(int closeCode, String message) + { + this.conn = null; + } +} \ No newline at end of file diff --git a/jetty-websocket/src/test/java/org/eclipse/jetty/websocket/helper/MessageSender.java b/jetty-websocket/src/test/java/org/eclipse/jetty/websocket/helper/MessageSender.java new file mode 100644 index 00000000000..1a039ceb192 --- /dev/null +++ b/jetty-websocket/src/test/java/org/eclipse/jetty/websocket/helper/MessageSender.java @@ -0,0 +1,52 @@ +package org.eclipse.jetty.websocket.helper; + +import java.io.IOException; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.TimeUnit; + +import org.eclipse.jetty.websocket.WebSocket; + +public class MessageSender implements WebSocket +{ + private Connection conn; + private CountDownLatch connectLatch = new CountDownLatch(1); + + public void onOpen(Connection connection) + { + this.conn = connection; + connectLatch.countDown(); + } + + public void onClose(int closeCode, String message) + { + this.conn = null; + } + + public boolean isConnected() + { + if (this.conn == null) + { + return false; + } + return this.conn.isOpen(); + } + + public void sendMessage(String format, Object... args) throws IOException + { + this.conn.sendMessage(String.format(format,args)); + } + + public void awaitConnect() throws InterruptedException + { + connectLatch.await(1,TimeUnit.SECONDS); + } + + public void close() + { + if (this.conn == null) + { + return; + } + this.conn.close(); + } +} \ No newline at end of file diff --git a/jetty-websocket/src/test/java/org/eclipse/jetty/websocket/helper/SafariD00.java b/jetty-websocket/src/test/java/org/eclipse/jetty/websocket/helper/SafariD00.java new file mode 100644 index 00000000000..a1370f092a0 --- /dev/null +++ b/jetty-websocket/src/test/java/org/eclipse/jetty/websocket/helper/SafariD00.java @@ -0,0 +1,129 @@ +package org.eclipse.jetty.websocket.helper; + +import static org.hamcrest.Matchers.*; + +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.io.OutputStream; +import java.io.UnsupportedEncodingException; +import java.net.InetSocketAddress; +import java.net.Socket; +import java.net.SocketAddress; +import java.net.URI; + +import org.eclipse.jetty.io.ByteArrayBuffer; +import org.eclipse.jetty.util.TypeUtil; +import org.junit.Assert; + +public class SafariD00 +{ + private URI uri; + private SocketAddress endpoint; + private Socket socket; + private OutputStream out; + private InputStream in; + + public SafariD00(URI uri) + { + this.uri = uri; + this.endpoint = new InetSocketAddress(uri.getHost(),uri.getPort()); + } + + /** + * Open the Socket to the destination endpoint and + * + * @return the open java Socket. + * @throws IOException + */ + public Socket connect() throws IOException + { + socket = new Socket(); + socket.connect(endpoint,1000); + + out = socket.getOutputStream(); + in = socket.getInputStream(); + + return socket; + } + + /** + * Issue an Http websocket (Draft-0) upgrade request using the Safari particulars. + * + * @throws UnsupportedEncodingException + */ + public void issueHandshake() throws IOException + { + StringBuilder req = new StringBuilder(); + req.append("GET ").append(uri.getPath()).append(" HTTP/1.1\r\n"); + req.append("Upgrade: WebSocket\r\n"); + req.append("Connection: Upgrade\r\n"); + req.append("Host: ").append(uri.getHost()).append(":").append(uri.getPort()).append("\r\n"); + req.append("Origin: http://www.google.com/\r\n"); + req.append("Sec-WebSocket-Key1: 15{ft :6@87 0 M 5 c901\r\n"); + req.append("Sec-WebSocket-Key2: 3? C;7~0 8 \" 3 2105 6 `_ {\r\n"); + req.append("\r\n"); + + System.out.printf("--- Request ---%n%s",req); + + byte reqBytes[] = req.toString().getBytes("UTF-8"); + byte hixieBytes[] = TypeUtil.fromHexString("e739617916c9daf3"); + byte buf[] = new byte[reqBytes.length + hixieBytes.length]; + System.arraycopy(reqBytes,0,buf,0,reqBytes.length); + System.arraycopy(hixieBytes,0,buf,reqBytes.length,hixieBytes.length); + + // Send HTTP GET Request (with hixie bytes) + out.write(buf,0,buf.length); + out.flush(); + + // Read HTTP 101 Upgrade / Handshake Response + InputStreamReader reader = new InputStreamReader(in); + BufferedReader br = new BufferedReader(reader); + + boolean foundEnd = false; + String line; + while (!foundEnd) + { + line = br.readLine(); + System.out.printf("RESP: %s%n",line); + if (line.length() == 0) + { + foundEnd = true; + } + } + + // Read expected handshake hixie bytes + byte hixieHandshakeExpected[] = TypeUtil.fromHexString("c7438d956cf611a6af70603e6fa54809"); + byte hixieHandshake[] = new byte[hixieHandshakeExpected.length]; + + int readLen = in.read(hixieHandshake,0,hixieHandshake.length); + Assert.assertThat("Read hixie handshake bytes",readLen,is(hixieHandshake.length)); + } + + public void sendMessage(String... msgs) throws IOException + { + int len = 0; + for (String msg : msgs) + { + len += (msg.length() + 2); + } + + ByteArrayBuffer buf = new ByteArrayBuffer(len); + + for (String msg : msgs) + { + buf.put((byte)0x00); + buf.put(msg.getBytes("UTF-8")); + buf.put((byte)0xFF); + } + + out.write(buf.array()); + out.flush(); + } + + public void disconnect() throws IOException + { + socket.close(); + } +} diff --git a/jetty-websocket/src/test/java/org/eclipse/jetty/websocket/helper/WebSocketCaptureServlet.java b/jetty-websocket/src/test/java/org/eclipse/jetty/websocket/helper/WebSocketCaptureServlet.java new file mode 100644 index 00000000000..706c4bd7eb4 --- /dev/null +++ b/jetty-websocket/src/test/java/org/eclipse/jetty/websocket/helper/WebSocketCaptureServlet.java @@ -0,0 +1,31 @@ +package org.eclipse.jetty.websocket.helper; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; + +import javax.servlet.ServletException; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +import org.eclipse.jetty.websocket.WebSocket; +import org.eclipse.jetty.websocket.WebSocketServlet; + +@SuppressWarnings("serial") +public class WebSocketCaptureServlet extends WebSocketServlet +{ + public List captures = new ArrayList();; + + @Override + protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException + { + resp.sendError(404); + } + + public WebSocket doWebSocketConnect(HttpServletRequest request, String protocol) + { + CaptureSocket capture = new CaptureSocket(); + captures.add(capture); + return capture; + } +} \ No newline at end of file