//
// ========================================================================
// 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"
);
}
}