// ======================================================================== // Copyright (c) 2004-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.server; import java.io.BufferedReader; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; import java.io.LineNumberReader; import java.io.OutputStream; import java.net.Socket; import java.net.URL; import java.util.Arrays; import java.util.Random; import java.util.concurrent.Exchanger; import javax.servlet.ServletException; import javax.servlet.ServletOutputStream; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import junit.framework.Assert; import org.eclipse.jetty.io.EndPoint; import org.eclipse.jetty.server.handler.AbstractHandler; import org.eclipse.jetty.util.IO; import org.eclipse.jetty.util.StringUtil; import org.eclipse.jetty.util.log.Log; import org.eclipse.jetty.util.log.StdErrLog; import org.hamcrest.Matchers; import org.junit.Test; import org.junit.matchers.JUnitMatchers; import static org.hamcrest.Matchers.greaterThanOrEqualTo; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotSame; import static org.junit.Assert.assertTrue; import static org.junit.Assert.assertThat; /** * */ public abstract class HttpServerTestBase extends HttpServerTestFixture { /** The request. */ private static final String REQUEST1_HEADER="POST / HTTP/1.0\n"+"Host: localhost\n"+"Content-Type: text/xml; charset=utf-8\n"+"Connection: close\n"+"Content-Length: "; private static final String REQUEST1_CONTENT="<?xml version=\"1.0\" encoding=\"ISO-8859-1\"?>\n" +"<nimbus xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"\n"+" xsi:noNamespaceSchemaLocation=\"nimbus.xsd\" version=\"1.0\">\n" +"</nimbus>"; private static final String REQUEST1=REQUEST1_HEADER+REQUEST1_CONTENT.getBytes().length+"\n\n"+REQUEST1_CONTENT; /** The expected response. */ private static final String RESPONSE1="HTTP/1.1 200 OK\n"+"Content-Length: 13\n"+"Server: Jetty("+Server.getVersion()+")\n"+"\n"+"Hello world\n"; // Break the request up into three pieces, splitting the header. private static final String FRAGMENT1=REQUEST1.substring(0,16); private static final String FRAGMENT2=REQUEST1.substring(16,34); private static final String FRAGMENT3=REQUEST1.substring(34); /** Second test request. */ protected static final String REQUEST2_HEADER= "POST / HTTP/1.0\n"+ "Host: localhost\n"+ "Content-Type: text/xml;charset=ISO-8859-1\n"+ "Content-Length: "; protected static final String REQUEST2_CONTENT= "<?xml version=\"1.0\" encoding=\"ISO-8859-1\"?>\n"+ "<nimbus xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"\n"+ " xsi:noNamespaceSchemaLocation=\"nimbus.xsd\" version=\"1.0\">\n"+ " <request requestId=\"1\">\n"+ " <getJobDetails>\n"+ " <jobId>73</jobId>\n"+ " </getJobDetails>\n"+ " </request>\n"+ "</nimbus>"; protected static final String REQUEST2=REQUEST2_HEADER+REQUEST2_CONTENT.getBytes().length+"\n\n"+REQUEST2_CONTENT; /** The second expected response. */ protected static final String RESPONSE2_CONTENT= "<?xml version=\"1.0\" encoding=\"ISO-8859-1\"?>\n"+ "<nimbus xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"\n"+ " xsi:noNamespaceSchemaLocation=\"nimbus.xsd\" version=\"1.0\">\n"+ " <request requestId=\"1\">\n"+ " <getJobDetails>\n"+ " <jobId>73</jobId>\n"+ " </getJobDetails>\n"+ " </request>\n" +"</nimbus>\n"; protected static final String RESPONSE2= "HTTP/1.1 200 OK\n"+ "Content-Type: text/xml;charset=ISO-8859-1\n"+ "Content-Length: "+RESPONSE2_CONTENT.getBytes().length+"\n"+ "Server: Jetty("+Server.getVersion()+")\n"+ "\n"+ RESPONSE2_CONTENT; /* * Feed the server the entire request at once. */ @Test public void testRequest1() throws Exception { configureServer(new HelloWorldHandler()); Socket client=newSocket(HOST,_connector.getLocalPort()); try { OutputStream os=client.getOutputStream(); os.write(REQUEST1.getBytes()); os.flush(); // Read the response. String response=readResponse(client); // Check the response assertEquals("response",RESPONSE1,response); } finally { client.close(); } } @Test public void testFragmentedChunk() throws Exception { configureServer(new EchoHandler()); Socket client=newSocket(HOST,_connector.getLocalPort()); try { OutputStream os=client.getOutputStream(); os.write(("GET /R2 HTTP/1.1\015\012"+ "Host: localhost\015\012"+ "Transfer-Encoding: chunked\015\012"+ "Content-Type: text/plain\015\012"+ "Connection: close\015\012"+ "\015\012").getBytes()); os.flush(); Thread.sleep(PAUSE); os.write(("5\015\012").getBytes()); os.flush(); Thread.sleep(PAUSE); os.write(("ABCDE\015\012"+ "0;\015\012\015\012").getBytes()); os.flush(); // Read the response. String response=readResponse(client); assertTrue (response.indexOf("200")>0); } finally { client.close(); } } /* * Feed the server fragmentary headers and see how it copes with it. */ @Test public void testRequest1Fragments() throws Exception, InterruptedException { configureServer(new HelloWorldHandler()); Socket client=newSocket(HOST,_connector.getLocalPort()); try { OutputStream os=client.getOutputStream(); // Write a fragment, flush, sleep, write the next fragment, etc. os.write(FRAGMENT1.getBytes()); os.flush(); Thread.sleep(PAUSE); os.write(FRAGMENT2.getBytes()); os.flush(); Thread.sleep(PAUSE); os.write(FRAGMENT3.getBytes()); os.flush(); // Read the response String response = readResponse(client); // Check the response assertEquals("response",RESPONSE1,response); } finally { client.close(); } } @Test public void testRequest2() throws Exception { configureServer(new EchoHandler()); byte[] bytes=REQUEST2.getBytes(); for (int i=0; i<LOOPS; i++) { Socket client=newSocket(HOST,_connector.getLocalPort()); try { OutputStream os=client.getOutputStream(); os.write(bytes); os.flush(); // Read the response String response=readResponse(client); // Check the response assertEquals("response "+i,RESPONSE2,response); } catch(IOException e) { e.printStackTrace(); _server.dumpStdErr(); throw e; } finally { client.close(); } } } @Test public void testRequest2Fragments() throws Exception { configureServer(new EchoHandler()); byte[] bytes=REQUEST2.getBytes(); final int pointCount=2; Random random=new Random(System.currentTimeMillis()); for (int i=0; i<LOOPS; i++) { int[] points=new int[pointCount]; StringBuilder message=new StringBuilder(); message.append("iteration #").append(i + 1); // Pick fragment points at random for (int j=0; j<points.length; ++j) { points[j]=random.nextInt(bytes.length); } // Sort the list Arrays.sort(points); Socket client=newSocket(HOST,_connector.getLocalPort()); try { OutputStream os=client.getOutputStream(); writeFragments(bytes,points,message,os); // Read the response String response=readResponse(client); // Check the response assertEquals("response for "+i+" "+message.toString(),RESPONSE2,response); } finally { client.close(); } } } @Test public void testRequest2Iterate() throws Exception { configureServer(new EchoHandler()); byte[] bytes=REQUEST2.getBytes(); for (int i=0; i<bytes.length; i+=3) { int[] points=new int[] { i }; StringBuilder message=new StringBuilder(); message.append("iteration #").append(i + 1); // Sort the list Arrays.sort(points); Socket client=newSocket(HOST,_connector.getLocalPort()); try { OutputStream os=client.getOutputStream(); writeFragments(bytes,points,message,os); // Read the response String response=readResponse(client); // Check the response assertEquals("response for "+i+" "+message.toString(),RESPONSE2,response); } finally { client.close(); } } } /* * After several iterations, I generated some known bad fragment points. */ @Test public void testRequest2KnownBad() throws Exception { configureServer(new EchoHandler()); byte[] bytes=REQUEST2.getBytes(); int[][] badPoints=new int[][] { { 70 }, // beginning here, drops last line of request { 71 }, // no response at all { 72 }, // again starts drops last line of request { 74 }, // again, no response at all }; for (int i=0; i<badPoints.length; ++i) { Socket client=newSocket(HOST,_connector.getLocalPort()); try { OutputStream os=client.getOutputStream(); StringBuilder message=new StringBuilder(); message.append("iteration #").append(i + 1); writeFragments(bytes,badPoints[i],message,os); // Read the response String response=readResponse(client); // Check the response // TODO - change to equals when code gets fixed assertNotSame("response for "+message.toString(),RESPONSE2,response); } finally { client.close(); } } } @Test public void testFlush() throws Exception { configureServer(new DataHandler()); String[] encoding = {"NONE","UTF-8","ISO-8859-1","ISO-8859-2"}; for (int e =0; e<encoding.length;e++) { for (int b=1;b<=128;b=b==1?2:b==2?32:b==32?128:129) { for (int w=41;w<42;w+=4096) { for (int c=0;c<1;c++) { String test=encoding[e]+"x"+b+"x"+w+"x"+c; try { URL url=new URL(_scheme+"://"+HOST+":"+_connector.getLocalPort()+"/?writes="+w+"&block="+b+ (e==0?"":("&encoding="+encoding[e]))+(c==0?"&chars=true":"")); InputStream in = (InputStream)url.getContent(); String response=IO.toString(in,e==0?null:encoding[e]); assertEquals(test,b*w,response.length()); } catch(Exception x) { System.err.println(test); x.printStackTrace(); throw x; } } } } } } @Test public void testBlockingWhileReadingRequestContent() throws Exception { configureServer(new DataHandler()); long start=System.currentTimeMillis(); Socket client=newSocket(HOST,_connector.getLocalPort()); try { OutputStream os=client.getOutputStream(); InputStream is=client.getInputStream(); os.write(( "GET /data?writes=1024&block=256 HTTP/1.1\r\n"+ "host: "+HOST+":"+_connector.getLocalPort()+"\r\n"+ "connection: close\r\n"+ "content-type: unknown\r\n"+ "content-length: 30\r\n"+ "\r\n" ).getBytes()); os.flush(); Thread.sleep(200); os.write(( "\r\n23456890" ).getBytes()); os.flush(); Thread.sleep(1000); os.write(( "abcdefghij" ).getBytes()); os.flush(); Thread.sleep(1000); os.write(( "0987654321\r\n" ).getBytes()); os.flush(); int total=0; int len=0; byte[] buf=new byte[1024*64]; while(len>=0) { Thread.sleep(100); len=is.read(buf); if (len>0) total+=len; } assertTrue(total>(1024*256)); assertTrue(30000L>(System.currentTimeMillis()-start)); } finally { client.close(); } } @Test public void testBlockingWhileWritingResponseContent() throws Exception { configureServer(new DataHandler()); long start=System.currentTimeMillis(); Socket client=newSocket(HOST,_connector.getLocalPort()); int total=0; try { OutputStream os=client.getOutputStream(); InputStream is=client.getInputStream(); os.write(( "GET /data?writes=512&block=1024 HTTP/1.1\r\n"+ "host: "+HOST+":"+_connector.getLocalPort()+"\r\n"+ "connection: close\r\n"+ "content-type: unknown\r\n"+ "\r\n" ).getBytes()); os.flush(); int len=0; byte[] buf=new byte[1024*32]; int i=0; while(len>=0) { if (i++%10==0) Thread.sleep(1000); len=is.read(buf); if (len>0) total+=len; } assertTrue(total>(512*1024)); assertTrue(30000L>(System.currentTimeMillis()-start)); } finally { //System.err.println("Got "+total+" of "+(512*1024)); client.close(); } } @Test public void testBigBlocks() throws Exception { configureServer(new BigBlockHandler()); Socket client=newSocket(HOST,_connector.getLocalPort()); client.setSoTimeout(20000); try { OutputStream os=client.getOutputStream(); BufferedReader in = new BufferedReader(new InputStreamReader(client.getInputStream())); os.write(( "GET /r1 HTTP/1.1\r\n"+ "host: "+HOST+":"+_connector.getLocalPort()+"\r\n"+ "\r\n"+ "GET /r2 HTTP/1.1\r\n"+ "host: "+HOST+":"+_connector.getLocalPort()+"\r\n"+ "connection: close\r\n"+ "\r\n" ).getBytes()); os.flush(); // read the chunked response header boolean chunked=false; boolean closed=false; while(true) { String line=in.readLine(); if (line==null || line.length()==0) break; chunked|="Transfer-Encoding: chunked".equals(line); closed|="Connection: close".equals(line); } Assert.assertTrue(chunked); Assert.assertFalse(closed); // Read the chunks int max=Integer.MIN_VALUE; while(true) { String chunk=in.readLine(); String line=in.readLine(); if (line.length()==0) break; int len=line.length(); Assert.assertEquals(Integer.valueOf(chunk,16).intValue(),len); if (max<len) max=len; } // Check that a direct content buffer was used as a chunk Assert.assertEquals(128*1024,max); // read and check the times are < 999ms String[] times=in.readLine().split(","); for (String t: times) Assert.assertTrue(Integer.valueOf(t).intValue()<999); // read the EOF chunk String end=in.readLine(); Assert.assertEquals("0",end); end=in.readLine(); Assert.assertEquals(0,end.length()); // read the non-chunked response header chunked=false; closed=false; while(true) { String line=in.readLine(); if (line==null || line.length()==0) break; chunked|="Transfer-Encoding: chunked".equals(line); closed|="Connection: close".equals(line); } Assert.assertFalse(chunked); Assert.assertTrue(closed); String bigline = in.readLine(); Assert.assertEquals(10*128*1024,bigline.length()); // read and check the times are < 999ms times=in.readLine().split(","); for (String t: times) Assert.assertTrue(t,Integer.valueOf(t).intValue()<999); // check close Assert.assertTrue(in.readLine()==null); } finally { client.close(); } } // Handler that sends big blocks of data in each of 10 writes, and then sends the time it took for each big block. protected static class BigBlockHandler extends AbstractHandler { public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException { byte[] buf = new byte[128*1024]; for (int i=0;i<buf.length;i++) buf[i]=(byte)("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_".charAt(i%63)); baseRequest.setHandled(true); response.setStatus(200); response.setContentType("text/plain"); ServletOutputStream out=response.getOutputStream(); long[] times=new long[10]; for (int i=0;i<times.length;i++) { // System.err.println("\nBLOCK "+request.getRequestURI()+" "+i); long start=System.currentTimeMillis(); out.write(buf); long end=System.currentTimeMillis(); times[i]=end-start; // System.err.println("Block "+request.getRequestURI()+" "+i+" "+times[i]); } out.println(); for (long t : times) { out.print(t); out.print(","); } out.close(); } } @Test public void testPipeline() throws Exception { configureServer(new HelloWorldHandler()); //for (int pipeline=1;pipeline<32;pipeline++) for (int pipeline=1;pipeline<32;pipeline++) { Socket client=newSocket(HOST,_connector.getLocalPort()); try { client.setSoTimeout(5000); OutputStream os=client.getOutputStream(); String request=""; for (int i=1;i<pipeline;i++) request+= "GET /data?writes=1&block=16&id="+i+" HTTP/1.1\r\n"+ "host: "+HOST+":"+_connector.getLocalPort()+"\r\n"+ "user-agent: testharness/1.0 (blah foo/bar)\r\n"+ "accept-encoding: nothing\r\n"+ "cookie: aaa=1234567890\r\n"+ "\r\n"; request+= "GET /data?writes=1&block=16 HTTP/1.1\r\n"+ "host: "+HOST+":"+_connector.getLocalPort()+"\r\n"+ "user-agent: testharness/1.0 (blah foo/bar)\r\n"+ "accept-encoding: nothing\r\n"+ "cookie: aaa=bbbbbb\r\n"+ "Connection: close\r\n"+ "\r\n"; os.write(request.getBytes()); os.flush(); LineNumberReader in = new LineNumberReader(new InputStreamReader(client.getInputStream())); String line = in.readLine(); int count=0; while (line!=null) { if ("HTTP/1.1 200 OK".equals(line)) count++; line = in.readLine(); } assertEquals(pipeline,count); } finally { client.close(); } } } @Test public void testRecycledWriters() throws Exception { configureServer(new EchoHandler()); Socket client=newSocket(HOST,_connector.getLocalPort()); try { OutputStream os=client.getOutputStream(); InputStream is=client.getInputStream(); os.write(( "POST /echo?charset=utf-8 HTTP/1.1\r\n"+ "host: "+HOST+":"+_connector.getLocalPort()+"\r\n"+ "content-type: text/plain; charset=utf-8\r\n"+ "content-length: 10\r\n"+ "\r\n").getBytes("iso-8859-1")); os.write(( "123456789\n" ).getBytes("utf-8")); os.write(( "POST /echo?charset=utf-8 HTTP/1.1\r\n"+ "host: "+HOST+":"+_connector.getLocalPort()+"\r\n"+ "content-type: text/plain; charset=utf-8\r\n"+ "content-length: 10\r\n"+ "\r\n" ).getBytes("iso-8859-1")); os.write(( "abcdefghZ\n" ).getBytes("utf-8")); String content="Wibble"; byte[] contentB=content.getBytes("utf-8"); os.write(( "POST /echo?charset=utf-16 HTTP/1.1\r\n"+ "host: "+HOST+":"+_connector.getLocalPort()+"\r\n"+ "content-type: text/plain; charset=utf-8\r\n"+ "content-length: "+contentB.length+"\r\n"+ "connection: close\r\n"+ "\r\n" ).getBytes("iso-8859-1")); os.write(contentB); os.flush(); ByteArrayOutputStream bout = new ByteArrayOutputStream(); IO.copy(is,bout); byte[] b=bout.toByteArray(); //System.err.println("OUTPUT: "+new String(b)); int i=0; while (b[i]!='Z') i++; int state=0; while(state!=4) { switch(b[i++]) { case '\r': if (state==0||state==2) state++; continue; case '\n': if (state==1||state==3) state++; continue; default: state=0; } } String in = new String(b,0,i,"utf-8"); assertTrue(in.indexOf("123456789")>=0); assertTrue(in.indexOf("abcdefghZ")>=0); assertTrue(in.indexOf("Wibble")<0); in = new String(b,i,b.length-i,"utf-16"); assertEquals("Wibble\n",in); } finally { client.close(); } } @Test public void testHead() throws Exception { configureServer(new EchoHandler(false)); Socket client=newSocket(HOST,_connector.getLocalPort()); try { OutputStream os=client.getOutputStream(); InputStream is=client.getInputStream(); os.write(( "POST /R1 HTTP/1.1\015\012"+ "Host: "+HOST+":"+_connector.getLocalPort()+"\r\n"+ "content-type: text/plain; charset=utf-8\r\n"+ "content-length: 10\r\n"+ "\015\012"+ "123456789\n" + "HEAD /R1 HTTP/1.1\015\012"+ "Host: "+HOST+":"+_connector.getLocalPort()+"\015\012"+ "content-type: text/plain; charset=utf-8\r\n"+ "content-length: 10\r\n"+ "\015\012"+ "123456789\n"+ "POST /R1 HTTP/1.1\015\012"+ "Host: "+HOST+":"+_connector.getLocalPort()+"\015\012"+ "content-type: text/plain; charset=utf-8\r\n"+ "content-length: 10\r\n"+ "Connection: close\015\012"+ "\015\012"+ "123456789\n" ).getBytes("iso-8859-1")); String in = IO.toString(is); int index=in.indexOf("123456789"); assertTrue(index>0); index=in.indexOf("123456789",index+1); assertTrue(index>0); index=in.indexOf("123456789",index+1); assertTrue(index==-1); } finally { client.close(); } } @Test public void testRecycledReaders() throws Exception { configureServer(new EchoHandler()); Socket client=newSocket(HOST,_connector.getLocalPort()); try { OutputStream os=client.getOutputStream(); InputStream is=client.getInputStream(); os.write(( "POST /echo?charset=utf-8 HTTP/1.1\r\n"+ "host: "+HOST+":"+_connector.getLocalPort()+"\r\n"+ "content-type: text/plain; charset=utf-8\r\n"+ "content-length: 10\r\n"+ "\r\n").getBytes("iso-8859-1")); os.write(( "123456789\n" ).getBytes("utf-8")); os.write(( "POST /echo?charset=utf-8 HTTP/1.1\r\n"+ "host: "+HOST+":"+_connector.getLocalPort()+"\r\n"+ "content-type: text/plain; charset=utf-8\r\n"+ "content-length: 10\r\n"+ "\r\n" ).getBytes("iso-8859-1")); os.write(( "abcdefghi\n" ).getBytes("utf-8")); String content="Wibble"; byte[] contentB=content.getBytes("utf-16"); os.write(( "POST /echo?charset=utf-8 HTTP/1.1\r\n"+ "host: "+HOST+":"+_connector.getLocalPort()+"\r\n"+ "content-type: text/plain; charset=utf-16\r\n"+ "content-length: "+contentB.length+"\r\n"+ "connection: close\r\n"+ "\r\n" ).getBytes("iso-8859-1")); os.write(contentB); os.flush(); String in = IO.toString(is); assertTrue(in.indexOf("123456789")>=0); assertTrue(in.indexOf("abcdefghi")>=0); assertTrue(in.indexOf("Wibble")>=0); } finally { client.close(); } } @Test public void testBlockedClient() throws Exception { configureServer(new HelloWorldHandler()); Socket client=newSocket(HOST,_connector.getLocalPort()); try { OutputStream os=client.getOutputStream(); InputStream is=client.getInputStream(); // Send a request with chunked input and expect 100 os.write(( "GET / HTTP/1.1\r\n"+ "Host: "+HOST+":"+_connector.getLocalPort()+"\r\n"+ "Transfer-Encoding: chunked\r\n"+ "Expect: 100-continue\r\n"+ "Connection: Keep-Alive\r\n"+ "\r\n" ).getBytes()); // Never send a body. // HelloWorldHandler does not read content, so 100 is not sent. // So close will have to happen anyway, without reset! os.flush(); client.setSoTimeout(2000); long start=System.currentTimeMillis(); String in = IO.toString(is); assertTrue(System.currentTimeMillis()-start<1000); assertTrue(in.indexOf("Connection: close")>0); assertTrue(in.indexOf("Hello world")>0); } finally { client.close(); } } @Test public void testCommittedError() throws Exception { CommittedErrorHandler handler =new CommittedErrorHandler(); configureServer(handler); Socket client=newSocket(HOST,_connector.getLocalPort()); try { ((StdErrLog)Log.getLogger(AbstractHttpConnection.class)).setHideStacks(true); OutputStream os=client.getOutputStream(); InputStream is=client.getInputStream(); // Send a request os.write(( "GET / HTTP/1.1\r\n"+ "Host: "+HOST+":"+_connector.getLocalPort()+"\r\n" + "\r\n" ).getBytes()); os.flush(); client.setSoTimeout(2000); String in = IO.toString(is); assertEquals(-1,is.read()); // Closed by error! assertTrue(in.indexOf("HTTP/1.1 200 OK")>=0); assertTrue(in.indexOf("Transfer-Encoding: chunked")>0); assertTrue(in.indexOf("Now is the time for all good men to come to the aid of the party")>0); assertTrue(in.indexOf("\r\n0\r\n")==-1); // chunking is interrupted by error close client.close(); Thread.sleep(100); assertTrue(!handler._endp.isOpen()); } finally { ((StdErrLog)Log.getLogger(AbstractHttpConnection.class)).setHideStacks(false); if (!client.isClosed()) client.close(); } } protected static class CommittedErrorHandler extends AbstractHandler { public EndPoint _endp; public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException { _endp=baseRequest.getConnection().getEndPoint(); response.setHeader("test","value"); response.setStatus(200); response.setContentType("text/plain"); response.getWriter().println("Now is the time for all good men to come to the aid of the party"); response.getWriter().flush(); response.flushBuffer(); throw new ServletException(new Exception("exception after commit")); } } protected static class AvailableHandler extends AbstractHandler { public Exchanger<Object> _ex = new Exchanger<Object>(); public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException { baseRequest.setHandled(true); response.setStatus(200); response.setContentType("text/plain"); InputStream in = request.getInputStream(); ServletOutputStream out=response.getOutputStream(); // should always be some input available, because of deferred dispatch. int avail=in.available(); out.println(avail); String buf=""; for (int i=0;i<avail;i++) buf+=(char)in.read(); avail=in.available(); out.println(avail); try { _ex.exchange(null); _ex.exchange(null); } catch(InterruptedException e) { e.printStackTrace(); } avail=in.available(); if (avail==0) { // handle blocking channel connectors buf+=(char)in.read(); avail=in.available(); out.println(avail+1); } else if (avail==1) { // handle blocking socket connectors buf+=(char)in.read(); avail=in.available(); out.println(avail+1); } else out.println(avail); while (avail>0) { buf+=(char)in.read(); avail=in.available(); } out.println(avail); out.println(buf); out.close(); } } @Test public void testAvailable() throws Exception { AvailableHandler ah=new AvailableHandler(); configureServer(ah); Socket client=newSocket(HOST,_connector.getLocalPort()); try { OutputStream os=client.getOutputStream(); InputStream is=client.getInputStream(); os.write(( "GET /data?writes=1024&block=256 HTTP/1.1\r\n"+ "host: "+HOST+":"+_connector.getLocalPort()+"\r\n"+ "connection: close\r\n"+ "content-type: unknown\r\n"+ "content-length: 30\r\n"+ "\r\n" ).getBytes()); os.flush(); Thread.sleep(500); os.write(( "1234567890" ).getBytes()); os.flush(); ah._ex.exchange(null); os.write(( "abcdefghijklmnopqrst" ).getBytes()); os.flush(); Thread.sleep(500); ah._ex.exchange(null); BufferedReader reader = new BufferedReader(new InputStreamReader(is)); // skip header while(reader.readLine().length()>0); assertThat(Integer.parseInt(reader.readLine()),Matchers.greaterThan(0)); assertEquals(0,Integer.parseInt(reader.readLine())); assertThat(Integer.parseInt(reader.readLine()),Matchers.greaterThan(0)); assertEquals(0,Integer.parseInt(reader.readLine())); assertEquals("1234567890abcdefghijklmnopqrst",reader.readLine()); } finally { client.close(); } } @Test public void testDualRequest1() throws Exception { configureServer(new HelloWorldHandler()); Socket client1=newSocket(HOST,_connector.getLocalPort()); Socket client2=newSocket(HOST,_connector.getLocalPort()); try { OutputStream os1=client1.getOutputStream(); OutputStream os2=client2.getOutputStream(); os1.write(REQUEST1.getBytes()); os2.write(REQUEST1.getBytes()); os1.flush(); os2.flush(); // Read the response. String response1=readResponse(client1); String response2=readResponse(client2); // Check the response assertEquals("client1",RESPONSE1,response1); assertEquals("client2",RESPONSE1,response2); } finally { client1.close(); client2.close(); } } /** * Read entire response from the client. Close the output. * * @param client Open client socket. * @return The response string. * @throws IOException in case of I/O problems */ protected static String readResponse(Socket client) throws IOException { BufferedReader br=null; StringBuilder sb=new StringBuilder(); try { br=new BufferedReader(new InputStreamReader(client.getInputStream())); String line; while ((line=br.readLine())!=null) { sb.append(line); sb.append('\n'); } return sb.toString(); } catch(IOException e) { System.err.println(e+" while reading '"+sb+"'"); throw e; } finally { if (br!=null) { br.close(); } } } private void writeFragments(byte[] bytes, int[] points, StringBuilder message, OutputStream os) throws IOException, InterruptedException { int last=0; // Write out the fragments for (int j=0; j<points.length; ++j) { int point=points[j]; os.write(bytes,last,point-last); last=point; os.flush(); Thread.sleep(PAUSE); // Update the log message message.append(" point #").append(j + 1).append(": ").append(point); } // Write the last fragment os.write(bytes,last,bytes.length-last); os.flush(); Thread.sleep(PAUSE); } @Test public void testUnreadInput () throws Exception { configureServer(new NoopHandler()); final int REQS=5; String content="This is a loooooooooooooooooooooooooooooooooo"+ "ooooooooooooooooooooooooooooooooooooooooooooo"+ "ooooooooooooooooooooooooooooooooooooooooooooo"+ "ooooooooooooooooooooooooooooooooooooooooooooo"+ "ooooooooooooooooooooooooooooooooooooooooooooo"+ "ooooooooooooooooooooooooooooooooooooooooooooo"+ "ooooooooooooooooooooooooooooooooooooooooooooo"+ "ooooooooooooooooooooooooooooooooooooooooooooo"+ "ooooooooooooooooooooooooooooooooooooooooooooo"+ "oooooooooooonnnnnnnnnnnnnnnnggggggggg content"+ new String(new char[65*1024]); final byte[] bytes = content.getBytes(); Socket client=newSocket(HOST,_connector.getLocalPort()); final OutputStream out=client.getOutputStream(); new Thread() { public void run() { try { for (int i=0; i<REQS; i++) { out.write("GET / HTTP/1.1\r\nHost: localhost\r\n".getBytes(StringUtil.__ISO_8859_1)); out.write(("Content-Length: "+bytes.length+"\r\n" + "\r\n").getBytes(StringUtil.__ISO_8859_1)); out.write(bytes,0,bytes.length); } out.write("GET / HTTP/1.1\r\nHost: localhost\r\nConnection: close\r\n\r\n".getBytes(StringUtil.__ISO_8859_1)); out.flush(); } catch(Exception e) { e.printStackTrace(); } } }.start(); String resps = readResponse(client); int offset=0; for (int i=0;i<(REQS+1);i++) { int ok=resps.indexOf("HTTP/1.1 200 OK",offset); assertThat("resp"+i,ok,greaterThanOrEqualTo(offset)); offset=ok+15; } } public class NoopHandler extends AbstractHandler { public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException { //don't read the input, just send something back ((Request)request).setHandled(true); response.setStatus(200); } } @Test public void testSuspendedPipeline() throws Exception { SuspendHandler suspend = new SuspendHandler(); suspend.setSuspendFor(30000); suspend.setResumeAfter(1000); configureServer(suspend); long start=System.currentTimeMillis(); Socket client=newSocket(HOST,_connector.getLocalPort()); try { OutputStream os=client.getOutputStream(); InputStream is=client.getInputStream(); // write an initial request os.write(( "GET / HTTP/1.1\r\n"+ "host: "+HOST+":"+_connector.getLocalPort()+"\r\n"+ "\r\n" ).getBytes()); os.flush(); Thread.sleep(200); // write an pipelined request os.write(( "GET / HTTP/1.1\r\n"+ "host: "+HOST+":"+_connector.getLocalPort()+"\r\n"+ "connection: close\r\n"+ "\r\n" ).getBytes()); os.flush(); String response=readResponse(client); assertThat(response,JUnitMatchers.containsString("RESUMEDHTTP/1.1 200 OK")); assertThat((System.currentTimeMillis()-start),greaterThanOrEqualTo(1999L)); // TODO This test should also check that that the CPU did not spin during the suspend. } finally { client.close(); } } }