// // ======================================================================== // Copyright (c) 1995-2017 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.server; import static org.eclipse.jetty.server.HttpInput.EARLY_EOF_CONTENT; import static org.eclipse.jetty.server.HttpInput.EOF_CONTENT; import static org.hamcrest.Matchers.empty; import static org.hamcrest.Matchers.equalTo; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertThat; import static org.junit.Assert.assertTrue; import java.io.IOException; import java.nio.ByteBuffer; import java.util.Queue; import java.util.concurrent.LinkedBlockingQueue; import javax.servlet.ReadListener; import org.eclipse.jetty.server.HttpChannelState.Action; import org.eclipse.jetty.server.HttpInput.Content; import org.eclipse.jetty.util.BufferUtil; import org.eclipse.jetty.util.thread.Scheduler; import org.hamcrest.Matchers; import org.junit.After; import org.junit.Assert; import org.junit.Before; import org.junit.Test; /** * this tests HttpInput and its interaction with HttpChannelState */ public class HttpInputAsyncStateTest { private static final Queue<String> __history = new LinkedBlockingQueue<>(); private ByteBuffer _expected = BufferUtil.allocate(16*1024); private boolean _eof; private boolean _noReadInDataAvailable; private boolean _completeInOnDataAvailable; private final ReadListener _listener = new ReadListener() { @Override public void onError(Throwable t) { __history.add("onError:" + t); } @Override public void onDataAvailable() throws IOException { __history.add("onDataAvailable"); if (!_noReadInDataAvailable && readAvailable() && _completeInOnDataAvailable) { __history.add("complete"); _state.complete(); } } @Override public void onAllDataRead() throws IOException { __history.add("onAllDataRead"); } }; private HttpInput _in; HttpChannelState _state; public static class TContent extends HttpInput.Content { public TContent(String content) { super(BufferUtil.toBuffer(content)); } } @Before public void before() { _noReadInDataAvailable = false; _in = new HttpInput(new HttpChannelState(new HttpChannel(null, new HttpConfiguration(), null, null) { @Override public void asyncReadFillInterested() { __history.add("asyncReadFillInterested"); } @Override public Scheduler getScheduler() { return null; } }) { @Override public void onReadUnready() { super.onReadUnready(); __history.add("onReadUnready"); } @Override public boolean onContentAdded() { boolean wake = super.onContentAdded(); __history.add("onReadPossible "+wake); return wake; } @Override public boolean onReadReady() { boolean wake = super.onReadReady(); __history.add("onReadReady "+wake); return wake; } }) { @Override public void wake() { __history.add("wake"); } }; _state = _in.getHttpChannelState(); __history.clear(); } private void check(String... history) { if (history==null || history.length==0) assertThat(__history,empty()); else assertThat(__history.toArray(new String[__history.size()]),Matchers.arrayContaining(history)); __history.clear(); } private void wake() { handle(null); } private void handle() { handle(null); } private void handle(Runnable run) { Action action = _state.handling(); loop: while(true) { switch(action) { case DISPATCH: if (run==null) Assert.fail(); run.run(); break; case READ_CALLBACK: _in.run(); break; case TERMINATED: case WAIT: break loop; case COMPLETE: __history.add("COMPLETE"); break; default: Assert.fail(); } action = _state.unhandle(); } } private void deliver(Content... content) { if (content!=null) { for (Content c: content) { if (c==EOF_CONTENT) { _in.eof(); _eof = true; } else if (c==HttpInput.EARLY_EOF_CONTENT) { _in.earlyEOF(); _eof = true; } else { _in.addContent(c); BufferUtil.append(_expected,c.getByteBuffer().slice()); } } } } boolean readAvailable() throws IOException { int len=0; try { while(_in.isReady()) { int b = _in.read(); if (b<0) { if (len>0) __history.add("read "+len); __history.add("read -1"); assertTrue(BufferUtil.isEmpty(_expected)); assertTrue(_eof); return true; } else { len++; assertFalse(BufferUtil.isEmpty(_expected)); int a = 0xff & _expected.get(); assertThat(b,equalTo(a)); } } __history.add("read "+len); assertTrue(BufferUtil.isEmpty(_expected)); } catch(IOException e) { if (len>0) __history.add("read "+len); __history.add("read "+e); throw e; } return false; } @After public void after() { Assert.assertThat(__history.poll(), Matchers.nullValue()); } @Test public void testInitialEmptyListenInHandle() throws Exception { deliver(EOF_CONTENT); check(); handle(()-> { _state.startAsync(null); _in.setReadListener(_listener); check("onReadReady false"); }); check("onAllDataRead"); } @Test public void testInitialEmptyListenAfterHandle() throws Exception { deliver(EOF_CONTENT); handle(()-> { _state.startAsync(null); check(); }); _in.setReadListener(_listener); check("onReadReady true","wake"); wake(); check("onAllDataRead"); } @Test public void testListenInHandleEmpty() throws Exception { handle(()-> { _state.startAsync(null); _in.setReadListener(_listener); check("onReadUnready"); }); check("asyncReadFillInterested"); deliver(EOF_CONTENT); check("onReadPossible true"); handle(); check("onAllDataRead"); } @Test public void testEmptyListenAfterHandle() throws Exception { handle(()-> { _state.startAsync(null); check(); }); deliver(EOF_CONTENT); check(); _in.setReadListener(_listener); check("onReadReady true","wake"); wake(); check("onAllDataRead"); } @Test public void testListenAfterHandleEmpty() throws Exception { handle(()-> { _state.startAsync(null); check(); }); _in.setReadListener(_listener); check("asyncReadFillInterested","onReadUnready"); deliver(EOF_CONTENT); check("onReadPossible true"); handle(); check("onAllDataRead"); } @Test public void testInitialEarlyEOFListenInHandle() throws Exception { deliver(EARLY_EOF_CONTENT); check(); handle(()-> { _state.startAsync(null); _in.setReadListener(_listener); check("onReadReady false"); }); check("onError:org.eclipse.jetty.io.EofException: Early EOF"); } @Test public void testInitialEarlyEOFListenAfterHandle() throws Exception { deliver(EARLY_EOF_CONTENT); handle(()-> { _state.startAsync(null); check(); }); _in.setReadListener(_listener); check("onReadReady true","wake"); wake(); check("onError:org.eclipse.jetty.io.EofException: Early EOF"); } @Test public void testListenInHandleEarlyEOF() throws Exception { handle(()-> { _state.startAsync(null); _in.setReadListener(_listener); check("onReadUnready"); }); check("asyncReadFillInterested"); deliver(EARLY_EOF_CONTENT); check("onReadPossible true"); handle(); check("onError:org.eclipse.jetty.io.EofException: Early EOF"); } @Test public void testEarlyEOFListenAfterHandle() throws Exception { handle(()-> { _state.startAsync(null); check(); }); deliver(EARLY_EOF_CONTENT); check(); _in.setReadListener(_listener); check("onReadReady true","wake"); wake(); check("onError:org.eclipse.jetty.io.EofException: Early EOF"); } @Test public void testListenAfterHandleEarlyEOF() throws Exception { handle(()-> { _state.startAsync(null); check(); }); _in.setReadListener(_listener); check("asyncReadFillInterested","onReadUnready"); deliver(EARLY_EOF_CONTENT); check("onReadPossible true"); handle(); check("onError:org.eclipse.jetty.io.EofException: Early EOF"); } @Test public void testInitialAllContentListenInHandle() throws Exception { deliver(new TContent("Hello"),EOF_CONTENT); check(); handle(()-> { _state.startAsync(null); _in.setReadListener(_listener); check("onReadReady false"); }); check("onDataAvailable","read 5","read -1","onAllDataRead"); } @Test public void testInitialAllContentListenAfterHandle() throws Exception { deliver(new TContent("Hello"),EOF_CONTENT); handle(()-> { _state.startAsync(null); check(); }); _in.setReadListener(_listener); check("onReadReady true","wake"); wake(); check("onDataAvailable","read 5","read -1","onAllDataRead"); } @Test public void testListenInHandleAllContent() throws Exception { handle(()-> { _state.startAsync(null); _in.setReadListener(_listener); check("onReadUnready"); }); check("asyncReadFillInterested"); deliver(new TContent("Hello"),EOF_CONTENT); check("onReadPossible true","onReadPossible false"); handle(); check("onDataAvailable","read 5","read -1","onAllDataRead"); } @Test public void testAllContentListenAfterHandle() throws Exception { handle(()-> { _state.startAsync(null); check(); }); deliver(new TContent("Hello"),EOF_CONTENT); check(); _in.setReadListener(_listener); check("onReadReady true","wake"); wake(); check("onDataAvailable","read 5","read -1","onAllDataRead"); } @Test public void testListenAfterHandleAllContent() throws Exception { handle(()-> { _state.startAsync(null); check(); }); _in.setReadListener(_listener); check("asyncReadFillInterested","onReadUnready"); deliver(new TContent("Hello"),EOF_CONTENT); check("onReadPossible true","onReadPossible false"); handle(); check("onDataAvailable","read 5","read -1","onAllDataRead"); } @Test public void testInitialIncompleteContentListenInHandle() throws Exception { deliver(new TContent("Hello"),EARLY_EOF_CONTENT); check(); handle(()-> { _state.startAsync(null); _in.setReadListener(_listener); check("onReadReady false"); }); check( "onDataAvailable", "read 5", "read org.eclipse.jetty.io.EofException: Early EOF", "onError:org.eclipse.jetty.io.EofException: Early EOF"); } @Test public void testInitialPartialContentListenAfterHandle() throws Exception { deliver(new TContent("Hello"),EARLY_EOF_CONTENT); handle(()-> { _state.startAsync(null); check(); }); _in.setReadListener(_listener); check("onReadReady true","wake"); wake(); check( "onDataAvailable", "read 5", "read org.eclipse.jetty.io.EofException: Early EOF", "onError:org.eclipse.jetty.io.EofException: Early EOF"); } @Test public void testListenInHandlePartialContent() throws Exception { handle(()-> { _state.startAsync(null); _in.setReadListener(_listener); check("onReadUnready"); }); check("asyncReadFillInterested"); deliver(new TContent("Hello"),EARLY_EOF_CONTENT); check("onReadPossible true","onReadPossible false"); handle(); check( "onDataAvailable", "read 5", "read org.eclipse.jetty.io.EofException: Early EOF", "onError:org.eclipse.jetty.io.EofException: Early EOF"); } @Test public void testPartialContentListenAfterHandle() throws Exception { handle(()-> { _state.startAsync(null); check(); }); deliver(new TContent("Hello"),EARLY_EOF_CONTENT); check(); _in.setReadListener(_listener); check("onReadReady true","wake"); wake(); check( "onDataAvailable", "read 5", "read org.eclipse.jetty.io.EofException: Early EOF", "onError:org.eclipse.jetty.io.EofException: Early EOF"); } @Test public void testListenAfterHandlePartialContent() throws Exception { handle(()-> { _state.startAsync(null); check(); }); _in.setReadListener(_listener); check("asyncReadFillInterested","onReadUnready"); deliver(new TContent("Hello"),EARLY_EOF_CONTENT); check("onReadPossible true","onReadPossible false"); handle(); check( "onDataAvailable", "read 5", "read org.eclipse.jetty.io.EofException: Early EOF", "onError:org.eclipse.jetty.io.EofException: Early EOF"); } @Test public void testReadAfterOnDataAvailable() throws Exception { _noReadInDataAvailable = true; handle(()-> { _state.startAsync(null); _in.setReadListener(_listener); check("onReadUnready"); }); check("asyncReadFillInterested"); deliver(new TContent("Hello"),EOF_CONTENT); check("onReadPossible true","onReadPossible false"); handle(); check("onDataAvailable"); readAvailable(); check("wake","read 5","read -1"); wake(); check("onAllDataRead"); } @Test public void testReadOnlyExpectedAfterOnDataAvailable() throws Exception { _noReadInDataAvailable = true; handle(()-> { _state.startAsync(null); _in.setReadListener(_listener); check("onReadUnready"); }); check("asyncReadFillInterested"); deliver(new TContent("Hello"),EOF_CONTENT); check("onReadPossible true","onReadPossible false"); handle(); check("onDataAvailable"); byte[] buffer = new byte[_expected.remaining()]; assertThat(_in.read(buffer),equalTo(buffer.length)); assertThat(new String(buffer),equalTo(BufferUtil.toString(_expected))); BufferUtil.clear(_expected); check(); assertTrue(_in.isReady()); check(); assertThat(_in.read(),equalTo(-1)); check("wake"); wake(); check("onAllDataRead"); } @Test public void testReadAndCompleteInOnDataAvailable() throws Exception { _completeInOnDataAvailable = true; handle(()-> { _state.startAsync(null); _in.setReadListener(_listener); check("onReadUnready"); }); check("asyncReadFillInterested"); deliver(new TContent("Hello"),EOF_CONTENT); check("onReadPossible true","onReadPossible false"); handle(()->{__history.add(_state.getState().toString());}); System.err.println(__history); check( "onDataAvailable", "read 5", "read -1", "complete", "COMPLETE" ); } }