package org.eclipse.jetty.client;
import static org.hamcrest.Matchers.*;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.net.Socket;
import java.net.SocketException;
import java.nio.channels.SocketChannel;
import java.util.Arrays;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicReference;
import javax.servlet.ServletException;
import javax.servlet.ServletInputStream;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.eclipse.jetty.http.HttpParser;
import org.eclipse.jetty.io.AsyncEndPoint;
import org.eclipse.jetty.io.Buffers;
import org.eclipse.jetty.io.EndPoint;
import org.eclipse.jetty.io.nio.AsyncConnection;
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.toolchain.test.IO;
import org.eclipse.jetty.util.log.Log;
import org.eclipse.jetty.util.log.Logger;
import org.junit.After;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Ignore;
import org.junit.Test;
public class TimeoutTest
{
private static final Logger logger = Log.getLogger(TimeoutTest.class);
private final AtomicInteger httpParses = new AtomicInteger();
private final AtomicInteger httpRequests = new AtomicInteger();
private ExecutorService threadPool;
private Server server;
private int serverPort;
private final AtomicReference<EndPoint> serverEndPoint = new AtomicReference<EndPoint>();
@Before
public void init() throws Exception
{
threadPool = Executors.newCachedThreadPool();
server = new Server();
SelectChannelConnector connector = new SelectChannelConnector()
{
@Override
protected AsyncConnection newConnection(SocketChannel channel, final AsyncEndPoint endPoint)
{
serverEndPoint.set(endPoint);
return new org.eclipse.jetty.server.AsyncHttpConnection(this,endPoint,getServer())
{
@Override
protected HttpParser newHttpParser(Buffers requestBuffers, EndPoint endPoint, HttpParser.EventHandler requestHandler)
{
return new HttpParser(requestBuffers,endPoint,requestHandler)
{
@Override
public int parseNext() throws IOException
{
httpParses.incrementAndGet();
return super.parseNext();
}
};
}
};
}
};
connector.setMaxIdleTime(2000);
// connector.setPort(5870);
connector.setPort(0);
server.addConnector(connector);
server.setHandler(new AbstractHandler()
{
public void handle(String target, Request request, HttpServletRequest httpRequest, HttpServletResponse httpResponse) throws IOException,
ServletException
{
httpRequests.incrementAndGet();
request.setHandled(true);
String contentLength = request.getHeader("Content-Length");
if (contentLength != null)
{
int length = Integer.parseInt(contentLength);
ServletInputStream input = request.getInputStream();
for (int i = 0; i < length; ++i)
input.read();
}
}
});
server.start();
serverPort = connector.getLocalPort();
httpRequests.set(0);
logger.debug(" => :{}",serverPort);
}
@After
public void destroy() throws Exception
{
if (server != null)
server.stop();
if (threadPool != null)
threadPool.shutdownNow();
}
private Socket newClient() throws IOException, InterruptedException
{
Socket client = new Socket("localhost",serverPort);
return client;
}
/**
* Test that performs a normal http POST request, with connection:close.
* Check that shutdownOutput is sufficient to close the server connection.
*/
@Test
public void testServerCloseClientDoesClose() throws Exception
{
// Log.getLogger("").setDebugEnabled(true);
final Socket client = newClient();
final OutputStream clientOutput = client.getOutputStream();
byte[] data = new byte[3 * 1024];
Arrays.fill(data,(byte)'Y');
String content = new String(data,"UTF-8");
// The request section
StringBuilder req = new StringBuilder();
req.append("POST / HTTP/1.1\r\n");
req.append("Host: localhost\r\n");
req.append("Content-Type: text/plain\r\n");
req.append("Content-Length: ").append(content.length()).append("\r\n");
req.append("Connection: close\r\n");
req.append("\r\n");
// and now, the POST content section.
req.append(content);
// Send request to server
clientOutput.write(req.toString().getBytes("UTF-8"));
clientOutput.flush();
InputStream in = null;
InputStreamReader isr = null;
BufferedReader reader = null;
try
{
in = client.getInputStream();
isr = new InputStreamReader(in);
reader = new BufferedReader(isr);
// Read the response header
String line = reader.readLine();
Assert.assertNotNull(line);
Assert.assertThat(line,startsWith("HTTP/1.1 200 "));
while ((line = reader.readLine()) != null)
{
if (line.trim().length() == 0)
{
break;
}
}
Assert.assertEquals("one request handled",1,httpRequests.get());
Assert.assertEquals("EOF received",-1,client.getInputStream().read());
// shutdown the output
client.shutdownOutput();
// Check that we did not spin
int httpParseCount = httpParses.get();
Assert.assertThat(httpParseCount,lessThan(50));
// Try to write another request (to prove that stream is closed)
try
{
clientOutput.write(req.toString().getBytes("UTF-8"));
clientOutput.flush();
Assert.fail("Should not have been able to send a second POST request (connection: close)");
}
catch(SocketException e)
{
}
Assert.assertEquals("one request handled",1,httpRequests.get());
}
finally
{
IO.close(reader);
IO.close(isr);
IO.close(in);
closeClient(client);
}
}
/**
* Test that performs a seemingly normal http POST request, but with
* a client that issues "connection: close", and then attempts to
* write a second POST request.
* <p>
* The connection should be closed by the server
*/
@Test
public void testServerCloseClientMoreDataSent() throws Exception
{
// Log.getLogger("").setDebugEnabled(true);
final Socket client = newClient();
final OutputStream clientOutput = client.getOutputStream();
byte[] data = new byte[3 * 1024];
Arrays.fill(data,(byte)'Y');
String content = new String(data,"UTF-8");
// The request section
StringBuilder req = new StringBuilder();
req.append("POST / HTTP/1.1\r\n");
req.append("Host: localhost\r\n");
req.append("Content-Type: text/plain\r\n");
req.append("Content-Length: ").append(content.length()).append("\r\n");
req.append("Connection: close\r\n");
req.append("\r\n");
// and now, the POST content section.
req.append(content);
// Send request to server
clientOutput.write(req.toString().getBytes("UTF-8"));
clientOutput.flush();
InputStream in = null;
InputStreamReader isr = null;
BufferedReader reader = null;
try
{
in = client.getInputStream();
isr = new InputStreamReader(in);
reader = new BufferedReader(isr);
// Read the response header
String line = reader.readLine();
Assert.assertNotNull(line);
Assert.assertThat(line,startsWith("HTTP/1.1 200 "));
while ((line = reader.readLine()) != null)
{
if (line.trim().length() == 0)
{
break;
}
}
Assert.assertEquals("EOF received",-1,client.getInputStream().read());
Assert.assertEquals("one request handled",1,httpRequests.get());
// Don't shutdown the output
// client.shutdownOutput();
// server side seeking EOF
Assert.assertTrue("is open",serverEndPoint.get().isOpen());
Assert.assertTrue("close sent",serverEndPoint.get().isOutputShutdown());
Assert.assertFalse("close not received",serverEndPoint.get().isInputShutdown());
// Check that we did not spin
TimeUnit.SECONDS.sleep(1);
int httpParseCount = httpParses.get();
Assert.assertThat(httpParseCount,lessThan(50));
// Write another request (which is ignored as the stream is closing), which causes real close.
clientOutput.write(req.toString().getBytes("UTF-8"));
clientOutput.flush();
// Check that we did not spin
TimeUnit.SECONDS.sleep(1);
httpParseCount = httpParses.get();
Assert.assertThat(httpParseCount,lessThan(50));
// server side is closed
Assert.assertFalse("is open",serverEndPoint.get().isOpen());
Assert.assertTrue("close sent",serverEndPoint.get().isOutputShutdown());
Assert.assertTrue("close not received",serverEndPoint.get().isInputShutdown());
Assert.assertEquals("one request handled",1,httpRequests.get());
}
finally
{
IO.close(reader);
IO.close(isr);
IO.close(in);
closeClient(client);
}
}
/**
* Test that performs a seemingly normal http POST request, but with
* a client that issues "connection: close", and then does not close
* the connection after reading the response.
* <p>
* The connection should be closed by the server after a timeout.
*/
@Test
public void testServerCloseClientDoesNotClose() throws Exception
{
// Log.getLogger("").setDebugEnabled(true);
final Socket client = newClient();
final OutputStream clientOutput = client.getOutputStream();
byte[] data = new byte[3 * 1024];
Arrays.fill(data,(byte)'Y');
String content = new String(data,"UTF-8");
// The request section
StringBuilder req = new StringBuilder();
req.append("POST / HTTP/1.1\r\n");
req.append("Host: localhost\r\n");
req.append("Content-Type: text/plain\r\n");
req.append("Content-Length: ").append(content.length()).append("\r\n");
req.append("Connection: close\r\n");
req.append("\r\n");
// and now, the POST content section.
req.append(content);
// Send request to server
clientOutput.write(req.toString().getBytes("UTF-8"));
clientOutput.flush();
InputStream in = null;
InputStreamReader isr = null;
BufferedReader reader = null;
try
{
in = client.getInputStream();
isr = new InputStreamReader(in);
reader = new BufferedReader(isr);
// Read the response header
String line = reader.readLine();
Assert.assertNotNull(line);
Assert.assertThat(line,startsWith("HTTP/1.1 200 "));
while ((line = reader.readLine()) != null)
{
if (line.trim().length() == 0)
{
break;
}
}
Assert.assertEquals("EOF received",-1,client.getInputStream().read());
Assert.assertEquals("one request handled",1,httpRequests.get());
// Don't shutdown the output
// client.shutdownOutput();
// server side seeking EOF
Assert.assertTrue("is open",serverEndPoint.get().isOpen());
Assert.assertTrue("close sent",serverEndPoint.get().isOutputShutdown());
Assert.assertFalse("close not received",serverEndPoint.get().isInputShutdown());
// Wait for the server idle timeout
TimeUnit.SECONDS.sleep(3);
int httpParseCount = httpParses.get();
Assert.assertThat(httpParseCount,lessThan(50));
// server side is closed
Assert.assertFalse("is open",serverEndPoint.get().isOpen());
Assert.assertTrue("close sent",serverEndPoint.get().isOutputShutdown());
Assert.assertTrue("close not received",serverEndPoint.get().isInputShutdown());
Assert.assertEquals("one request handled",1,httpRequests.get());
// client will eventually get broken pipe if it keeps writing
try
{
for (int i=0;i<1000;i++)
{
clientOutput.write(req.toString().getBytes("UTF-8"));
clientOutput.flush();
}
Assert.fail("Client should have seen a broken pipe");
}
catch(IOException e)
{
// expected broken pipe
}
}
finally
{
IO.close(reader);
IO.close(isr);
IO.close(in);
closeClient(client);
}
}
private void closeClient(Socket client) throws IOException
{
client.close();
}
}