diff --git a/jetty-http/src/main/java/org/eclipse/jetty/http/HttpParser.java b/jetty-http/src/main/java/org/eclipse/jetty/http/HttpParser.java index 239b44f0dfb..39cf670d5db 100644 --- a/jetty-http/src/main/java/org/eclipse/jetty/http/HttpParser.java +++ b/jetty-http/src/main/java/org/eclipse/jetty/http/HttpParser.java @@ -652,6 +652,9 @@ public class HttpParser case CACHE_CONTROL: case USER_AGENT: add_to_connection_trie=_connectionFields!=null && _field==null; + break; + + default: break; } if (add_to_connection_trie && !_connectionFields.isFull() && _header!=null && _valueString!=null) @@ -1089,6 +1092,8 @@ public class HttpParser BufferUtil.clear(buffer); } return false; + default: break; + } // Request/response line @@ -1262,6 +1267,9 @@ public class HttpParser BufferUtil.clear(buffer); return false; } + + default: + break; } } @@ -1340,8 +1348,19 @@ public class HttpParser case CLOSED: case END: break; + + case EOF_CONTENT: + _handler.messageComplete(); + break; + default: - LOG.warn("Closing {}",this); + if (_state.ordinal()>State.END.ordinal()) + { + _handler.earlyEOF(); + _handler.messageComplete(); + } + else + LOG.warn("Closing {}",this); } setState(State.CLOSED); _endOfContent=EndOfContent.UNKNOWN_CONTENT; @@ -1369,6 +1388,7 @@ public class HttpParser /* ------------------------------------------------------------------------------- */ private void setState(State state) { + // LOG.debug("{} --> {}",_state,state); _state=state; } diff --git a/jetty-io/src/main/java/org/eclipse/jetty/io/AbstractConnection.java b/jetty-io/src/main/java/org/eclipse/jetty/io/AbstractConnection.java index a0b090ccb59..3fc9bef3ea6 100644 --- a/jetty-io/src/main/java/org/eclipse/jetty/io/AbstractConnection.java +++ b/jetty-io/src/main/java/org/eclipse/jetty/io/AbstractConnection.java @@ -24,6 +24,7 @@ import java.util.concurrent.Executor; import java.util.concurrent.TimeoutException; import java.util.concurrent.atomic.AtomicReference; +import org.eclipse.jetty.util.BlockingCallback; import org.eclipse.jetty.util.Callback; import org.eclipse.jetty.util.log.Log; import org.eclipse.jetty.util.log.Logger; @@ -112,13 +113,130 @@ public abstract class AbstractConnection implements Connection if (_state.compareAndSet(State.FILLING,State.FILLING_INTERESTED)) break loop; break; + + case FILLING_BLOCKED: + if (_state.compareAndSet(State.FILLING_BLOCKED,State.FILLING_BLOCKED_INTERESTED)) + break loop; + break; + + case BLOCKED: + if (_state.compareAndSet(State.BLOCKED,State.BLOCKED_INTERESTED)) + break loop; + break; + case FILLING_BLOCKED_INTERESTED: + case FILLING_INTERESTED: + case BLOCKED_INTERESTED: + case INTERESTED: + break loop; + } + } + } + + + private void unblock() + { + LOG.debug("unblock {}",this); + + loop:while(true) + { + switch(_state.get()) + { + case FILLING_BLOCKED: + if (_state.compareAndSet(State.FILLING_BLOCKED,State.FILLING)) + break loop; + break; + + case FILLING_BLOCKED_INTERESTED: + if (_state.compareAndSet(State.FILLING_BLOCKED_INTERESTED,State.FILLING_INTERESTED)) + break loop; + break; + + case BLOCKED_INTERESTED: + if (_state.compareAndSet(State.BLOCKED_INTERESTED,State.INTERESTED)) + { + getEndPoint().fillInterested(_readCallback); + break loop; + } + break; + + case BLOCKED: + if (_state.compareAndSet(State.BLOCKED,State.IDLE)) + break loop; + break; + + case FILLING: + case IDLE: case FILLING_INTERESTED: case INTERESTED: break loop; } } } + + + /** + */ + protected void block(final BlockingCallback callback) + { + LOG.debug("block {}",this); + + final Callback blocked=new Callback() + { + @Override + public void succeeded() + { + unblock(); + callback.succeeded(); + } + + @Override + public void failed(Throwable x) + { + unblock(); + callback.failed(x); + } + }; + + loop:while(true) + { + switch(_state.get()) + { + case IDLE: + if (_state.compareAndSet(State.IDLE,State.BLOCKED)) + { + getEndPoint().fillInterested(blocked); + break loop; + } + break; + + case FILLING: + if (_state.compareAndSet(State.FILLING,State.FILLING_BLOCKED)) + { + getEndPoint().fillInterested(blocked); + break loop; + } + break; + + case FILLING_INTERESTED: + if (_state.compareAndSet(State.FILLING_INTERESTED,State.FILLING_BLOCKED_INTERESTED)) + { + getEndPoint().fillInterested(blocked); + break loop; + } + break; + + case BLOCKED: + case BLOCKED_INTERESTED: + case FILLING_BLOCKED: + case FILLING_BLOCKED_INTERESTED: + throw new IllegalStateException("Already Blocked"); + + case INTERESTED: + throw new IllegalStateException(); + } + } + } /** *

Callback method invoked when the endpoint is ready to be read.

@@ -225,7 +343,7 @@ public abstract class AbstractConnection implements Connection private enum State { - IDLE, INTERESTED, FILLING, FILLING_INTERESTED + IDLE, INTERESTED, FILLING, FILLING_INTERESTED, FILLING_BLOCKED, BLOCKED, FILLING_BLOCKED_INTERESTED, BLOCKED_INTERESTED } private class ReadCallback implements Callback, Runnable @@ -247,12 +365,25 @@ public abstract class AbstractConnection implements Connection { case IDLE: case INTERESTED: - throw new IllegalStateException(); + case BLOCKED: + case BLOCKED_INTERESTED: + LOG.warn(new IllegalStateException()); + return; case FILLING: if (_state.compareAndSet(State.FILLING,State.IDLE)) break loop; break; + + case FILLING_BLOCKED: + if (_state.compareAndSet(State.FILLING_BLOCKED,State.BLOCKED)) + break loop; + break; + + case FILLING_BLOCKED_INTERESTED: + if (_state.compareAndSet(State.FILLING_BLOCKED_INTERESTED,State.BLOCKED_INTERESTED)) + break loop; + break; case FILLING_INTERESTED: if (_state.compareAndSet(State.FILLING_INTERESTED,State.INTERESTED)) @@ -266,7 +397,7 @@ public abstract class AbstractConnection implements Connection } } else - LOG.warn(new Throwable()); + LOG.warn(new IllegalStateException()); } @Override diff --git a/jetty-maven-plugin/src/main/java/org/eclipse/jetty/maven/plugin/AbstractJettyMojo.java b/jetty-maven-plugin/src/main/java/org/eclipse/jetty/maven/plugin/AbstractJettyMojo.java index 9429512905a..558899c1819 100644 --- a/jetty-maven-plugin/src/main/java/org/eclipse/jetty/maven/plugin/AbstractJettyMojo.java +++ b/jetty-maven-plugin/src/main/java/org/eclipse/jetty/maven/plugin/AbstractJettyMojo.java @@ -284,7 +284,7 @@ public abstract class AbstractJettyMojo extends AbstractMojo /** * A wrapper for the Server object */ - protected JettyServer server = JettyServer.getInstance(); + protected JettyServer server = new JettyServer(); /** @@ -494,6 +494,8 @@ public abstract class AbstractJettyMojo extends AbstractMojo String tmp = System.getProperty(PORT_SYSPROPERTY, MavenServerConnector.DEFAULT_PORT_STR); httpConnector.setPort(Integer.parseInt(tmp.trim())); } + if (httpConnector.getServer() == null) + httpConnector.setServer(this.server); this.server.addConnector(httpConnector); } @@ -504,12 +506,13 @@ public abstract class AbstractJettyMojo extends AbstractMojo //if not configured in the pom, create one if (httpConnector == null) { - httpConnector = new MavenServerConnector(); + httpConnector = new MavenServerConnector(); //use any jetty.port settings provided String tmp = System.getProperty(PORT_SYSPROPERTY, MavenServerConnector.DEFAULT_PORT_STR); httpConnector.setPort(Integer.parseInt(tmp.trim())); } - + if (httpConnector.getServer() == null) + httpConnector.setServer(this.server); this.server.setConnectors(new Connector[] {httpConnector}); } diff --git a/jetty-maven-plugin/src/main/java/org/eclipse/jetty/maven/plugin/JettyServer.java b/jetty-maven-plugin/src/main/java/org/eclipse/jetty/maven/plugin/JettyServer.java index 5be4aafff5e..8018052680a 100644 --- a/jetty-maven-plugin/src/main/java/org/eclipse/jetty/maven/plugin/JettyServer.java +++ b/jetty-maven-plugin/src/main/java/org/eclipse/jetty/maven/plugin/JettyServer.java @@ -38,16 +38,6 @@ public class JettyServer extends org.eclipse.jetty.server.Server { public static final JettyServer __instance = new JettyServer(); - /** - * Singleton instance - * @return - */ - public static JettyServer getInstance() - { - return __instance; - } - - private RequestLog requestLog; private ContextHandlerCollection contexts; @@ -56,7 +46,7 @@ public class JettyServer extends org.eclipse.jetty.server.Server /** * */ - private JettyServer() + public JettyServer() { super(); setStopAtShutdown(true); diff --git a/jetty-maven-plugin/src/main/java/org/eclipse/jetty/maven/plugin/MavenServerConnector.java b/jetty-maven-plugin/src/main/java/org/eclipse/jetty/maven/plugin/MavenServerConnector.java index f7c20c5878c..54aedb1303e 100644 --- a/jetty-maven-plugin/src/main/java/org/eclipse/jetty/maven/plugin/MavenServerConnector.java +++ b/jetty-maven-plugin/src/main/java/org/eclipse/jetty/maven/plugin/MavenServerConnector.java @@ -19,21 +19,259 @@ package org.eclipse.jetty.maven.plugin; +import java.util.Collection; +import java.util.List; +import java.util.concurrent.Executor; +import java.util.concurrent.Future; + +import org.eclipse.jetty.io.ByteBufferPool; +import org.eclipse.jetty.io.EndPoint; +import org.eclipse.jetty.server.ConnectionFactory; +import org.eclipse.jetty.server.Connector; +import org.eclipse.jetty.server.Server; import org.eclipse.jetty.server.ServerConnector; +import org.eclipse.jetty.util.annotation.ManagedAttribute; +import org.eclipse.jetty.util.component.AbstractLifeCycle; +import org.eclipse.jetty.util.thread.Scheduler; + + + /** * MavenServerConnector * - * + * As the ServerConnector class does not have a no-arg constructor, and moreover requires + * the server instance passed in to all its constructors, it cannot + * be referenced in the pom.xml. This class wraps a ServerConnector, delaying setting the + * server instance. Only a few of the setters from the ServerConnector class are supported. */ -public class MavenServerConnector extends ServerConnector +public class MavenServerConnector extends AbstractLifeCycle implements Connector { public static int DEFAULT_PORT = 8080; public static String DEFAULT_PORT_STR = String.valueOf(DEFAULT_PORT); public static int DEFAULT_MAX_IDLE_TIME = 30000; + private Server server; + private ServerConnector delegate; + private String host; + private String name; + private int port; + private long idleTimeout; + private int lingerTime; + + public MavenServerConnector() { - super(JettyServer.getInstance()); + } + + public void setServer(Server server) + { + this.server = server; + } + + public void setHost(String host) + { + this.host = host; + } + + public String getHost() + { + return this.host; + } + + public void setPort(int port) + { + this.port = port; + } + + public int getPort () + { + return this.port; + } + + public void setName (String name) + { + this.name = name; + } + + public void setIdleTimeout(long idleTimeout) + { + this.idleTimeout = idleTimeout; + } + + public void setSoLingerTime(int lingerTime) + { + this.lingerTime = lingerTime; + } + + @Override + protected void doStart() throws Exception + { + + if (this.server == null) + throw new IllegalStateException("Server not set for MavenServerConnector"); + + this.delegate = new ServerConnector(this.server); + this.delegate.setName(this.name); + this.delegate.setPort(this.port); + this.delegate.setHost(this.host); + this.delegate.setIdleTimeout(idleTimeout); + this.delegate.setSoLingerTime(lingerTime); + this.delegate.start(); + + super.doStart(); + } + + @Override + protected void doStop() throws Exception + { + this.delegate.stop(); + super.doStop(); + this.delegate = null; + } + + /** + * @see org.eclipse.jetty.util.component.Graceful#shutdown() + */ + @Override + public Future shutdown() + { + checkDelegate(); + return this.delegate.shutdown(); + } + + /** + * @see org.eclipse.jetty.server.Connector#getServer() + */ + @Override + public Server getServer() + { + return this.server; + } + + /** + * @see org.eclipse.jetty.server.Connector#getExecutor() + */ + @Override + public Executor getExecutor() + { + checkDelegate(); + return this.delegate.getExecutor(); + } + + /** + * @see org.eclipse.jetty.server.Connector#getScheduler() + */ + @Override + public Scheduler getScheduler() + { + checkDelegate(); + return this.delegate.getScheduler(); + } + + /** + * @see org.eclipse.jetty.server.Connector#getByteBufferPool() + */ + @Override + public ByteBufferPool getByteBufferPool() + { + checkDelegate(); + return this.delegate.getByteBufferPool(); + } + + /** + * @see org.eclipse.jetty.server.Connector#getConnectionFactory(java.lang.String) + */ + @Override + public ConnectionFactory getConnectionFactory(String nextProtocol) + { + checkDelegate(); + return this.delegate.getConnectionFactory(nextProtocol); + } + + /** + * @see org.eclipse.jetty.server.Connector#getConnectionFactory(java.lang.Class) + */ + @Override + public T getConnectionFactory(Class factoryType) + { + checkDelegate(); + return this.delegate.getConnectionFactory(factoryType); + } + + /** + * @see org.eclipse.jetty.server.Connector#getDefaultConnectionFactory() + */ + @Override + public ConnectionFactory getDefaultConnectionFactory() + { + checkDelegate(); + return getDefaultConnectionFactory(); + } + + /** + * @see org.eclipse.jetty.server.Connector#getConnectionFactories() + */ + @Override + public Collection getConnectionFactories() + { + checkDelegate(); + return this.delegate.getConnectionFactories(); + } + + /** + * @see org.eclipse.jetty.server.Connector#getProtocols() + */ + @Override + public List getProtocols() + { + checkDelegate(); + return this.delegate.getProtocols(); + } + + /** + * @see org.eclipse.jetty.server.Connector#getIdleTimeout() + */ + @Override + @ManagedAttribute("maximum time a connection can be idle before being closed (in ms)") + public long getIdleTimeout() + { + checkDelegate(); + return this.delegate.getIdleTimeout(); + } + + /** + * @see org.eclipse.jetty.server.Connector#getTransport() + */ + @Override + public Object getTransport() + { + checkDelegate(); + return this.delegate.getTransport(); + } + + /** + * @see org.eclipse.jetty.server.Connector#getConnectedEndPoints() + */ + @Override + public Collection getConnectedEndPoints() + { + checkDelegate(); + return this.delegate.getConnectedEndPoints(); + } + + /** + * @see org.eclipse.jetty.server.Connector#getName() + */ + @Override + public String getName() + { + return this.name; + } + + private void checkDelegate() throws IllegalStateException + { + if (this.delegate == null) + throw new IllegalStateException ("MavenServerConnector delegate not ready"); } } diff --git a/jetty-maven-plugin/src/main/java/org/eclipse/jetty/maven/plugin/Starter.java b/jetty-maven-plugin/src/main/java/org/eclipse/jetty/maven/plugin/Starter.java index 649b8477d5a..4d56d0ffd05 100644 --- a/jetty-maven-plugin/src/main/java/org/eclipse/jetty/maven/plugin/Starter.java +++ b/jetty-maven-plugin/src/main/java/org/eclipse/jetty/maven/plugin/Starter.java @@ -55,7 +55,7 @@ public class Starter private List jettyXmls; // list of jetty.xml config files to apply - Mandatory private File contextXml; //name of context xml file to configure the webapp - Mandatory - private JettyServer server; + private JettyServer server = new JettyServer(); private JettyWebAppContext webApp; @@ -120,8 +120,6 @@ public class Starter { LOG.debug("Starting Jetty Server ..."); - this.server = JettyServer.getInstance(); - //apply any configs from jetty.xml files first applyJettyXml (); @@ -132,6 +130,7 @@ public class Starter { //if a SystemProperty -Djetty.port= has been supplied, use that as the default port MavenServerConnector httpConnector = new MavenServerConnector(); + httpConnector.setServer(this.server); String tmp = System.getProperty(PORT_SYSPROPERTY, MavenServerConnector.DEFAULT_PORT_STR); httpConnector.setPort(Integer.parseInt(tmp.trim())); connectors = new Connector[] {httpConnector}; diff --git a/jetty-runner/pom.xml b/jetty-runner/pom.xml index 0f6dff63647..3ddb4caed02 100644 --- a/jetty-runner/pom.xml +++ b/jetty-runner/pom.xml @@ -27,9 +27,7 @@ ** - **/MANIFEST.MF - **/ECLIPSEF.RSA - **/ECLIPSEF.SF + **/MANIFEST.MF,META-INF/*.RSA,META-INF/*.DSA,META-INF/*.SF ${project.build.directory}/classes false true diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/HttpConnection.java b/jetty-server/src/main/java/org/eclipse/jetty/server/HttpConnection.java index 69433f5e3ab..17f09a7b913 100644 --- a/jetty-server/src/main/java/org/eclipse/jetty/server/HttpConnection.java +++ b/jetty-server/src/main/java/org/eclipse/jetty/server/HttpConnection.java @@ -207,8 +207,29 @@ public class HttpConnection extends AbstractConnection implements Runnable, Http // Can the parser progress (even with an empty buffer) boolean call_channel=_parser.parseNext(_requestBuffer==null?BufferUtil.EMPTY_BUFFER:_requestBuffer); - // If there is a request buffer, we are re-entering here - if (!call_channel && BufferUtil.isEmpty(_requestBuffer)) + // Parse the buffer + if (call_channel) + { + // Parse as much content as there is available before calling the channel + // this is both efficient (may queue many chunks), will correctly set available for 100 continues + // and will drive the parser to completion if all content is available. + while (_parser.inContentState()) + { + if (!_parser.parseNext(_requestBuffer==null?BufferUtil.EMPTY_BUFFER:_requestBuffer)) + break; + } + + // The parser returned true, which indicates the channel is ready to handle a request. + // Call the channel and this will either handle the request/response to completion OR, + // if the request suspends, the request/response will be incomplete so the outer loop will exit. + + _channel.run(); + + // Return if suspended or upgraded + if (_channel.getState().isSuspended() || getEndPoint().getConnection()!=this) + return; + } + else if (BufferUtil.isEmpty(_requestBuffer)) { if (_requestBuffer == null) _requestBuffer = _bufferPool.acquire(getInputBufferSize(), REQUEST_BUFFER_DIRECT); @@ -242,33 +263,15 @@ public class HttpConnection extends AbstractConnection implements Runnable, Http releaseRequestBuffer(); return; } - - // Parse what we have read - call_channel=_parser.parseNext(_requestBuffer); } - - // Parse the buffer - if (call_channel) + else { - // Parse as much content as there is available before calling the channel - // this is both efficient (may queue many chunks), will correctly set available for 100 continues - // and will drive the parser to completion if all content is available. - while (_parser.inContentState()) - { - if (!_parser.parseNext(_requestBuffer==null?BufferUtil.EMPTY_BUFFER:_requestBuffer)) - break; - } - - // The parser returned true, which indicates the channel is ready to handle a request. - // Call the channel and this will either handle the request/response to completion OR, - // if the request suspends, the request/response will be incomplete so the outer loop will exit. - - _channel.run(); - - // Return if suspended or upgraded - if (_channel.getState().isSuspended() || getEndPoint().getConnection()!=this) - return; - } + // TODO work out how we can get here and a better way to handle it + LOG.warn("Unexpected state: "+this+ " "+_channel+" "+_channel.getRequest()); + if (!_channel.getState().isSuspended()) + getEndPoint().close(); + return; + } } } catch (EofException e) @@ -547,7 +550,7 @@ public class HttpConnection extends AbstractConnection implements Runnable, Http // need to call blockForContent again while (event && BufferUtil.hasContent(_requestBuffer) && _parser.inContentState()) _parser.parseNext(_requestBuffer); - + // If we have an event, return if (event) return; @@ -563,7 +566,7 @@ public class HttpConnection extends AbstractConnection implements Runnable, Http } // Wait until we can read - getEndPoint().fillInterested(_readBlocker); + block(_readBlocker); LOG.debug("{} block readable on {}",this,_readBlocker); _readBlocker.block(); diff --git a/jetty-servlet/src/test/java/org/eclipse/jetty/servlet/AsyncServletTest.java b/jetty-servlet/src/test/java/org/eclipse/jetty/servlet/AsyncServletTest.java index d5ad4ee4522..c2703fe7799 100644 --- a/jetty-servlet/src/test/java/org/eclipse/jetty/servlet/AsyncServletTest.java +++ b/jetty-servlet/src/test/java/org/eclipse/jetty/servlet/AsyncServletTest.java @@ -19,7 +19,6 @@ package org.eclipse.jetty.servlet; import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertTrue; import java.io.IOException; import java.io.InputStream; @@ -345,6 +344,45 @@ public class AsyncServletTest Assert.assertThat(response,Matchers.not(Matchers.containsString(content))); } + + @Test + public void testAsyncRead() throws Exception + { + String header="GET /ctx/path/info?suspend=2000&resume=1500 HTTP/1.1\r\n"+ + "Host: localhost\r\n"+ + "Content-Length: 10\r\n"+ + "\r\n"; + String body="12345678\r\n"; + String close="GET /ctx/path/info HTTP/1.1\r\n"+ + "Host: localhost\r\n"+ + "Connection: close\r\n"+ + "\r\n"; + + try (Socket socket = new Socket("localhost",_port);) + { + socket.setSoTimeout(10000); + socket.getOutputStream().write(header.getBytes("ISO-8859-1")); + Thread.sleep(500); + socket.getOutputStream().write(body.getBytes("ISO-8859-1"),0,2); + Thread.sleep(500); + socket.getOutputStream().write(body.getBytes("ISO-8859-1"),2,8); + socket.getOutputStream().write(close.getBytes("ISO-8859-1")); + + String response = IO.toString(socket.getInputStream()); + assertEquals("HTTP/1.1 200 OK",response.substring(0,15)); + assertContains( + "history: REQUEST\r\n"+ + "history: initial\r\n"+ + "history: suspend\r\n"+ + "history: async-read=10\r\n"+ + "history: resume\r\n"+ + "history: ASYNC\r\n"+ + "history: !initial\r\n"+ + "history: onComplete\r\n",response); + } + } + + public synchronized String process(String query,String content) throws Exception { String request = "GET /ctx/path/info"; @@ -364,9 +402,8 @@ public class AsyncServletTest int port=_port; String response=null; - try + try (Socket socket = new Socket("localhost",port);) { - Socket socket = new Socket("localhost",port); socket.setSoTimeout(1000000); socket.getOutputStream().write(request.getBytes("UTF-8")); @@ -379,11 +416,10 @@ public class AsyncServletTest throw e; } - // System.err.println(response); - return response; } - + + private static class AsyncServlet extends HttpServlet @@ -429,7 +465,7 @@ public class AsyncServletTest if (request.getDispatcherType()==DispatcherType.REQUEST) { - ((HttpServletResponse)response).addHeader("history","initial"); + response.addHeader("history","initial"); if (read_before>0) { byte[] buf=new byte[read_before]; @@ -442,6 +478,30 @@ public class AsyncServletTest while(b!=-1) b=in.read(); } + else if (request.getContentLength()>0) + { + new Thread() + { + @Override + public void run() + { + int c=0; + try + { + InputStream in=request.getInputStream(); + int b=0; + while(b!=-1) + if((b=in.read())>=0) + c++; + response.addHeader("history","async-read="+c); + } + catch(Exception e) + { + e.printStackTrace(); + } + } + }.start(); + } if (suspend_for>=0) { @@ -449,7 +509,7 @@ public class AsyncServletTest if (suspend_for>0) async.setTimeout(suspend_for); async.addListener(__listener); - ((HttpServletResponse)response).addHeader("history","suspend"); + response.addHeader("history","suspend"); if (complete_after>0) { @@ -527,7 +587,7 @@ public class AsyncServletTest } else { - ((HttpServletResponse)response).addHeader("history","!initial"); + response.addHeader("history","!initial"); if (suspend2_for>=0 && request.getAttribute("2nd")==null) { @@ -540,7 +600,7 @@ public class AsyncServletTest async.setTimeout(suspend2_for); } // continuation.addContinuationListener(__listener); - ((HttpServletResponse)response).addHeader("history","suspend"); + response.addHeader("history","suspend"); if (complete2_after>0) { @@ -581,7 +641,7 @@ public class AsyncServletTest @Override public void run() { - ((HttpServletResponse)response).addHeader("history","resume"); + response.addHeader("history","resume"); async.dispatch(); } }; @@ -592,7 +652,7 @@ public class AsyncServletTest } else if (resume2_after==0) { - ((HttpServletResponse)response).addHeader("history","dispatch"); + response.addHeader("history","dispatch"); async.dispatch(); } } @@ -633,15 +693,11 @@ public class AsyncServletTest @Override public void onStartAsync(AsyncEvent event) throws IOException { - // TODO Auto-generated method stub - } @Override public void onError(AsyncEvent event) throws IOException { - // TODO Auto-generated method stub - } @Override diff --git a/jetty-spdy/spdy-core/src/main/java/org/eclipse/jetty/spdy/PushSynInfo.java b/jetty-spdy/spdy-core/src/main/java/org/eclipse/jetty/spdy/PushSynInfo.java index 7f5030441e6..de411b476cb 100644 --- a/jetty-spdy/spdy-core/src/main/java/org/eclipse/jetty/spdy/PushSynInfo.java +++ b/jetty-spdy/spdy-core/src/main/java/org/eclipse/jetty/spdy/PushSynInfo.java @@ -32,7 +32,7 @@ public class PushSynInfo extends SynInfo private int associatedStreamId; public PushSynInfo(int associatedStreamId, PushInfo pushInfo){ - super(pushInfo.getHeaders(), pushInfo.isClose()); + super(pushInfo.getTimeout(), pushInfo.getUnit(), pushInfo.getHeaders(), pushInfo.isClose(), (byte)0); this.associatedStreamId = associatedStreamId; } diff --git a/jetty-spdy/spdy-http-server/src/main/java/org/eclipse/jetty/spdy/server/http/HttpTransportOverSPDY.java b/jetty-spdy/spdy-http-server/src/main/java/org/eclipse/jetty/spdy/server/http/HttpTransportOverSPDY.java index c98633d580e..11eecba5871 100644 --- a/jetty-spdy/spdy-http-server/src/main/java/org/eclipse/jetty/spdy/server/http/HttpTransportOverSPDY.java +++ b/jetty-spdy/spdy-http-server/src/main/java/org/eclipse/jetty/spdy/server/http/HttpTransportOverSPDY.java @@ -284,8 +284,8 @@ public class HttpTransportOverSPDY implements HttpTransport Fields pushHeaders = createPushHeaders(scheme, host, pushResource); final Fields pushRequestHeaders = createRequestHeaders(scheme, host, uri, pushResource); - // TODO: handle the timeout better - stream.push(new PushInfo(0, TimeUnit.MILLISECONDS, pushHeaders, false), new Promise.Adapter() + stream.push(new PushInfo(pushHeaders, false), + new Promise.Adapter() { @Override public void succeeded(Stream pushStream) diff --git a/jetty-websocket/websocket-client/src/main/java/org/eclipse/jetty/websocket/client/ClientUpgradeResponse.java b/jetty-websocket/websocket-client/src/main/java/org/eclipse/jetty/websocket/client/ClientUpgradeResponse.java index 7c9e971cb93..2751fc84bf4 100644 --- a/jetty-websocket/websocket-client/src/main/java/org/eclipse/jetty/websocket/client/ClientUpgradeResponse.java +++ b/jetty-websocket/websocket-client/src/main/java/org/eclipse/jetty/websocket/client/ClientUpgradeResponse.java @@ -22,8 +22,9 @@ import java.io.IOException; import java.nio.ByteBuffer; import org.eclipse.jetty.websocket.api.UpgradeResponse; +import org.eclipse.jetty.websocket.common.io.http.HttpResponseHeaderParseListener; -public class ClientUpgradeResponse extends UpgradeResponse +public class ClientUpgradeResponse extends UpgradeResponse implements HttpResponseHeaderParseListener { private ByteBuffer remainingBuffer; @@ -43,6 +44,7 @@ public class ClientUpgradeResponse extends UpgradeResponse throw new UnsupportedOperationException("Not supported on client implementation"); } + @Override public void setRemainingBuffer(ByteBuffer remainingBuffer) { this.remainingBuffer = remainingBuffer; diff --git a/jetty-websocket/websocket-client/src/main/java/org/eclipse/jetty/websocket/client/io/UpgradeConnection.java b/jetty-websocket/websocket-client/src/main/java/org/eclipse/jetty/websocket/client/io/UpgradeConnection.java index b37a02a4a83..c24a17b6f9c 100644 --- a/jetty-websocket/websocket-client/src/main/java/org/eclipse/jetty/websocket/client/io/UpgradeConnection.java +++ b/jetty-websocket/websocket-client/src/main/java/org/eclipse/jetty/websocket/client/io/UpgradeConnection.java @@ -39,11 +39,12 @@ import org.eclipse.jetty.websocket.api.WebSocketPolicy; import org.eclipse.jetty.websocket.api.extensions.ExtensionConfig; import org.eclipse.jetty.websocket.client.ClientUpgradeRequest; import org.eclipse.jetty.websocket.client.ClientUpgradeResponse; -import org.eclipse.jetty.websocket.client.io.HttpResponseHeaderParser.ParseException; import org.eclipse.jetty.websocket.common.AcceptHash; import org.eclipse.jetty.websocket.common.WebSocketSession; import org.eclipse.jetty.websocket.common.events.EventDriver; import org.eclipse.jetty.websocket.common.extensions.ExtensionStack; +import org.eclipse.jetty.websocket.common.io.http.HttpResponseHeaderParser; +import org.eclipse.jetty.websocket.common.io.http.HttpResponseHeaderParser.ParseException; /** * This is the initial connection handling that exists immediately after physical connection is established to destination server. @@ -92,7 +93,7 @@ public class UpgradeConnection extends AbstractConnection this.request = connectPromise.getRequest(); // Setup the parser - this.parser = new HttpResponseHeaderParser(); + this.parser = new HttpResponseHeaderParser(new ClientUpgradeResponse()); } public void disconnect(boolean onlyOutput) @@ -173,7 +174,7 @@ public class UpgradeConnection extends AbstractConnection { LOG.debug("Filled {} bytes - {}",filled,BufferUtil.toDetailString(buffer)); } - ClientUpgradeResponse resp = parser.parse(buffer); + ClientUpgradeResponse resp = (ClientUpgradeResponse)parser.parse(buffer); if (resp != null) { // Got a response! diff --git a/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/io/http/HttpResponseHeaderParseListener.java b/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/io/http/HttpResponseHeaderParseListener.java new file mode 100644 index 00000000000..6cb2ae99dcb --- /dev/null +++ b/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/io/http/HttpResponseHeaderParseListener.java @@ -0,0 +1,32 @@ +// +// ======================================================================== +// Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd. +// ------------------------------------------------------------------------ +// All rights reserved. This program and the accompanying materials +// are made available under the terms of the Eclipse Public License v1.0 +// and Apache License v2.0 which accompanies this distribution. +// +// The Eclipse Public License is available at +// http://www.eclipse.org/legal/epl-v10.html +// +// The Apache License v2.0 is available at +// http://www.opensource.org/licenses/apache2.0.php +// +// You may elect to redistribute this code under either of these licenses. +// ======================================================================== +// + +package org.eclipse.jetty.websocket.common.io.http; + +import java.nio.ByteBuffer; + +public interface HttpResponseHeaderParseListener +{ + void addHeader(String name, String value); + + void setRemainingBuffer(ByteBuffer copy); + + void setStatusCode(int statusCode); + + void setStatusReason(String statusReason); +} \ No newline at end of file diff --git a/jetty-websocket/websocket-client/src/main/java/org/eclipse/jetty/websocket/client/io/HttpResponseHeaderParser.java b/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/io/http/HttpResponseHeaderParser.java similarity index 79% rename from jetty-websocket/websocket-client/src/main/java/org/eclipse/jetty/websocket/client/io/HttpResponseHeaderParser.java rename to jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/io/http/HttpResponseHeaderParser.java index 8b67dfdef44..b1b01c9879f 100644 --- a/jetty-websocket/websocket-client/src/main/java/org/eclipse/jetty/websocket/client/io/HttpResponseHeaderParser.java +++ b/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/io/http/HttpResponseHeaderParser.java @@ -16,7 +16,7 @@ // ======================================================================== // -package org.eclipse.jetty.websocket.client.io; +package org.eclipse.jetty.websocket.common.io.http; import java.nio.ByteBuffer; import java.util.regex.Matcher; @@ -25,7 +25,6 @@ import java.util.regex.Pattern; import org.eclipse.jetty.util.BufferUtil; import org.eclipse.jetty.util.StringUtil; import org.eclipse.jetty.util.Utf8LineParser; -import org.eclipse.jetty.websocket.client.ClientUpgradeResponse; /** * Responsible for reading UTF8 Response Header lines and parsing them into a provided UpgradeResponse object. @@ -56,12 +55,13 @@ public class HttpResponseHeaderParser private static final Pattern PAT_HEADER = Pattern.compile("([^:]+):\\s*(.*)"); private static final Pattern PAT_STATUS_LINE = Pattern.compile("^HTTP/1.[01]\\s+(\\d+)\\s+(.*)",Pattern.CASE_INSENSITIVE); - private ClientUpgradeResponse response; - private Utf8LineParser lineParser; + private final HttpResponseHeaderParseListener listener; + private final Utf8LineParser lineParser; private State state; - public HttpResponseHeaderParser() + public HttpResponseHeaderParser(HttpResponseHeaderParseListener listener) { + this.listener = listener; this.lineParser = new Utf8LineParser(); this.state = State.STATUS_LINE; } @@ -71,7 +71,7 @@ public class HttpResponseHeaderParser return (state == State.END); } - public ClientUpgradeResponse parse(ByteBuffer buf) throws ParseException + public HttpResponseHeaderParseListener parse(ByteBuffer buf) throws ParseException { while (!isDone() && (buf.remaining() > 0)) { @@ -84,8 +84,8 @@ public class HttpResponseHeaderParser ByteBuffer copy = ByteBuffer.allocate(buf.remaining()); BufferUtil.put(buf,copy); BufferUtil.flipToFlush(copy,0); - this.response.setRemainingBuffer(copy); - return this.response; + this.listener.setRemainingBuffer(copy); + return listener; } } } @@ -98,22 +98,21 @@ public class HttpResponseHeaderParser { case STATUS_LINE: { - this.response = new ClientUpgradeResponse(); Matcher mat = PAT_STATUS_LINE.matcher(line); if (!mat.matches()) { - throw new ParseException("Unexpected HTTP upgrade response status line [" + line + "]"); + throw new ParseException("Unexpected HTTP response status line [" + line + "]"); } try { - response.setStatusCode(Integer.parseInt(mat.group(1))); + listener.setStatusCode(Integer.parseInt(mat.group(1))); } catch (NumberFormatException e) { - throw new ParseException("Unexpected HTTP upgrade response status code",e); + throw new ParseException("Unexpected HTTP response status code",e); } - response.setStatusReason(mat.group(2)); + listener.setStatusReason(mat.group(2)); state = State.HEADER; break; } @@ -130,8 +129,8 @@ public class HttpResponseHeaderParser { String headerName = header.group(1); String headerValue = header.group(2); - // TODO: need to split header/value if comma delimited - response.addHeader(headerName,headerValue); + // do need to split header/value if comma delimited? + listener.addHeader(headerName,headerValue); } break; } diff --git a/jetty-websocket/websocket-client/src/test/java/org/eclipse/jetty/websocket/client/internal/io/HttpResponseHeaderParserTest.java b/jetty-websocket/websocket-common/src/test/java/org/eclipse/jetty/websocket/common/io/http/HttpResponseHeaderParserTest.java similarity index 53% rename from jetty-websocket/websocket-client/src/test/java/org/eclipse/jetty/websocket/client/internal/io/HttpResponseHeaderParserTest.java rename to jetty-websocket/websocket-common/src/test/java/org/eclipse/jetty/websocket/common/io/http/HttpResponseHeaderParserTest.java index d2f882216f3..837cf10bcbe 100644 --- a/jetty-websocket/websocket-client/src/test/java/org/eclipse/jetty/websocket/client/internal/io/HttpResponseHeaderParserTest.java +++ b/jetty-websocket/websocket-common/src/test/java/org/eclipse/jetty/websocket/common/io/http/HttpResponseHeaderParserTest.java @@ -16,9 +16,10 @@ // ======================================================================== // -package org.eclipse.jetty.websocket.client.internal.io; +package org.eclipse.jetty.websocket.common.io.http; import static org.hamcrest.Matchers.*; +import static org.junit.Assert.*; import java.nio.ByteBuffer; import java.util.ArrayList; @@ -27,8 +28,6 @@ import java.util.List; import org.eclipse.jetty.toolchain.test.TestTracker; import org.eclipse.jetty.util.BufferUtil; import org.eclipse.jetty.util.StringUtil; -import org.eclipse.jetty.websocket.api.UpgradeResponse; -import org.eclipse.jetty.websocket.client.io.HttpResponseHeaderParser; import org.junit.Assert; import org.junit.Rule; import org.junit.Test; @@ -43,6 +42,32 @@ public class HttpResponseHeaderParserTest buf.put(ByteBuffer.wrap(StringUtil.getBytes(line,StringUtil.__UTF8))); } + @Test + public void testParseNotFound() + { + StringBuilder resp = new StringBuilder(); + resp.append("HTTP/1.1 404 Not Found\r\n"); + resp.append("Date: Fri, 26 Apr 2013 21:43:08 GMT\r\n"); + resp.append("Content-Type: text/html; charset=ISO-8859-1\r\n"); + resp.append("Cache-Control: must-revalidate,no-cache,no-store\r\n"); + resp.append("Content-Length: 38\r\n"); + resp.append("Server: Jetty(9.0.0.v20130308)\r\n"); + resp.append("\r\n"); + // and some body content + resp.append("What you are looking for is not here\r\n"); + + ByteBuffer buf = BufferUtil.toBuffer(resp.toString(),StringUtil.__UTF8_CHARSET); + + HttpResponseParseCapture capture = new HttpResponseParseCapture(); + HttpResponseHeaderParser parser = new HttpResponseHeaderParser(capture); + assertThat("Parser.parse",parser.parse(buf),notNullValue()); + assertThat("Response.statusCode",capture.getStatusCode(),is(404)); + assertThat("Response.statusReason",capture.getStatusReason(),is("Not Found")); + assertThat("Response.headers[Content-Length]",capture.getHeader("Content-Length"),is("38")); + + assertThat("Response.remainingBuffer",capture.getRemainingBuffer().remaining(),is(38)); + } + @Test public void testParseRealWorldResponse() { @@ -73,14 +98,14 @@ public class HttpResponseHeaderParserTest BufferUtil.flipToFlush(buf,0); // Parse Buffer - HttpResponseHeaderParser parser = new HttpResponseHeaderParser(); - UpgradeResponse response = parser.parse(buf); - Assert.assertThat("Response",response,notNullValue()); + HttpResponseParseCapture capture = new HttpResponseParseCapture(); + HttpResponseHeaderParser parser = new HttpResponseHeaderParser(capture); + assertThat("Parser.parse",parser.parse(buf),notNullValue()); - Assert.assertThat("Response.statusCode",response.getStatusCode(),is(200)); - Assert.assertThat("Response.statusReason",response.getStatusReason(),is("OK")); + Assert.assertThat("Response.statusCode",capture.getStatusCode(),is(200)); + Assert.assertThat("Response.statusReason",capture.getStatusReason(),is("OK")); - Assert.assertThat("Response.header[age]",response.getHeader("age"),is("518097")); + Assert.assertThat("Response.header[age]",capture.getHeader("age"),is("518097")); } @Test @@ -122,24 +147,47 @@ public class HttpResponseHeaderParserTest small3.position(70); // Parse Buffer - HttpResponseHeaderParser parser = new HttpResponseHeaderParser(); - UpgradeResponse response; + HttpResponseParseCapture capture = new HttpResponseParseCapture(); + HttpResponseHeaderParser parser = new HttpResponseHeaderParser(capture); + assertThat("Parser.parse",parser.parse(buf),notNullValue()); // Parse small 1 - response = parser.parse(small1); - Assert.assertThat("Small 1",response,nullValue()); + Assert.assertThat("Small 1",parser.parse(small1),nullValue()); // Parse small 2 - response = parser.parse(small2); - Assert.assertThat("Small 2",response,nullValue()); + Assert.assertThat("Small 2",parser.parse(small2),nullValue()); // Parse small 3 - response = parser.parse(small3); - Assert.assertThat("Small 3",response,notNullValue()); + Assert.assertThat("Small 3",parser.parse(small3),notNullValue()); - Assert.assertThat("Response.statusCode",response.getStatusCode(),is(200)); - Assert.assertThat("Response.statusReason",response.getStatusReason(),is("OK")); + Assert.assertThat("Response.statusCode",capture.getStatusCode(),is(200)); + Assert.assertThat("Response.statusReason",capture.getStatusReason(),is("OK")); - Assert.assertThat("Response.header[age]",response.getHeader("age"),is("518097")); + Assert.assertThat("Response.header[age]",capture.getHeader("age"),is("518097")); + } + + @Test + public void testParseUpgrade() + { + // Example from RFC6455 - Section 1.2 (Protocol Overview) + StringBuilder resp = new StringBuilder(); + resp.append("HTTP/1.1 101 Switching Protocols\r\n"); + resp.append("Upgrade: websocket\r\n"); + resp.append("Connection: Upgrade\r\n"); + resp.append("Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo=\r\n"); + resp.append("Sec-WebSocket-Protocol: chat\r\n"); + resp.append("\r\n"); + + ByteBuffer buf = BufferUtil.toBuffer(resp.toString(),StringUtil.__UTF8_CHARSET); + + HttpResponseParseCapture capture = new HttpResponseParseCapture(); + HttpResponseHeaderParser parser = new HttpResponseHeaderParser(capture); + assertThat("Parser.parse",parser.parse(buf),notNullValue()); + assertThat("Response.statusCode",capture.getStatusCode(),is(101)); + assertThat("Response.statusReason",capture.getStatusReason(),is("Switching Protocols")); + assertThat("Response.headers[Upgrade]",capture.getHeader("Upgrade"),is("websocket")); + assertThat("Response.headers[Connection]",capture.getHeader("Connection"),is("Upgrade")); + + assertThat("Buffer.remaining",buf.remaining(),is(0)); } } diff --git a/jetty-websocket/websocket-common/src/test/java/org/eclipse/jetty/websocket/common/io/http/HttpResponseParseCapture.java b/jetty-websocket/websocket-common/src/test/java/org/eclipse/jetty/websocket/common/io/http/HttpResponseParseCapture.java new file mode 100644 index 00000000000..a7973e6e5cd --- /dev/null +++ b/jetty-websocket/websocket-common/src/test/java/org/eclipse/jetty/websocket/common/io/http/HttpResponseParseCapture.java @@ -0,0 +1,76 @@ +// +// ======================================================================== +// Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd. +// ------------------------------------------------------------------------ +// All rights reserved. This program and the accompanying materials +// are made available under the terms of the Eclipse Public License v1.0 +// and Apache License v2.0 which accompanies this distribution. +// +// The Eclipse Public License is available at +// http://www.eclipse.org/legal/epl-v10.html +// +// The Apache License v2.0 is available at +// http://www.opensource.org/licenses/apache2.0.php +// +// You may elect to redistribute this code under either of these licenses. +// ======================================================================== +// + +package org.eclipse.jetty.websocket.common.io.http; + +import java.nio.ByteBuffer; +import java.util.HashMap; +import java.util.Locale; +import java.util.Map; + +public class HttpResponseParseCapture implements HttpResponseHeaderParseListener +{ + private int statusCode; + private String statusReason; + private Map headers = new HashMap<>(); + private ByteBuffer remainingBuffer; + + @Override + public void addHeader(String name, String value) + { + headers.put(name.toLowerCase(Locale.ENGLISH),value); + } + + public String getHeader(String name) + { + return headers.get(name.toLowerCase(Locale.ENGLISH)); + } + + public ByteBuffer getRemainingBuffer() + { + return remainingBuffer; + } + + public int getStatusCode() + { + return statusCode; + } + + public String getStatusReason() + { + return statusReason; + } + + @Override + public void setRemainingBuffer(ByteBuffer copy) + { + this.remainingBuffer = copy; + } + + @Override + public void setStatusCode(int code) + { + this.statusCode = code; + } + + @Override + public void setStatusReason(String reason) + { + this.statusReason = reason; + } +} \ No newline at end of file diff --git a/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/AnnotatedMaxMessageSizeTest.java b/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/AnnotatedMaxMessageSizeTest.java index 09d06a5a558..2db4a48e2dc 100644 --- a/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/AnnotatedMaxMessageSizeTest.java +++ b/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/AnnotatedMaxMessageSizeTest.java @@ -103,7 +103,7 @@ public class AnnotatedMaxMessageSizeTest // Read frame (hopefully text frame) IncomingFramesCapture capture = client.readFrames(1,TimeUnit.MILLISECONDS,500); - WebSocketFrame tf = capture.getFrames().get(0); + WebSocketFrame tf = capture.getFrames().poll(); Assert.assertThat("Text Frame.status code",tf.getPayloadAsUTF8(),is(msg)); } finally diff --git a/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/ChromeTest.java b/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/ChromeTest.java index fd16ef78686..68bc1b1a3a2 100644 --- a/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/ChromeTest.java +++ b/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/ChromeTest.java @@ -24,6 +24,7 @@ import java.util.concurrent.TimeUnit; import org.eclipse.jetty.websocket.common.WebSocketFrame; import org.eclipse.jetty.websocket.server.blockhead.BlockheadClient; +import org.eclipse.jetty.websocket.server.blockhead.HttpResponse; import org.eclipse.jetty.websocket.server.examples.MyEchoServlet; import org.eclipse.jetty.websocket.server.helper.IncomingFramesCapture; import org.junit.AfterClass; @@ -60,8 +61,8 @@ public class ChromeTest client.setProtocols("chat"); client.connect(); client.sendStandardRequest(); - String response = client.expectUpgradeResponse(); - Assert.assertThat("Response",response,containsString("x-webkit-deflate-frame")); + HttpResponse response = client.expectUpgradeResponse(); + Assert.assertThat("Response",response.getExtensionsHeader(),containsString("x-webkit-deflate-frame")); // Generate text frame String msg = "this is an echo ... cho ... ho ... o"; @@ -69,7 +70,7 @@ public class ChromeTest // Read frame (hopefully text frame) IncomingFramesCapture capture = client.readFrames(1,TimeUnit.MILLISECONDS,500); - WebSocketFrame tf = capture.getFrames().get(0); + WebSocketFrame tf = capture.getFrames().poll(); Assert.assertThat("Text Frame.status code",tf.getPayloadAsUTF8(),is(msg)); } finally diff --git a/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/FragmentExtensionTest.java b/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/FragmentExtensionTest.java index cab75e16c89..b84ee24b9e3 100644 --- a/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/FragmentExtensionTest.java +++ b/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/FragmentExtensionTest.java @@ -24,6 +24,7 @@ import java.util.concurrent.TimeUnit; import org.eclipse.jetty.websocket.common.WebSocketFrame; import org.eclipse.jetty.websocket.server.blockhead.BlockheadClient; +import org.eclipse.jetty.websocket.server.blockhead.HttpResponse; import org.eclipse.jetty.websocket.server.helper.EchoServlet; import org.eclipse.jetty.websocket.server.helper.IncomingFramesCapture; import org.junit.AfterClass; @@ -80,9 +81,9 @@ public class FragmentExtensionTest client.setTimeout(TimeUnit.SECONDS,1); client.connect(); client.sendStandardRequest(); - String resp = client.expectUpgradeResponse(); + HttpResponse resp = client.expectUpgradeResponse(); - Assert.assertThat("Response",resp,containsString("fragment")); + Assert.assertThat("Response",resp.getExtensionsHeader(),containsString("fragment")); String msg = "Sent as a long message that should be split"; client.write(WebSocketFrame.text(msg)); @@ -91,7 +92,7 @@ public class FragmentExtensionTest IncomingFramesCapture capture = client.readFrames(parts.length,TimeUnit.MILLISECONDS,1000); for (int i = 0; i < parts.length; i++) { - WebSocketFrame frame = capture.getFrames().get(i); + WebSocketFrame frame = capture.getFrames().poll(); Assert.assertThat("text[" + i + "].payload",frame.getPayloadAsUTF8(),is(parts[i])); } } diff --git a/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/FrameCompressionExtensionTest.java b/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/FrameCompressionExtensionTest.java index 9e0dfebc295..78a6be25a28 100644 --- a/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/FrameCompressionExtensionTest.java +++ b/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/FrameCompressionExtensionTest.java @@ -24,6 +24,7 @@ import java.util.concurrent.TimeUnit; import org.eclipse.jetty.websocket.common.WebSocketFrame; import org.eclipse.jetty.websocket.server.blockhead.BlockheadClient; +import org.eclipse.jetty.websocket.server.blockhead.HttpResponse; import org.eclipse.jetty.websocket.server.helper.EchoServlet; import org.eclipse.jetty.websocket.server.helper.IncomingFramesCapture; import org.junit.AfterClass; @@ -64,9 +65,9 @@ public class FrameCompressionExtensionTest client.setTimeout(TimeUnit.SECONDS,1); client.connect(); client.sendStandardRequest(); - String resp = client.expectUpgradeResponse(); + HttpResponse resp = client.expectUpgradeResponse(); - Assert.assertThat("Response",resp,containsString("x-webkit-deflate-frame")); + Assert.assertThat("Response",resp.getExtensionsHeader(),containsString("x-webkit-deflate-frame")); String msg = "Hello"; @@ -74,7 +75,7 @@ public class FrameCompressionExtensionTest client.write(WebSocketFrame.text(msg)); IncomingFramesCapture capture = client.readFrames(1,TimeUnit.MILLISECONDS,1000); - WebSocketFrame frame = capture.getFrames().get(0); + WebSocketFrame frame = capture.getFrames().poll(); Assert.assertThat("TEXT.payload",frame.getPayloadAsUTF8(),is(msg.toString())); // Client sends second message @@ -83,7 +84,7 @@ public class FrameCompressionExtensionTest client.write(WebSocketFrame.text(msg)); capture = client.readFrames(1,TimeUnit.SECONDS,1); - frame = capture.getFrames().get(0); + frame = capture.getFrames().poll(); Assert.assertThat("TEXT.payload",frame.getPayloadAsUTF8(),is(msg.toString())); } finally diff --git a/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/IdentityExtensionTest.java b/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/IdentityExtensionTest.java index e56dc954627..47fa7caa52f 100644 --- a/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/IdentityExtensionTest.java +++ b/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/IdentityExtensionTest.java @@ -24,6 +24,7 @@ import java.util.concurrent.TimeUnit; import org.eclipse.jetty.websocket.common.WebSocketFrame; import org.eclipse.jetty.websocket.server.blockhead.BlockheadClient; +import org.eclipse.jetty.websocket.server.blockhead.HttpResponse; import org.eclipse.jetty.websocket.server.helper.EchoServlet; import org.eclipse.jetty.websocket.server.helper.IncomingFramesCapture; import org.junit.AfterClass; @@ -65,14 +66,14 @@ public class IdentityExtensionTest client.setTimeout(TimeUnit.SECONDS,1); client.connect(); client.sendStandardRequest(); - String resp = client.expectUpgradeResponse(); + HttpResponse resp = client.expectUpgradeResponse(); - Assert.assertThat("Response",resp,containsString("identity")); + Assert.assertThat("Response",resp.getExtensionsHeader(),containsString("identity")); client.write(WebSocketFrame.text("Hello")); IncomingFramesCapture capture = client.readFrames(1,TimeUnit.MILLISECONDS,1000); - WebSocketFrame frame = capture.getFrames().get(0); + WebSocketFrame frame = capture.getFrames().poll(); Assert.assertThat("TEXT.payload",frame.getPayloadAsUTF8(),is("Hello")); } finally diff --git a/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/WebSocketCloseTest.java b/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/WebSocketCloseTest.java index b7e80d3f258..00c001bcd2e 100644 --- a/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/WebSocketCloseTest.java +++ b/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/WebSocketCloseTest.java @@ -18,6 +18,8 @@ package org.eclipse.jetty.websocket.server; +import static org.hamcrest.Matchers.*; + import java.io.IOException; import java.util.ArrayList; import java.util.List; @@ -43,15 +45,11 @@ import org.eclipse.jetty.websocket.servlet.WebSocketServletFactory; import org.junit.AfterClass; import org.junit.Assert; import org.junit.BeforeClass; -import org.junit.Ignore; import org.junit.Test; -import static org.hamcrest.Matchers.is; - /** * Tests various close scenarios */ -@Ignore public class WebSocketCloseTest { @SuppressWarnings("serial") @@ -145,7 +143,7 @@ public class WebSocketCloseTest client.expectUpgradeResponse(); IncomingFramesCapture capture = client.readFrames(1,TimeUnit.SECONDS,1); - WebSocketFrame frame = capture.getFrames().get(0); + WebSocketFrame frame = capture.getFrames().poll(); Assert.assertThat("frames[0].opcode",frame.getOpCode(),is(OpCode.CLOSE)); CloseInfo close = new CloseInfo(frame); Assert.assertThat("Close Status Code",close.getStatusCode(),is(StatusCode.NORMAL)); diff --git a/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/WebSocketInvalidVersionTest.java b/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/WebSocketInvalidVersionTest.java index 5d47449e841..410fa82af0e 100644 --- a/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/WebSocketInvalidVersionTest.java +++ b/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/WebSocketInvalidVersionTest.java @@ -21,6 +21,7 @@ package org.eclipse.jetty.websocket.server; import static org.hamcrest.Matchers.*; import org.eclipse.jetty.websocket.server.blockhead.BlockheadClient; +import org.eclipse.jetty.websocket.server.blockhead.HttpResponse; import org.eclipse.jetty.websocket.server.examples.MyEchoServlet; import org.junit.AfterClass; import org.junit.Assert; @@ -56,9 +57,10 @@ public class WebSocketInvalidVersionTest { client.connect(); client.sendStandardRequest(); - String respHeader = client.readResponseHeader(); - Assert.assertThat("Response Code",respHeader,startsWith("HTTP/1.1 400 Unsupported websocket version specification")); - Assert.assertThat("Response Header Versions",respHeader,containsString("Sec-WebSocket-Version: 13\r\n")); + HttpResponse response = client.readResponseHeader(); + Assert.assertThat("Response Status Code",response.getStatusCode(),is(400)); + Assert.assertThat("Response Status Reason",response.getStatusReason(),containsString("Unsupported websocket version specification")); + Assert.assertThat("Response Versions",response.getHeader("Sec-WebSocket-Version"),is("13")); } finally { diff --git a/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/WebSocketServerSessionTest.java b/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/WebSocketServerSessionTest.java index f2a6dc5d5c3..61940904dbb 100644 --- a/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/WebSocketServerSessionTest.java +++ b/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/WebSocketServerSessionTest.java @@ -21,6 +21,7 @@ package org.eclipse.jetty.websocket.server; import static org.hamcrest.Matchers.*; import java.net.URI; +import java.util.Queue; import java.util.concurrent.TimeUnit; import org.eclipse.jetty.toolchain.test.AdvancedRunner; @@ -96,13 +97,14 @@ public class WebSocketServerSessionTest // Read frame (hopefully text frame) IncomingFramesCapture capture = client.readFrames(4,TimeUnit.MILLISECONDS,500); - WebSocketFrame tf = capture.getFrames().pop(); + Queue frames = capture.getFrames(); + WebSocketFrame tf = frames.poll(); Assert.assertThat("Parameter Map[snack]",tf.getPayloadAsUTF8(),is("[cashews]")); - tf = capture.getFrames().pop(); + tf = frames.poll(); Assert.assertThat("Parameter Map[amount]",tf.getPayloadAsUTF8(),is("[handful]")); - tf = capture.getFrames().pop(); + tf = frames.poll(); Assert.assertThat("Parameter Map[brand]",tf.getPayloadAsUTF8(),is("[off]")); - tf = capture.getFrames().pop(); + tf = frames.poll(); Assert.assertThat("Parameter Map[cost]",tf.getPayloadAsUTF8(),is("")); } finally diff --git a/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/WebSocketServletRFCTest.java b/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/WebSocketServletRFCTest.java index 294ee654953..8ca6db30d5e 100644 --- a/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/WebSocketServletRFCTest.java +++ b/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/WebSocketServletRFCTest.java @@ -115,7 +115,7 @@ public class WebSocketServletRFCTest // Read frame echo'd back (hopefully a single binary frame) IncomingFramesCapture capture = client.readFrames(1,TimeUnit.MILLISECONDS,1000); - Frame binmsg = capture.getFrames().get(0); + Frame binmsg = capture.getFrames().poll(); int expectedSize = buf1.length + buf2.length + buf3.length; Assert.assertThat("BinaryFrame.payloadLength",binmsg.getPayloadLength(),is(expectedSize)); @@ -181,7 +181,7 @@ public class WebSocketServletRFCTest // Read frame (hopefully text frame) IncomingFramesCapture capture = client.readFrames(1,TimeUnit.MILLISECONDS,500); - WebSocketFrame tf = capture.getFrames().get(0); + WebSocketFrame tf = capture.getFrames().poll(); Assert.assertThat("Text Frame.status code",tf.getPayloadAsUTF8(),is(msg)); } finally @@ -209,7 +209,7 @@ public class WebSocketServletRFCTest // Read frame (hopefully close frame) IncomingFramesCapture capture = client.readFrames(1,TimeUnit.MILLISECONDS,500); - Frame cf = capture.getFrames().get(0); + Frame cf = capture.getFrames().poll(); CloseInfo close = new CloseInfo(cf); Assert.assertThat("Close Frame.status code",close.getStatusCode(),is(StatusCode.SERVER_ERROR)); } @@ -252,7 +252,7 @@ public class WebSocketServletRFCTest // Read frame (hopefully text frame) IncomingFramesCapture capture = client.readFrames(1,TimeUnit.MILLISECONDS,500); - WebSocketFrame tf = capture.getFrames().get(0); + WebSocketFrame tf = capture.getFrames().poll(); Assert.assertThat("Text Frame.status code",tf.getPayloadAsUTF8(),is(msg)); } finally @@ -292,7 +292,7 @@ public class WebSocketServletRFCTest } IncomingFramesCapture capture = client.readFrames(1,TimeUnit.SECONDS,1); - WebSocketFrame frame = capture.getFrames().get(0); + WebSocketFrame frame = capture.getFrames().poll(); Assert.assertThat("frames[0].opcode",frame.getOpCode(),is(OpCode.CLOSE)); CloseInfo close = new CloseInfo(frame); Assert.assertThat("Close Status Code",close.getStatusCode(),is(StatusCode.MESSAGE_TOO_LARGE)); @@ -334,7 +334,7 @@ public class WebSocketServletRFCTest } IncomingFramesCapture capture = client.readFrames(1,TimeUnit.SECONDS,1); - WebSocketFrame frame = capture.getFrames().get(0); + WebSocketFrame frame = capture.getFrames().poll(); Assert.assertThat("frames[0].opcode",frame.getOpCode(),is(OpCode.CLOSE)); CloseInfo close = new CloseInfo(frame); Assert.assertThat("Close Status Code",close.getStatusCode(),is(StatusCode.MESSAGE_TOO_LARGE)); @@ -367,7 +367,7 @@ public class WebSocketServletRFCTest client.writeRaw(bb); IncomingFramesCapture capture = client.readFrames(1,TimeUnit.SECONDS,1); - WebSocketFrame frame = capture.getFrames().get(0); + WebSocketFrame frame = capture.getFrames().poll(); Assert.assertThat("frames[0].opcode",frame.getOpCode(),is(OpCode.CLOSE)); CloseInfo close = new CloseInfo(frame); Assert.assertThat("Close Status Code",close.getStatusCode(),is(StatusCode.BAD_PAYLOAD)); @@ -413,7 +413,7 @@ public class WebSocketServletRFCTest // Read frame (hopefully text frame) IncomingFramesCapture capture = client.readFrames(1,TimeUnit.MILLISECONDS,500); - WebSocketFrame tf = capture.getFrames().get(0); + WebSocketFrame tf = capture.getFrames().poll(); Assert.assertThat("Text Frame.status code",tf.getPayloadAsUTF8(),is(msg)); } finally diff --git a/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/ab/Fuzzer.java b/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/ab/Fuzzer.java index 09e9f3ec173..5c9d07c6f74 100644 --- a/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/ab/Fuzzer.java +++ b/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/ab/Fuzzer.java @@ -151,7 +151,7 @@ public class Fuzzer for (int i = 0; i < expectedCount; i++) { WebSocketFrame expected = expect.get(i); - WebSocketFrame actual = capture.getFrames().pop(); + WebSocketFrame actual = capture.getFrames().poll(); prefix = "Frame[" + i + "]"; @@ -188,14 +188,16 @@ public class Fuzzer // we expect that the close handshake to have occurred and the server should have closed the connection try { - @SuppressWarnings("unused") - int val = client.read(); + ByteBuffer buf = ByteBuffer.wrap(new byte[] + { 0x00 }); + BufferUtil.flipToFill(buf); + int len = client.read(buf); - Assert.fail("Server has not closed socket"); + Assert.assertThat("Server has not closed socket",len,lessThanOrEqualTo(0)); } - catch (SocketException e) + catch (IOException e) { - + // valid path } IOState ios = client.getIOState(); diff --git a/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/blockhead/BlockheadClient.java b/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/blockhead/BlockheadClient.java index 561f9a89c50..ce46ba23fe6 100644 --- a/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/blockhead/BlockheadClient.java +++ b/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/blockhead/BlockheadClient.java @@ -19,13 +19,10 @@ package org.eclipse.jetty.websocket.server.blockhead; import static org.hamcrest.Matchers.*; -import static org.junit.Assert.*; -import java.io.BufferedReader; import java.io.EOFException; import java.io.IOException; import java.io.InputStream; -import java.io.InputStreamReader; import java.io.OutputStream; import java.net.HttpURLConnection; import java.net.InetAddress; @@ -41,8 +38,6 @@ import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeoutException; import java.util.concurrent.atomic.AtomicInteger; -import java.util.regex.Matcher; -import java.util.regex.Pattern; import javax.net.ssl.HttpsURLConnection; @@ -51,7 +46,6 @@ import org.eclipse.jetty.io.MappedByteBufferPool; import org.eclipse.jetty.util.BufferUtil; import org.eclipse.jetty.util.IO; import org.eclipse.jetty.util.StringUtil; -import org.eclipse.jetty.util.Utf8StringBuilder; import org.eclipse.jetty.util.log.Log; import org.eclipse.jetty.util.log.Logger; import org.eclipse.jetty.websocket.api.WebSocketException; @@ -71,6 +65,7 @@ import org.eclipse.jetty.websocket.common.WebSocketFrame; import org.eclipse.jetty.websocket.common.extensions.ExtensionStack; import org.eclipse.jetty.websocket.common.extensions.WebSocketExtensionFactory; import org.eclipse.jetty.websocket.common.io.IOState; +import org.eclipse.jetty.websocket.common.io.http.HttpResponseHeaderParser; import org.eclipse.jetty.websocket.server.helper.IncomingFramesCapture; import org.junit.Assert; @@ -117,6 +112,7 @@ public class BlockheadClient implements IncomingFrames, OutgoingFrames private ExtensionStack extensionStack; private IOState ioState; private CountDownLatch disconnectedLatch = new CountDownLatch(1); + private ByteBuffer remainingBuffer; public BlockheadClient(URI destWebsocketURI) throws URISyntaxException { @@ -234,32 +230,31 @@ public class BlockheadClient implements IncomingFrames, OutgoingFrames } } - public String expectUpgradeResponse() throws IOException + public HttpResponse expectUpgradeResponse() throws IOException { - String respHeader = readResponseHeader(); + HttpResponse response = readResponseHeader(); if (LOG.isDebugEnabled()) { - LOG.debug("Response Header: {}{}",'\n',respHeader); + LOG.debug("Response Header: {}{}",'\n',response); } - Assert.assertThat("Response Code",respHeader,startsWith("HTTP/1.1 101 Switching Protocols")); - Assert.assertThat("Response Header Upgrade",respHeader,containsString("Upgrade: WebSocket\r\n")); - Assert.assertThat("Response Header Connection",respHeader,containsString("Connection: Upgrade\r\n")); + Assert.assertThat("Response Status Code",response.getStatusCode(),is(101)); + Assert.assertThat("Response Status Reason",response.getStatusReason(),is("Switching Protocols")); + Assert.assertThat("Response Header[Upgrade]",response.getHeader("Upgrade"),is("WebSocket")); + Assert.assertThat("Response Header[Connection]",response.getHeader("Connection"),is("Upgrade")); // Validate the Sec-WebSocket-Accept - Pattern patAcceptHeader = Pattern.compile("Sec-WebSocket-Accept: (.*=)",Pattern.CASE_INSENSITIVE); - Matcher matAcceptHeader = patAcceptHeader.matcher(respHeader); - Assert.assertThat("Response Header Sec-WebSocket-Accept Exists?",matAcceptHeader.find(),is(true)); + String acceptKey = response.getHeader("Sec-WebSocket-Accept"); + Assert.assertThat("Response Header[Sec-WebSocket-Accept Exists]",acceptKey,notNullValue()); String reqKey = REQUEST_HASH_KEY; String expectedHash = AcceptHash.hashKey(reqKey); - String acceptKey = matAcceptHeader.group(1); Assert.assertThat("Valid Sec-WebSocket-Accept Hash?",acceptKey,is(expectedHash)); // collect extensions configured in response header - List configs = getExtensionConfigs(respHeader); + List configs = getExtensionConfigs(response); extensionStack = new ExtensionStack(this.extensionFactory); extensionStack.negotiate(configs); @@ -288,7 +283,7 @@ public class BlockheadClient implements IncomingFrames, OutgoingFrames LOG.debug("outgoing = {}",outgoing); LOG.debug("incoming = {}",extensionStack); - return respHeader; + return response; } public void flush() throws IOException @@ -296,22 +291,16 @@ public class BlockheadClient implements IncomingFrames, OutgoingFrames out.flush(); } - private List getExtensionConfigs(String respHeader) + private List getExtensionConfigs(HttpResponse response) { List configs = new ArrayList<>(); - Pattern expat = Pattern.compile("Sec-WebSocket-Extensions: (.*)\r",Pattern.CASE_INSENSITIVE); - Matcher mat = expat.matcher(respHeader); - int offset = 0; - while (mat.find(offset)) + String econf = response.getHeader("Sec-WebSocket-Extensions"); + if (econf != null) { - String econf = mat.group(1); LOG.debug("Found Extension Response: {}",econf); - ExtensionConfig config = ExtensionConfig.parse(econf); configs.add(config); - - offset = mat.end(1); } return configs; } @@ -423,35 +412,6 @@ public class BlockheadClient implements IncomingFrames, OutgoingFrames return (socket != null) && (socket.isConnected()); } - public void lookFor(String string) throws IOException - { - String orig = string; - Utf8StringBuilder scanned = new Utf8StringBuilder(); - try - { - while (true) - { - int b = in.read(); - if (b < 0) - { - throw new EOFException(); - } - scanned.append((byte)b); - assertEquals("looking for\"" + orig + "\" in '" + scanned + "'",string.charAt(0),b); - if (string.length() == 1) - { - break; - } - string = string.substring(1); - } - } - catch (IOException e) - { - System.err.println("IOE while looking for \"" + orig + "\" in '" + scanned + "'"); - throw e; - } - } - @Override public void outgoingFrame(Frame frame, WriteCallback callback) { @@ -487,17 +447,18 @@ public class BlockheadClient implements IncomingFrames, OutgoingFrames } } - public int read() throws IOException - { - return in.read(); - } - public int read(ByteBuffer buf) throws IOException { if (eof) { throw new EOFException("Hit EOF"); } + + if ((remainingBuffer != null) && (remainingBuffer.remaining() > 0)) + { + return BufferUtil.put(remainingBuffer,buf); + } + int len = 0; int b; while ((in.available() > 0) && (buf.remaining() > 0)) @@ -572,25 +533,24 @@ public class BlockheadClient implements IncomingFrames, OutgoingFrames return incomingFrames; } - public String readResponseHeader() throws IOException + public HttpResponse readResponseHeader() throws IOException { - InputStreamReader isr = new InputStreamReader(in); - BufferedReader reader = new BufferedReader(isr); - StringBuilder header = new StringBuilder(); - // Read the response header - String line = reader.readLine(); - Assert.assertNotNull(line); - Assert.assertThat(line,startsWith("HTTP/1.1 ")); - header.append(line).append("\r\n"); - while ((line = reader.readLine()) != null) + HttpResponse response = new HttpResponse(); + HttpResponseHeaderParser parser = new HttpResponseHeaderParser(response); + + ByteBuffer buf = BufferUtil.allocate(512); + + do { - if (line.trim().length() == 0) - { - break; - } - header.append(line).append("\r\n"); + BufferUtil.flipToFill(buf); + read(buf); + BufferUtil.flipToFlush(buf,0); } - return header.toString(); + while (parser.parse(buf) == null); + + remainingBuffer = response.getRemainingBuffer(); + + return response; } public void sendStandardRequest() throws IOException diff --git a/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/blockhead/HttpResponse.java b/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/blockhead/HttpResponse.java new file mode 100644 index 00000000000..59d44ebea86 --- /dev/null +++ b/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/blockhead/HttpResponse.java @@ -0,0 +1,95 @@ +// +// ======================================================================== +// Copyright (c) 1995-2013 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.server.blockhead; + +import java.nio.ByteBuffer; +import java.util.HashMap; +import java.util.Locale; +import java.util.Map; + +import org.eclipse.jetty.websocket.common.io.http.HttpResponseHeaderParseListener; + +public class HttpResponse implements HttpResponseHeaderParseListener +{ + private int statusCode; + private String statusReason; + private Map headers = new HashMap<>(); + private ByteBuffer remainingBuffer; + + @Override + public void addHeader(String name, String value) + { + headers.put(name.toLowerCase(Locale.ENGLISH),value); + } + + public String getExtensionsHeader() + { + return getHeader("Sec-WebSocket-Extensions"); + } + + public String getHeader(String name) + { + return headers.get(name.toLowerCase(Locale.ENGLISH)); + } + + public ByteBuffer getRemainingBuffer() + { + return remainingBuffer; + } + + public int getStatusCode() + { + return statusCode; + } + + public String getStatusReason() + { + return statusReason; + } + + @Override + public void setRemainingBuffer(ByteBuffer copy) + { + this.remainingBuffer = copy; + } + + @Override + public void setStatusCode(int code) + { + this.statusCode = code; + } + + @Override + public void setStatusReason(String reason) + { + this.statusReason = reason; + } + + @Override + public String toString() + { + StringBuilder str = new StringBuilder(); + str.append("HTTP/1.1 ").append(statusCode).append(' ').append(statusReason); + for (Map.Entry entry : headers.entrySet()) + { + str.append('\n').append(entry.getKey()).append(": ").append(entry.getValue()); + } + return str.toString(); + } +} diff --git a/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/helper/IncomingFramesCapture.java b/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/helper/IncomingFramesCapture.java index a0e2889f6ea..82a47232738 100644 --- a/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/helper/IncomingFramesCapture.java +++ b/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/helper/IncomingFramesCapture.java @@ -20,8 +20,9 @@ package org.eclipse.jetty.websocket.server.helper; import static org.hamcrest.Matchers.*; -import java.util.LinkedList; +import java.util.Queue; +import org.eclipse.jetty.toolchain.test.EventQueue; import org.eclipse.jetty.util.BufferUtil; import org.eclipse.jetty.util.log.Log; import org.eclipse.jetty.util.log.Logger; @@ -35,8 +36,8 @@ import org.junit.Assert; public class IncomingFramesCapture implements IncomingFrames { private static final Logger LOG = Log.getLogger(IncomingFramesCapture.class); - private LinkedList frames = new LinkedList<>(); - private LinkedList errors = new LinkedList<>(); + private EventQueue frames = new EventQueue<>(); + private EventQueue errors = new EventQueue<>(); public void assertErrorCount(int expectedCount) { @@ -81,10 +82,10 @@ public class IncomingFramesCapture implements IncomingFrames public void dump() { System.err.printf("Captured %d incoming frames%n",frames.size()); - for (int i = 0; i < frames.size(); i++) + int i = 0; + for (Frame frame : frames) { - Frame frame = frames.get(i); - System.err.printf("[%3d] %s%n",i,frame); + System.err.printf("[%3d] %s%n",i++,frame); System.err.printf(" %s%n",BufferUtil.toDetailString(frame.getPayload())); } } @@ -102,7 +103,7 @@ public class IncomingFramesCapture implements IncomingFrames return count; } - public LinkedList getErrors() + public Queue getErrors() { return errors; } @@ -120,7 +121,7 @@ public class IncomingFramesCapture implements IncomingFrames return count; } - public LinkedList getFrames() + public Queue getFrames() { return frames; } diff --git a/pom.xml b/pom.xml index 7b480d92ceb..417706f9e06 100644 --- a/pom.xml +++ b/pom.xml @@ -542,7 +542,7 @@ org.eclipse.jetty.toolchain jetty-test-helper - 2.0 + 2.2 org.slf4j