// ======================================================================== // 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.http; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertTrue; import java.io.IOException; import java.util.concurrent.atomic.AtomicInteger; import org.eclipse.jetty.io.Buffer; import org.eclipse.jetty.io.Buffers.Type; import org.eclipse.jetty.io.ByteArrayBuffer; import org.eclipse.jetty.io.ByteArrayEndPoint; import org.eclipse.jetty.io.PooledBuffers; import org.eclipse.jetty.io.SimpleBuffers; import org.eclipse.jetty.io.View; import org.junit.Test; public class HttpGeneratorClientTest { public final static String CONTENT="The quick brown fox jumped over the lazy dog.\nNow is the time for all good men to come to the aid of the party\nThe moon is blue to a fish in love.\n"; public final static String[] connect={null,"keep-alive","close"}; @Test public void testContentLength() throws Exception { Buffer bb=new ByteArrayBuffer(8096); Buffer sb=new ByteArrayBuffer(1500); ByteArrayEndPoint endp = new ByteArrayEndPoint(new byte[0],4096); HttpGenerator generator = new HttpGenerator(new SimpleBuffers(sb,bb),endp); generator.setRequest("GET","/usr"); HttpFields fields = new HttpFields(); fields.add("Header","Value"); fields.add("Content-Type","text/plain"); String content = "The quick brown fox jumped over the lazy dog"; fields.addLongField("Content-Length",content.length()); generator.completeHeader(fields,false); generator.addContent(new ByteArrayBuffer(content).asMutableBuffer(),true); generator.flushBuffer(); generator.complete(); generator.flushBuffer(); String result=endp.getOut().toString().replace("\r\n","|").replace('\r','|').replace('\n','|'); assertEquals("GET /usr HTTP/1.1|Header: Value|Content-Type: text/plain|Content-Length: 44||"+content,result); } @Test public void testAutoContentLength() throws Exception { Buffer bb=new ByteArrayBuffer(8096); Buffer sb=new ByteArrayBuffer(1500); ByteArrayEndPoint endp = new ByteArrayEndPoint(new byte[0],4096); HttpGenerator generator = new HttpGenerator(new SimpleBuffers(sb,bb),endp); generator.setRequest("GET","/usr"); HttpFields fields = new HttpFields(); fields.add("Header","Value"); fields.add("Content-Type","text/plain"); String content = "The quick brown fox jumped over the lazy dog"; generator.addContent(new ByteArrayBuffer(content).asMutableBuffer(),true); generator.completeHeader(fields,true); generator.flushBuffer(); generator.complete(); generator.flushBuffer(); String result=endp.getOut().toString().replace("\r\n","|").replace('\r','|').replace('\n','|'); assertEquals("GET /usr HTTP/1.1|Header: Value|Content-Type: text/plain|Content-Length: 44||"+content,result); } @Test public void testChunked() throws Exception { Buffer bb=new ByteArrayBuffer(8096); Buffer sb=new ByteArrayBuffer(1500); ByteArrayEndPoint endp = new ByteArrayEndPoint(new byte[0],4096); HttpGenerator generator = new HttpGenerator(new SimpleBuffers(sb,bb),endp); generator.setRequest("GET","/usr"); HttpFields fields = new HttpFields(); fields.add("Header","Value"); fields.add("Content-Type","text/plain"); String content = "The quick brown fox jumped over the lazy dog"; generator.completeHeader(fields,false); generator.addContent(new ByteArrayBuffer(content).asMutableBuffer(),false); generator.flushBuffer(); generator.complete(); generator.flushBuffer(); String result=endp.getOut().toString().replace("\r\n","|").replace('\r','|').replace('\n','|'); assertEquals("GET /usr HTTP/1.1|Header: Value|Content-Type: text/plain|Transfer-Encoding: chunked||2C|"+content+"|0||",result); } /** * When the endpoint experiences back pressure, check that chunked transfer does not * screw up the chunking by leaving out the second chunk header. */ @Test public void testChunkedWithBackPressure() throws Exception { final AtomicInteger availableChannelBytes = new AtomicInteger(500); ByteArrayEndPoint endp = new ByteArrayEndPoint(new byte[0],4096) { @Override public int flush(Buffer buffer) throws IOException { // Simulate a socket that can only take 500 bytes at a time View view = new View(buffer, buffer.markIndex(), buffer.getIndex(), Math.min(buffer.putIndex(), buffer.getIndex()+availableChannelBytes.get()), buffer.isReadOnly()?Buffer.READONLY:Buffer.READWRITE); int read = super.flush(view); buffer.skip(read); availableChannelBytes.getAndAdd(-1*read); return read; } }; PooledBuffers pool = new PooledBuffers(Type.BYTE_ARRAY,1416,Type.BYTE_ARRAY,8096,Type.BYTE_ARRAY,10240); HttpGenerator generator = new HttpGenerator(pool,endp); generator.setRequest("GET","/usr"); HttpFields fields = new HttpFields(); fields.add("Header","Value"); fields.add("Content-Type","text/plain"); String content = "The quick brown fox jumped, "; // addContent only goes into "bypass" mode if the content is longer than 1024 characters. while (content.length() < 1024) { content = content + content; } String content2 = "over the lazy dog"; generator.completeHeader(fields,false); generator.addContent(new ByteArrayBuffer(content).asMutableBuffer(),false); generator.addContent(new ByteArrayBuffer(content2).asMutableBuffer(),false); // Now we'll allow more bytes to flow availableChannelBytes.set(5000); generator.flushBuffer(); generator.complete(); generator.flushBuffer(); String result=endp.getOut().toString(); System.err.println("result:"+result); result=result.replace("\r\n","|").replace('\r','|').replace('\n','|'); assertEquals("GET /usr HTTP/1.1|Header: Value|Content-Type: text/plain|Transfer-Encoding: chunked||700|"+content+"|11|"+content2+"|0||",result); } @Test public void testHTTP() throws Exception { Buffer bb=new ByteArrayBuffer(8096); Buffer sb=new ByteArrayBuffer(1500); HttpFields fields = new HttpFields(); ByteArrayEndPoint endp = new ByteArrayEndPoint(new byte[0],4096); HttpGenerator hb = new HttpGenerator(new SimpleBuffers(sb,bb),endp); Handler handler = new Handler(); HttpParser parser=null; // For HTTP version for (int v=9;v<=11;v++) { // For each test result for (int r=0;r<tr.length;r++) { // chunks = 1 to 3 for (int chunks=1;chunks<=6;chunks++) { // For none, keep-alive, close for (int c=0;c<connect.length;c++) { String t="v="+v+",r="+r+",chunks="+chunks+",c="+c+",tr="+tr[r]; // System.err.println(t); hb.reset(); endp.reset(); fields.clear(); // System.out.println("TEST: "+t); try { tr[r].build(v,hb,connect[c],null,chunks, fields); } catch(IllegalStateException e) { if (v<10 || v==10 && chunks>2) continue; System.err.println(t); throw e; } String request=endp.getOut().toString(); // System.out.println(request+(hb.isPersistent()?"...\n":"---\n")); assertTrue(t,hb.isPersistent()); if (v==9) { assertEquals(t,"GET /context/path/info\r\n", request); continue; } parser=new HttpParser(new ByteArrayBuffer(request.getBytes()), handler); try { parser.parse(); } catch(IOException e) { if (tr[r].body!=null) throw e; continue; } if (tr[r].body!=null) assertEquals(t,tr[r].body, this.content); if (v==10) assertTrue(t,hb.isPersistent() || tr[r].values[1]==null || c==2 || c==0); else assertTrue(t,hb.isPersistent() || c==2); assertTrue(t,tr[r].values[1]==null || content.length()==Integer.parseInt(tr[r].values[1])); } } } } } private static final String[] headers= { "Content-Type","Content-Length","Connection","Transfer-Encoding","Other"}; private static class TR { private String[] values=new String[headers.length]; private String body; private TR(String ct, String cl ,String content) { values[0]=ct; values[1]=cl; values[4]="value"; this.body=content; } private void build(int version,HttpGenerator hb, String connection, String te, int chunks, HttpFields fields) throws Exception { values[2]=connection; values[3]=te; hb.setRequest(HttpMethods.GET,"/context/path/info"); hb.setVersion(version); for (int i=0;i<headers.length;i++) { if (values[i]==null) continue; fields.put(new ByteArrayBuffer(headers[i]),new ByteArrayBuffer(values[i])); } if (body!=null) { int inc=1+body.length()/chunks; Buffer buf=new ByteArrayBuffer(body); View view = new View(buf); for (int i=1;i<chunks;i++) { view.setPutIndex(i*inc); view.setGetIndex((i-1)*inc); hb.addContent(view,Generator.MORE); if (hb.isBufferFull() && hb.isState(AbstractGenerator.STATE_HEADER)) hb.completeHeader(fields, Generator.MORE); if (i%2==0) { if (hb.isState(AbstractGenerator.STATE_HEADER)) { if (version<11) fields.addLongField("Content-Length",body.length()); hb.completeHeader(fields, Generator.MORE); } hb.flushBuffer(); } } view.setPutIndex(buf.putIndex()); view.setGetIndex((chunks-1)*inc); hb.addContent(view,Generator.LAST); if(hb.isState(AbstractGenerator.STATE_HEADER)) hb.completeHeader(fields, Generator.LAST); } else { hb.completeHeader(fields, Generator.LAST); } hb.complete(); while(!hb.isComplete()) hb.flushBuffer(); } @Override public String toString() { return "["+values[0]+","+values[1]+","+(body==null?"none":"_content")+"]"; } } private final TR[] tr = { /* 0 */ new TR(null,null,null), /* 1 */ new TR(null,null,CONTENT), /* 3 */ new TR(null,""+CONTENT.length(),CONTENT), /* 4 */ new TR("text/html",null,null), /* 5 */ new TR("text/html",null,CONTENT), /* 7 */ new TR("text/html",""+CONTENT.length(),CONTENT), }; private String content; private String f0; private String f1; private String f2; private String[] hdr; private String[] val; private int h; private class Handler extends HttpParser.EventHandler { private int index=0; @Override public void content(Buffer ref) { if (index == 0) content= ""; content= content.substring(0, index) + ref; index+=ref.length(); } @Override public void startRequest(Buffer tok0, Buffer tok1, Buffer tok2) { h= -1; hdr= new String[9]; val= new String[9]; f0= tok0.toString(); f1= tok1.toString(); if (tok2!=null) f2= tok2.toString(); else f2=null; index=0; // System.out.println(f0+" "+f1+" "+f2); } /* (non-Javadoc) * @see org.eclipse.jetty.EventHandler#startResponse(org.eclipse.io.Buffer, int, org.eclipse.io.Buffer) */ @Override public void startResponse(Buffer version, int status, Buffer reason) { h= -1; hdr= new String[9]; val= new String[9]; f0= version.toString(); f1= ""+status; if (reason!=null) f2= reason.toString(); else f2=null; index=0; } @Override public void parsedHeader(Buffer name,Buffer value) { hdr[++h]= name.toString(); val[h]= value.toString(); } @Override public void headerComplete() { content= null; } @Override public void messageComplete(long contentLength) { } } }