/*
* Copyright (c) 2009-2009 Mort Bay Consulting Pty. Ltd.
*
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* and Apache License v2.0 which accompanies this distribution.
* The Eclipse Public License is available at
* http://www.eclipse.org/legal/epl-v10.html
* The Apache License v2.0 is available at
* http://www.opensource.org/licenses/apache2.0.php
*
* You may elect to redistribute this code under either of these licenses.
*/
package org.eclipse.jetty.client;
import java.io.IOException;
import java.net.SocketTimeoutException;
import javax.servlet.ServletException;
import javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.eclipse.jetty.io.Buffer;
import org.eclipse.jetty.server.AbstractHttpConnection;
import org.eclipse.jetty.server.Connector;
import org.eclipse.jetty.server.Request;
import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.server.handler.AbstractHandler;
import org.eclipse.jetty.server.nio.SelectChannelConnector;
import org.eclipse.jetty.util.log.Log;
import org.eclipse.jetty.util.log.StdErrLog;
import org.junit.After;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
import static org.hamcrest.Matchers.is;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertThat;
import static org.junit.Assert.assertTrue;
/**
*/
public abstract class AbstractHttpExchangeCancelTest
{
private Server server;
private Connector connector;
@Before
public void setUp() throws Exception
{
server = new Server();
connector = new SelectChannelConnector();
server.addConnector(connector);
server.setHandler(new EmptyHandler());
server.start();
}
@After
public void tearDown() throws Exception
{
server.stop();
server.join();
}
/* ------------------------------------------------------------ */
@Test
public void testHttpExchangeCancelOnSend1() throws Exception
{
// One of the first things that HttpClient.send() does
// is to change the status of the exchange
// We exploit that to be sure the exchange is canceled
// without race conditions
TestHttpExchange exchange = new TestHttpExchange()
{
@Override
boolean setStatus(int status)
{
// Cancel before setting the new status
if (getStatus() == HttpExchange.STATUS_START &&
status == STATUS_WAITING_FOR_CONNECTION)
cancel();
return super.setStatus(status);
}
};
exchange.setAddress(newAddress());
exchange.setRequestURI("/");
getHttpClient().send(exchange);
// Cancelling here is wrong and makes the test fail spuriously
// due to a race condition with send(): the send() can complete
// before the exchange is canceled so it will be in STATUS_COMPLETE
// which will fail the test.
// exchange.cancel();
int status = exchange.waitForDone();
assertEquals(HttpExchange.STATUS_CANCELLED, status);
assertFalse(exchange.isResponseCompleted());
assertFalse(exchange.isFailed());
assertFalse(exchange.isAssociated());
}
/* ------------------------------------------------------------ */
@Test
public void testHttpExchangeCancelOnSend2() throws Exception
{
// One of the first things that HttpClient.send() does
// is to change the status of the exchange
// We exploit that to be sure the exchange is canceled
// without race conditions
TestHttpExchange exchange = new TestHttpExchange()
{
@Override
boolean setStatus(int status)
{
// Cancel after setting the new status
int oldStatus = getStatus();
boolean set = super.setStatus(status);
if (oldStatus == STATUS_START &&
getStatus() == HttpExchange.STATUS_WAITING_FOR_CONNECTION)
cancel();
return set;
}
};
exchange.setAddress(newAddress());
exchange.setRequestURI("/");
getHttpClient().send(exchange);
// Cancelling here is wrong and makes the test fail spuriously
// due to a race condition with send(): the send() can complete
// before the exchange is canceled so it will be in STATUS_COMPLETE
// which will fail the test.
// exchange.cancel();
int status = exchange.waitForDone();
assertEquals(HttpExchange.STATUS_CANCELLED, status);
assertFalse(exchange.isResponseCompleted());
assertFalse(exchange.isFailed());
assertFalse(exchange.isAssociated());
}
/* ------------------------------------------------------------ */
@Test
public void testHttpExchangeCancelOnRequestCommitted() throws Exception
{
TestHttpExchange exchange = new TestHttpExchange()
{
@Override
protected void onRequestCommitted() throws IOException
{
super.onRequestCommitted();
cancel();
}
};
exchange.setAddress(newAddress());
exchange.setRequestURI("/");
getHttpClient().send(exchange);
int status = exchange.waitForDone();
assertEquals(HttpExchange.STATUS_CANCELLED, status);
assertFalse(exchange.isResponseCompleted());
assertFalse(exchange.isFailed());
assertFalse(exchange.isAssociated());
}
/* ------------------------------------------------------------ */
@Test
public void testHttpExchangeCancelOnRequestComplete() throws Exception
{
TestHttpExchange exchange = new TestHttpExchange()
{
@Override
protected void onRequestComplete() throws IOException
{
super.onRequestComplete();
cancel();
}
};
exchange.setAddress(newAddress());
exchange.setRequestURI("/");
getHttpClient().send(exchange);
int status = exchange.waitForDone();
assertThat("Exchange Status", status, is(HttpExchange.STATUS_CANCELLED));
assertThat("Exchange.isResponseCompleted", exchange.isResponseCompleted(), is(false));
assertThat("Exchange.isFailed", exchange.isFailed(), is(false));
assertThat("Exchange.isAssociated", exchange.isAssociated(), is(false));
}
/* ------------------------------------------------------------ */
@Test
public void testHttpExchangeCancelOnResponseStatus() throws Exception
{
TestHttpExchange exchange = new TestHttpExchange()
{
@Override
protected void onResponseStatus(Buffer version, int status, Buffer reason) throws IOException
{
super.onResponseStatus(version, status, reason);
cancel();
}
};
exchange.setAddress(newAddress());
exchange.setRequestURI("/");
getHttpClient().send(exchange);
int status = exchange.waitForDone();
assertEquals(HttpExchange.STATUS_CANCELLED, status);
assertFalse(exchange.isResponseCompleted());
assertFalse(exchange.isFailed());
assertFalse(exchange.isAssociated());
}
/* ------------------------------------------------------------ */
@Test
public void testHttpExchangeCancelOnResponseHeader() throws Exception
{
TestHttpExchange exchange = new TestHttpExchange()
{
@Override
protected void onResponseHeader(Buffer name, Buffer value) throws IOException
{
super.onResponseHeader(name, value);
cancel();
}
};
exchange.setAddress(newAddress());
exchange.setRequestURI("/");
getHttpClient().send(exchange);
int status = exchange.waitForDone();
assertEquals(HttpExchange.STATUS_CANCELLED, status);
assertFalse(exchange.isResponseCompleted());
assertFalse(exchange.isFailed());
assertFalse(exchange.isAssociated());
}
/* ------------------------------------------------------------ */
@Test
public void testHttpExchangeCancelOnResponseHeadersComplete() throws Exception
{
TestHttpExchange exchange = new TestHttpExchange()
{
@Override
protected void onResponseHeaderComplete() throws IOException
{
super.onResponseHeaderComplete();
cancel();
}
};
exchange.setAddress(newAddress());
exchange.setRequestURI("/");
getHttpClient().send(exchange);
int status = exchange.waitForDone();
assertEquals(HttpExchange.STATUS_CANCELLED, status);
assertFalse(exchange.isResponseCompleted());
assertFalse(exchange.isFailed());
assertFalse(exchange.isAssociated());
}
/* ------------------------------------------------------------ */
@Test
public void testHttpExchangeCancelOnResponseContent() throws Exception
{
TestHttpExchange exchange = new TestHttpExchange()
{
@Override
protected void onResponseContent(Buffer content) throws IOException
{
super.onResponseContent(content);
cancel();
}
};
exchange.setAddress(newAddress());
exchange.setRequestURI("/?action=body");
getHttpClient().send(exchange);
int status = exchange.waitForDone();
assertEquals(HttpExchange.STATUS_CANCELLED, status);
assertFalse(exchange.isResponseCompleted());
assertFalse(exchange.isFailed());
assertFalse(exchange.isAssociated());
}
/* ------------------------------------------------------------ */
@Test
public void testHttpExchangeCancelOnResponseComplete() throws Exception
{
TestHttpExchange exchange = new TestHttpExchange()
{
@Override
protected void onResponseComplete() throws IOException
{
super.onResponseComplete();
cancel();
}
};
exchange.setAddress(newAddress());
exchange.setRequestURI("/");
getHttpClient().send(exchange);
int status = exchange.waitForDone();
assertTrue(exchange.isResponseCompleted());
assertFalse(exchange.isFailed());
assertFalse(exchange.isAssociated());
assertEquals(HttpExchange.STATUS_COMPLETED, status);
}
/* ------------------------------------------------------------ */
@Test
public void testHttpExchangeOnServerException() throws Exception
{
try
{
((StdErrLog)Log.getLogger(AbstractHttpConnection.class)).setHideStacks(true);
TestHttpExchange exchange = new TestHttpExchange();
exchange.setAddress(newAddress());
exchange.setRequestURI("/?action=throw");
getHttpClient().send(exchange);
int status = exchange.waitForDone();
assertEquals(HttpExchange.STATUS_COMPLETED, status);
assertTrue(exchange.isResponseCompleted());
assertFalse(exchange.isFailed());
assertFalse(exchange.isAssociated());
}
finally
{
((StdErrLog)Log.getLogger(AbstractHttpConnection.class)).setHideStacks(false);
}
}
/* ------------------------------------------------------------ */
@Test
public void testHttpExchangeOnExpire() throws Exception
{
HttpClient httpClient = getHttpClient();
httpClient.stop();
httpClient.setTimeout(1000);
httpClient.start();
TestHttpExchange exchange = new TestHttpExchange();
exchange.setAddress(newAddress());
exchange.setRequestURI("/?action=wait5000");
long start = System.currentTimeMillis();
httpClient.send(exchange);
int status = exchange.waitForDone();
long end = System.currentTimeMillis();
assertTrue(HttpExchange.STATUS_EXPIRED==status||HttpExchange.STATUS_EXCEPTED==status);
assertFalse(exchange.isResponseCompleted());
assertTrue(end-start<4000);
assertTrue(exchange.isExpired());
assertFalse(exchange.isFailed());
assertFalse(exchange.isAssociated());
}
@Test
public void testHttpExchangeCancelReturnsConnection() throws Exception
{
TestHttpExchange exchange = new TestHttpExchange();
Address address = newAddress();
exchange.setAddress(address);
long delay = 5000;
exchange.setRequestURI("/?action=wait" + delay);
HttpClient httpClient = getHttpClient();
HttpDestination destination = httpClient.getDestination(address, false);
int connections = destination.getConnections();
httpClient.send(exchange);
Thread.sleep(delay / 2);
Assert.assertEquals(connections + 1, destination.getConnections());
exchange.cancel();
Assert.assertEquals(connections, destination.getConnections());
}
/* ------------------------------------------------------------ */
protected abstract HttpClient getHttpClient();
/* ------------------------------------------------------------ */
protected Address newAddress()
{
return new Address("localhost", connector.getLocalPort());
}
/* ------------------------------------------------------------ */
private static class EmptyHandler extends AbstractHandler
{
/* ------------------------------------------------------------ */
public void handle(String path, Request request, HttpServletRequest httpRequest, HttpServletResponse httpResponse) throws IOException, ServletException
{
request.setHandled(true);
String action = httpRequest.getParameter("action");
if (action != null)
{
if ("body".equals(action))
{
ServletOutputStream output = httpResponse.getOutputStream();
output.write("body".getBytes("UTF-8"));
// output.flush();
}
else if ("throw".equals(action))
{
throw new ServletException();
}
else if (action.startsWith("wait"))
{
long sleep = Long.valueOf(action.substring("wait".length()));
long start=System.currentTimeMillis();
try
{
Thread.sleep(sleep);
long end=System.currentTimeMillis();
assertTrue("Duration "+(end-start)+" >~ "+sleep,(end-start)>sleep-100);
}
catch (InterruptedException x)
{
Thread.currentThread().interrupt();
}
}
}
}
}
/* ------------------------------------------------------------ */
protected static class TestHttpExchange extends ContentExchange
{
private boolean responseCompleted;
private boolean failed = false;
private boolean expired = false;
/* ------------------------------------------------------------ */
protected TestHttpExchange()
{
super(true);
}
/* ------------------------------------------------------------ */
@Override
protected synchronized void onResponseComplete() throws IOException
{
this.responseCompleted = true;
}
/* ------------------------------------------------------------ */
public synchronized boolean isResponseCompleted()
{
return responseCompleted;
}
/* ------------------------------------------------------------ */
@Override
protected synchronized void onException(Throwable ex)
{
LOG.debug(ex);
if (ex instanceof SocketTimeoutException ||
ex.getCause() instanceof SocketTimeoutException)
expired=true;
else
failed = true;
}
/* ------------------------------------------------------------ */
public synchronized boolean isFailed()
{
return failed;
}
/* ------------------------------------------------------------ */
@Override
protected synchronized void onExpire()
{
this.expired = true;
}
/* ------------------------------------------------------------ */
public synchronized boolean isExpired()
{
return expired;
}
}
}