Issue#1066 Simplify HttpGeneration

Reduce the transformations needed on header fields, so they can be more often
set directly and no need to split and recombine.

The Content-Length field is added IF it is needed for framing or if it was explicitly set

The Transfer-Encoding: chunk field is used only as a hint that there is content.

Connection fields are used as is, but are checked for close and keep-alive
This commit is contained in:
Greg Wilkins 2016-11-03 16:52:29 +11:00
parent 41d506fe4e
commit 6781a949b1
20 changed files with 398 additions and 297 deletions

View File

@ -1,10 +1,10 @@
org.eclipse.jetty.util.log.class=org.eclipse.jetty.util.log.StdErrLog
org.jboss.LEVEL=DEBUG
# org.jboss.LEVEL=DEBUG
org.eclipse.jetty.LEVEL=INFO
org.eclipse.jetty.util.DecoratedObjectFactory.LEVEL=DEBUG
# org.eclipse.jetty.util.DecoratedObjectFactory.LEVEL=DEBUG
# org.eclipse.jetty.LEVEL=DEBUG
org.eclipse.jetty.websocket.LEVEL=DEBUG
# org.eclipse.jetty.websocket.LEVEL=DEBUG
# org.eclipse.jetty.websocket.client.LEVEL=DEBUG

View File

@ -36,6 +36,7 @@ import org.eclipse.jetty.io.ByteArrayEndPoint;
import org.eclipse.jetty.toolchain.test.TestTracker;
import org.eclipse.jetty.toolchain.test.annotation.Slow;
import org.eclipse.jetty.util.Promise;
import org.hamcrest.Matchers;
import org.junit.After;
import org.junit.Assert;
import org.junit.Before;
@ -258,7 +259,7 @@ public class HttpSenderOverHTTPTest
String requestString = endPoint.takeOutputString();
Assert.assertTrue(requestString.startsWith("GET "));
Assert.assertTrue(requestString.endsWith("\r\n\r\n" + content1 + content2));
Assert.assertThat(requestString,Matchers.endsWith("\r\n\r\n" + content1 + content2));
Assert.assertTrue(headersLatch.await(5, TimeUnit.SECONDS));
Assert.assertTrue(successLatch.await(5, TimeUnit.SECONDS));
}

View File

@ -18,16 +18,18 @@
package org.eclipse.jetty.http;
import static org.eclipse.jetty.http.HttpStatus.INTERNAL_SERVER_ERROR_500;
import java.io.IOException;
import java.nio.BufferOverflowException;
import java.nio.ByteBuffer;
import java.util.Arrays;
import java.util.HashSet;
import java.util.Set;
import org.eclipse.jetty.http.HttpTokens.EndOfContent;
import org.eclipse.jetty.util.ArrayTrie;
import org.eclipse.jetty.util.BufferUtil;
import org.eclipse.jetty.util.StringUtil;
import org.eclipse.jetty.util.Trie;
import org.eclipse.jetty.util.log.Log;
import org.eclipse.jetty.util.log.Logger;
@ -46,11 +48,10 @@ public class HttpGenerator
public final static boolean __STRICT=Boolean.getBoolean("org.eclipse.jetty.http.HttpGenerator.STRICT");
private final static byte[] __colon_space = new byte[] {':',' '};
private final static HttpHeaderValue[] CLOSE = {HttpHeaderValue.CLOSE};
public static final MetaData.Response CONTINUE_100_INFO = new MetaData.Response(HttpVersion.HTTP_1_1,100,null,null,-1);
public static final MetaData.Response PROGRESS_102_INFO = new MetaData.Response(HttpVersion.HTTP_1_1,102,null,null,-1);
public final static MetaData.Response RESPONSE_500_INFO =
new MetaData.Response(HttpVersion.HTTP_1_1,HttpStatus.INTERNAL_SERVER_ERROR_500,null,new HttpFields(){{put(HttpHeader.CONNECTION,HttpHeaderValue.CLOSE);}},0);
new MetaData.Response(HttpVersion.HTTP_1_1,INTERNAL_SERVER_ERROR_500,null,new HttpFields(){{put(HttpHeader.CONNECTION,HttpHeaderValue.CLOSE);}},0);
// states
public enum State { START, COMMITTED, COMPLETING, COMPLETING_1XX, END }
@ -63,13 +64,18 @@ public class HttpGenerator
private EndOfContent _endOfContent = EndOfContent.UNKNOWN_CONTENT;
private long _contentPrepared = 0;
private boolean _noContent = false;
private boolean _noContentResponse = false;
private Boolean _persistent = null;
private final int _send;
private final static int SEND_SERVER = 0x01;
private final static int SEND_XPOWEREDBY = 0x02;
private final static Set<String> __assumedContentMethods = new HashSet<>(Arrays.asList(new String[]{HttpMethod.POST.asString(),HttpMethod.PUT.asString()}));
private final static Trie<Boolean> __assumedContentMethods = new ArrayTrie<>(8);
static
{
__assumedContentMethods.put(HttpMethod.POST.asString(),Boolean.TRUE);
__assumedContentMethods.put(HttpMethod.PUT.asString(),Boolean.TRUE);
}
/* ------------------------------------------------------------------------------- */
public static void setJettyVersion(String serverVersion)
@ -101,7 +107,7 @@ public class HttpGenerator
{
_state = State.START;
_endOfContent = EndOfContent.UNKNOWN_CONTENT;
_noContent=false;
_noContentResponse=false;
_persistent = null;
_contentPrepared = 0;
_needCRLF = false;
@ -160,7 +166,7 @@ public class HttpGenerator
/* ------------------------------------------------------------ */
public boolean isNoContent()
{
return _noContent;
return _noContentResponse;
}
/* ------------------------------------------------------------ */
@ -227,7 +233,7 @@ public class HttpGenerator
generateRequestLine(info,header);
if (info.getHttpVersion()==HttpVersion.HTTP_0_9)
throw new BadMessageException(500,"HTTP/0.9 not supported");
throw new BadMessageException(INTERNAL_SERVER_ERROR_500,"HTTP/0.9 not supported");
generateHeaders(info,header,content,last);
@ -252,10 +258,17 @@ public class HttpGenerator
return Result.FLUSH;
}
catch(BadMessageException e)
{
throw e;
}
catch(BufferOverflowException e)
{
throw new BadMessageException(INTERNAL_SERVER_ERROR_500,"Request header too large",e);
}
catch(Exception e)
{
String message= (e instanceof BufferOverflowException)?"Request header too large":e.getMessage();
throw new BadMessageException(500,message,e);
throw new BadMessageException(INTERNAL_SERVER_ERROR_500,e.getMessage(),e);
}
finally
{
@ -344,7 +357,7 @@ public class HttpGenerator
return Result.NEED_INFO;
HttpVersion version=info.getHttpVersion();
if (version==null)
throw new BadMessageException(500,"No version");
throw new BadMessageException(INTERNAL_SERVER_ERROR_500,"No version");
switch(version)
{
case HTTP_1_0:
@ -381,7 +394,7 @@ public class HttpGenerator
int status=info.getStatus();
if (status>=100 && status<200 )
{
_noContent=true;
_noContentResponse=true;
if (status!=HttpStatus.SWITCHING_PROTOCOLS_101 )
{
@ -392,7 +405,7 @@ public class HttpGenerator
}
else if (status==HttpStatus.NO_CONTENT_204 || status==HttpStatus.NOT_MODIFIED_304)
{
_noContent=true;
_noContentResponse=true;
}
generateHeaders(info,header,content,last);
@ -407,10 +420,17 @@ public class HttpGenerator
}
_state = last?State.COMPLETING:State.COMMITTED;
}
catch(BadMessageException e)
{
throw e;
}
catch(BufferOverflowException e)
{
throw new BadMessageException(INTERNAL_SERVER_ERROR_500,"Request header too large",e);
}
catch(Exception e)
{
String message= (e instanceof BufferOverflowException)?"Response header too large":e.getMessage();
throw new BadMessageException(500,message,e);
throw new BadMessageException(INTERNAL_SERVER_ERROR_500,e.getMessage(),e);
}
finally
{
@ -578,22 +598,29 @@ public class HttpGenerator
}
/* ------------------------------------------------------------ */
private void generateHeaders(MetaData _info,ByteBuffer header,ByteBuffer content,boolean last)
private void generateHeaders(MetaData info,ByteBuffer header,ByteBuffer content,boolean last)
{
final MetaData.Request request=(_info instanceof MetaData.Request)?(MetaData.Request)_info:null;
final MetaData.Response response=(_info instanceof MetaData.Response)?(MetaData.Response)_info:null;
final MetaData.Request request=(info instanceof MetaData.Request)?(MetaData.Request)info:null;
final MetaData.Response response=(info instanceof MetaData.Response)?(MetaData.Response)info:null;
if (LOG.isDebugEnabled())
{
LOG.debug("generateHeaders {} last={} content={}",info,last,BufferUtil.toDetailString(content));
LOG.debug(info.getFields().toString());
}
// default field values
int send=_send;
HttpField transfer_encoding=null;
boolean keep_alive=false;
boolean http11 = info.getHttpVersion() == HttpVersion.HTTP_1_1;
boolean close = false;
boolean chunked = false;
boolean content_type = false;
StringBuilder connection = null;
long content_length = _info.getContentLength();
long content_length = info.getContentLength();
boolean content_length_field = false;
// Generate fields
HttpFields fields = _info.getFields();
HttpFields fields = info.getFields();
if (fields != null)
{
int n=fields.size();
@ -612,10 +639,11 @@ public class HttpGenerator
switch (h)
{
case CONTENT_LENGTH:
_endOfContent=EndOfContent.CONTENT_LENGTH;
if (content_length<0)
content_length=Long.valueOf(field.getValue());
// handle setting the field specially below
content_length = field.getLongValue();
else if (content_length!=field.getLongValue())
throw new BadMessageException(INTERNAL_SERVER_ERROR_500,String.format("Incorrect Content-Length %d!=%d",content_length,field.getLongValue()));
content_length_field = true;
break;
case CONTENT_TYPE:
@ -628,84 +656,32 @@ public class HttpGenerator
case TRANSFER_ENCODING:
{
if (_info.getHttpVersion() == HttpVersion.HTTP_1_1)
if (http11)
{
// Don't add yet, treat this only as a hint that there is content
// with a preference to chunk if we can
transfer_encoding = field;
// Do NOT add yet!
chunked = field.contains(HttpHeaderValue.CHUNKED.asString());
}
break;
}
case CONNECTION:
{
if (request!=null)
putTo(field,header);
// Lookup and/or split connection value field
HttpHeaderValue[] values = HttpHeaderValue.CLOSE.is(field.getValue())?CLOSE:new HttpHeaderValue[]{HttpHeaderValue.CACHE.get(field.getValue())};
String[] split = null;
if (values[0]==null)
{
split = StringUtil.csvSplit(field.getValue());
if (split.length>0)
{
values=new HttpHeaderValue[split.length];
for (int i=0;i<split.length;i++)
values[i]=HttpHeaderValue.CACHE.get(split[i]);
}
}
// Handle connection values
for (int i=0;i<values.length;i++)
{
HttpHeaderValue value=values[i];
switch (value==null?HttpHeaderValue.UNKNOWN:value)
{
case UPGRADE:
{
// special case for websocket connection ordering
header.put(HttpHeader.CONNECTION.getBytesColonSpace()).put(HttpHeader.UPGRADE.getBytes());
header.put(CRLF);
break;
}
case CLOSE:
if (field.contains(HttpHeaderValue.CLOSE.asString()))
{
close=true;
_persistent=false;
if (response!=null)
{
if (_endOfContent == EndOfContent.UNKNOWN_CONTENT)
_endOfContent=EndOfContent.EOF_CONTENT;
}
break;
}
case KEEP_ALIVE:
if (!http11 && field.contains(HttpHeaderValue.KEEP_ALIVE.asString()))
{
if (_info.getHttpVersion() == HttpVersion.HTTP_1_0)
{
keep_alive = true;
if (response!=null)
_persistent=true;
}
break;
}
default:
{
if (connection==null)
connection=new StringBuilder();
else
connection.append(',');
connection.append(split==null?field.getValue():split[i]);
}
}
}
// Do NOT add yet!
break;
}
case SERVER:
{
send=send&~SEND_SERVER;
@ -720,143 +696,91 @@ public class HttpGenerator
}
}
// Can we work out the content length?
if (last && content_length<0)
content_length = _contentPrepared+BufferUtil.length(content);
// Calculate how to end _content and connection, _content length and transfer encoding
// settings.
// From http://tools.ietf.org/html/rfc7230#section-3.3.3
// From RFC 2616 4.4:
// 1. No body for 1xx, 204, 304 & HEAD response
// 3. If Transfer-Encoding==(.*,)?chunked && HTTP/1.1 && !HttpConnection==close then chunk
// 5. Content-Length without Transfer-Encoding
// 6. Request and none over the above, then Content-Length=0 if POST/PUT
// 7. close
// settings from http://tools.ietf.org/html/rfc7230#section-3.3.3
boolean assumed_content_request = request!=null && Boolean.TRUE.equals(__assumedContentMethods.get(request.getMethod()));
boolean assumed_content = assumed_content_request || content_type || chunked;
boolean nocontent_request = request!=null && content_length<=0 && !assumed_content;
int status=response!=null?response.getStatus():-1;
switch (_endOfContent)
// If the message is known not to have content
if (_noContentResponse || nocontent_request)
{
case UNKNOWN_CONTENT:
// It may be that we have no _content, or perhaps _content just has not been
// written yet?
// Response known not to have a body
if (_contentPrepared == 0 && response!=null && _noContent)
// We don't need to indicate a body length
_endOfContent=EndOfContent.NO_CONTENT;
else if (_info.getContentLength()>0)
{
// we have been given a content length
_endOfContent=EndOfContent.CONTENT_LENGTH;
if ((response!=null || content_length>0 || content_type ) && !_noContent)
{
// known length but not actually set.
header.put(HttpHeader.CONTENT_LENGTH.getBytesColonSpace());
BufferUtil.putDecLong(header, content_length);
header.put(HttpTokens.CRLF);
}
}
else if (last)
{
// we have seen all the _content there is, so we can be content-length limited.
_endOfContent=EndOfContent.CONTENT_LENGTH;
long actual_length = _contentPrepared+BufferUtil.length(content);
if (content_length>=0 && content_length!=actual_length)
throw new BadMessageException(500,"Content-Length header("+content_length+") != actual("+actual_length+")");
// Do we need to tell the headers about it
putContentLength(header,actual_length,content_type,request,response);
// But it is an error if there actually is content
if (_contentPrepared>0 || content_length>0)
{
if (_contentPrepared==0 && last)
{
// TODO discard content for backward compatibility with 9.3 releases
// TODO review if it is still needed in 9.4 or can we just throw.
content.clear();
content_length=0;
}
else
throw new BadMessageException(INTERNAL_SERVER_ERROR_500,"Content for no content response");
}
}
// Else if we are HTTP/1.1 and the content length is unknown and we are either persistent
// or it is a request with content (which cannot EOF)
else if (http11 && content_length<0 && (_persistent || assumed_content_request))
{
// No idea, so we must assume that a body is coming.
// we use chunking
_endOfContent = EndOfContent.CHUNKED_CONTENT;
// HTTP 1.0 does not understand chunked content, so we must use EOF content.
// For a request with HTTP 1.0 & Connection: keep-alive
// we *must* close the connection, otherwise the client
// has no way to detect the end of the content.
if (!isPersistent() || _info.getHttpVersion().ordinal() < HttpVersion.HTTP_1_1.ordinal())
_endOfContent = EndOfContent.EOF_CONTENT;
}
break;
chunked = true;
case CONTENT_LENGTH:
{
putContentLength(header,content_length,content_type,request,response);
break;
}
case NO_CONTENT:
throw new BadMessageException(500);
case EOF_CONTENT:
_persistent = request!=null;
break;
case CHUNKED_CONTENT:
break;
default:
break;
}
// Add transfer_encoding if needed
if (isChunking())
{
// try to use user supplied encoding as it may have other values.
if (transfer_encoding != null && !HttpHeaderValue.CHUNKED.toString().equalsIgnoreCase(transfer_encoding.getValue()))
{
String c = transfer_encoding.getValue();
if (c.endsWith(HttpHeaderValue.CHUNKED.toString()))
putTo(transfer_encoding,header);
else
throw new BadMessageException(500,"BAD TE");
}
else
if (transfer_encoding == null)
header.put(TRANSFER_ENCODING_CHUNKED);
}
// Handle connection if need be
if (_endOfContent==EndOfContent.EOF_CONTENT)
else if (transfer_encoding.toString().endsWith(HttpHeaderValue.CHUNKED.toString()))
{
keep_alive=false;
putTo(transfer_encoding,header);
transfer_encoding = null;
}
else
throw new BadMessageException(INTERNAL_SERVER_ERROR_500,"BAD TE");
}
// Else if we known the content length and are a request or a persistent response,
else if (content_length>=0 && (request!=null || _persistent))
{
// Use the content length
_endOfContent = EndOfContent.CONTENT_LENGTH;
putContentLength(header,content_length);
}
// Else if we are a response
else if (response!=null)
{
// We can use EOF
_endOfContent = EndOfContent.EOF_CONTENT;
_persistent=false;
}
if (content_length>=0 && ( content_length> 0 || assumed_content || content_length_field ))
putContentLength(header,content_length);
// If this is a response, work out persistence
if (response!=null)
{
if (!isPersistent() && (close || _info.getHttpVersion().ordinal() > HttpVersion.HTTP_1_0.ordinal()))
{
if (connection==null)
if (http11 && !close)
header.put(CONNECTION_CLOSE);
}
// Else we must be a request
else
{
header.put(CONNECTION_CLOSE,0,CONNECTION_CLOSE.length-2);
header.put((byte)',');
header.put(StringUtil.getBytes(connection.toString()));
header.put(CRLF);
}
}
else if (keep_alive)
{
if (connection==null)
header.put(CONNECTION_KEEP_ALIVE);
else
{
header.put(CONNECTION_KEEP_ALIVE,0,CONNECTION_KEEP_ALIVE.length-2);
header.put((byte)',');
header.put(StringUtil.getBytes(connection.toString()));
header.put(CRLF);
}
}
else if (connection!=null)
{
header.put(HttpHeader.CONNECTION.getBytesColonSpace());
header.put(StringUtil.getBytes(connection.toString()));
header.put(CRLF);
}
// with no way to indicate body length
throw new BadMessageException(INTERNAL_SERVER_ERROR_500,"Unknown content length for request");
}
if (LOG.isDebugEnabled())
LOG.debug(_endOfContent.toString());
// Add transfer encoding if it is not chunking
if (transfer_encoding!=null && !chunked)
putTo(transfer_encoding,header);
// Send server?
int status=response!=null?response.getStatus():-1;
if (status>199)
header.put(SEND[send]);
@ -865,19 +789,16 @@ public class HttpGenerator
}
/* ------------------------------------------------------------------------------- */
private void putContentLength(ByteBuffer header, long contentLength, boolean contentType, MetaData.Request request, MetaData.Response response)
private static void putContentLength(ByteBuffer header,long contentLength)
{
if (contentLength>0)
if (contentLength==0)
header.put(CONTENT_LENGTH_0);
else
{
header.put(HttpHeader.CONTENT_LENGTH.getBytesColonSpace());
BufferUtil.putDecLong(header, contentLength);
header.put(HttpTokens.CRLF);
}
else if (!_noContent)
{
if (contentType || response!=null || (request!=null && __assumedContentMethods.contains(request.getMethod())))
header.put(CONTENT_LENGTH_0);
}
}
/* ------------------------------------------------------------------------------- */
@ -905,10 +826,8 @@ public class HttpGenerator
// common _content
private static final byte[] LAST_CHUNK = { (byte) '0', (byte) '\015', (byte) '\012', (byte) '\015', (byte) '\012'};
private static final byte[] CONTENT_LENGTH_0 = StringUtil.getBytes("Content-Length: 0\015\012");
private static final byte[] CONNECTION_KEEP_ALIVE = StringUtil.getBytes("Connection: keep-alive\015\012");
private static final byte[] CONNECTION_CLOSE = StringUtil.getBytes("Connection: close\015\012");
private static final byte[] HTTP_1_1_SPACE = StringUtil.getBytes(HttpVersion.HTTP_1_1+" ");
private static final byte[] CRLF = StringUtil.getBytes("\015\012");
private static final byte[] TRANSFER_ENCODING_CHUNKED = StringUtil.getBytes("Transfer-Encoding: chunked\015\012");
private static final byte[][] SEND = new byte[][]{
new byte[0],

View File

@ -225,8 +225,8 @@ public class MetaData implements Iterable<HttpField>
public String toString()
{
HttpFields fields = getFields();
return String.format("%s{u=%s,%s,h=%d}",
getMethod(), getURI(), getHttpVersion(), fields == null ? -1 : fields.size());
return String.format("%s{u=%s,%s,h=%d,cl=%d}",
getMethod(), getURI(), getHttpVersion(), fields == null ? -1 : fields.size(), getContentLength());
}
}
@ -300,7 +300,7 @@ public class MetaData implements Iterable<HttpField>
public String toString()
{
HttpFields fields = getFields();
return String.format("%s{s=%d,h=%d}", getHttpVersion(), getStatus(), fields == null ? -1 : fields.size());
return String.format("%s{s=%d,h=%d,cl=%d}", getHttpVersion(), getStatus(), fields == null ? -1 : fields.size(), getContentLength());
}
}
}

View File

@ -19,6 +19,7 @@
package org.eclipse.jetty.http;
import static org.hamcrest.Matchers.containsString;
import static org.hamcrest.Matchers.endsWith;
import static org.hamcrest.Matchers.not;
import static org.hamcrest.Matchers.startsWith;
import static org.junit.Assert.assertEquals;
@ -27,6 +28,7 @@ import static org.junit.Assert.assertThat;
import java.nio.ByteBuffer;
import org.eclipse.jetty.util.BufferUtil;
import org.hamcrest.Matchers;
import org.junit.Assert;
import org.junit.Test;
@ -229,7 +231,7 @@ public class HttpGeneratorServerTest
}
@Test
public void testResponseNoContent() throws Exception
public void testResponseIncorrectContentLength() throws Exception
{
ByteBuffer header = BufferUtil.allocate(8096);
@ -239,7 +241,36 @@ public class HttpGeneratorServerTest
assertEquals(HttpGenerator.Result.NEED_INFO, result);
assertEquals(HttpGenerator.State.START, gen.getState());
MetaData.Response info = new MetaData.Response(HttpVersion.HTTP_1_1, 200, null, new HttpFields(), -1);
MetaData.Response info = new MetaData.Response(HttpVersion.HTTP_1_1, 200, null, new HttpFields(), 10);
info.getFields().add("Last-Modified", DateGenerator.__01Jan1970);
info.getFields().add("Content-Length", "11");
result = gen.generateResponse(info, null, null, null, true);
assertEquals(HttpGenerator.Result.NEED_HEADER, result);
try
{
gen.generateResponse(info, header, null, null, true);
Assert.fail();
}
catch(BadMessageException e)
{
assertEquals(e._code,500);
}
}
@Test
public void testResponseNoContentPersistent() throws Exception
{
ByteBuffer header = BufferUtil.allocate(8096);
HttpGenerator gen = new HttpGenerator();
HttpGenerator.Result result = gen.generateResponse(null, null, null, null, true);
assertEquals(HttpGenerator.Result.NEED_INFO, result);
assertEquals(HttpGenerator.State.START, gen.getState());
MetaData.Response info = new MetaData.Response(HttpVersion.HTTP_1_1, 200, null, new HttpFields(), 0);
info.getFields().add("Last-Modified", DateGenerator.__01Jan1970);
result = gen.generateResponse(info, null, null, null, true);
@ -261,6 +292,40 @@ public class HttpGeneratorServerTest
assertThat(head, containsString("Content-Length: 0"));
}
@Test
public void testResponseKnownNoContentNotPersistent() throws Exception
{
ByteBuffer header = BufferUtil.allocate(8096);
HttpGenerator gen = new HttpGenerator();
HttpGenerator.Result result = gen.generateResponse(null, null, null, null, true);
assertEquals(HttpGenerator.Result.NEED_INFO, result);
assertEquals(HttpGenerator.State.START, gen.getState());
MetaData.Response info = new MetaData.Response(HttpVersion.HTTP_1_1, 200, null, new HttpFields(), 0);
info.getFields().add("Last-Modified", DateGenerator.__01Jan1970);
info.getFields().add("Connection", "close");
result = gen.generateResponse(info, null, null, null, true);
assertEquals(HttpGenerator.Result.NEED_HEADER, result);
result = gen.generateResponse(info, header, null, null, true);
assertEquals(HttpGenerator.Result.FLUSH, result);
assertEquals(HttpGenerator.State.COMPLETING, gen.getState());
String head = BufferUtil.toString(header);
BufferUtil.clear(header);
result = gen.generateResponse(null, null, null, null, false);
assertEquals(HttpGenerator.Result.SHUTDOWN_OUT, result);
assertEquals(HttpGenerator.State.END, gen.getState());
assertEquals(0, gen.getContentPrepared());
assertThat(head, containsString("HTTP/1.1 200 OK"));
assertThat(head, containsString("Last-Modified: Thu, 01 Jan 1970 00:00:00 GMT"));
assertThat(head, containsString("Connection: close"));
}
@Test
public void testResponseUpgrade() throws Exception
{
@ -344,19 +409,23 @@ public class HttpGeneratorServerTest
assertEquals(HttpGenerator.Result.DONE, result);
assertEquals(HttpGenerator.State.END, gen.getState());
assertThat(out, containsString("HTTP/1.1 200 OK"));
assertThat(out, containsString("Last-Modified: Thu, 01 Jan 1970 00:00:00 GMT"));
assertThat(out, not(containsString("Content-Length")));
assertThat(out, containsString("Transfer-Encoding: chunked"));
assertThat(out, containsString("\r\n\r\nD\r\n"));
assertThat(out, containsString("\r\nHello World! \r\n"));
assertThat(out, containsString("\r\n2E\r\n"));
assertThat(out, containsString("\r\nThe quick brown fox jumped over the lazy dog. \r\n"));
assertThat(out, containsString("\r\n0\r\n"));
assertThat(out, endsWith(
"\r\n\r\nD\r\n"+
"Hello World! \r\n"+
"2E\r\n"+
"The quick brown fox jumped over the lazy dog. \r\n"+
"0\r\n"+
"\r\n"));
}
@Test
public void testResponseWithKnownContent() throws Exception
public void testResponseWithKnownContentLengthFromMetaData() throws Exception
{
ByteBuffer header = BufferUtil.allocate(4096);
ByteBuffer content0 = BufferUtil.toBuffer("Hello World! ");
@ -403,6 +472,58 @@ public class HttpGeneratorServerTest
assertThat(out, containsString("\r\n\r\nHello World! The quick brown fox jumped over the lazy dog. "));
}
@Test
public void testResponseWithKnownContentLengthFromHeader() throws Exception
{
ByteBuffer header = BufferUtil.allocate(4096);
ByteBuffer content0 = BufferUtil.toBuffer("Hello World! ");
ByteBuffer content1 = BufferUtil.toBuffer("The quick brown fox jumped over the lazy dog. ");
HttpGenerator gen = new HttpGenerator();
HttpGenerator.Result result = gen.generateResponse(null, null, null, content0, false);
assertEquals(HttpGenerator.Result.NEED_INFO, result);
assertEquals(HttpGenerator.State.START, gen.getState());
MetaData.Response info = new MetaData.Response(HttpVersion.HTTP_1_1, 200, null, new HttpFields(), -1);
info.getFields().add("Last-Modified", DateGenerator.__01Jan1970);
info.getFields().add("Content-Length",""+(content0.remaining()+content1.remaining()));
result = gen.generateResponse(info, null, null, content0, false);
assertEquals(HttpGenerator.Result.NEED_HEADER, result);
assertEquals(HttpGenerator.State.START, gen.getState());
result = gen.generateResponse(info, header, null, content0, false);
assertEquals(HttpGenerator.Result.FLUSH, result);
assertEquals(HttpGenerator.State.COMMITTED, gen.getState());
String out = BufferUtil.toString(header);
BufferUtil.clear(header);
out += BufferUtil.toString(content0);
BufferUtil.clear(content0);
result = gen.generateResponse(null, null, null, content1, false);
assertEquals(HttpGenerator.Result.FLUSH, result);
assertEquals(HttpGenerator.State.COMMITTED, gen.getState());
out += BufferUtil.toString(content1);
BufferUtil.clear(content1);
result = gen.generateResponse(null, null, null, null, true);
assertEquals(HttpGenerator.Result.CONTINUE, result);
assertEquals(HttpGenerator.State.COMPLETING, gen.getState());
result = gen.generateResponse(null, null, null, null, true);
assertEquals(HttpGenerator.Result.DONE, result);
assertEquals(HttpGenerator.State.END, gen.getState());
assertThat(out, containsString("HTTP/1.1 200 OK"));
assertThat(out, containsString("Last-Modified: Thu, 01 Jan 1970 00:00:00 GMT"));
assertThat(out, not(containsString("chunked")));
assertThat(out, containsString("Content-Length: 59"));
assertThat(out, containsString("\r\n\r\nHello World! The quick brown fox jumped over the lazy dog. "));
}
@Test
public void test100ThenResponseWithContent() throws Exception
{

View File

@ -215,14 +215,20 @@ public class HttpTester
ByteBuffer buffer = in.getBuffer();
int len=0;
while(len>=0)
while(true)
{
if (BufferUtil.hasContent(buffer))
if (parser.parseNext(buffer))
break;
if (in.fillBuffer()<=0)
int len=in.fillBuffer();
if (len==0)
break;
if (len<=0)
{
parser.atEOF();
parser.parseNext(buffer);
break;
}
}
if (r.isComplete())

View File

@ -1,3 +1,3 @@
org.eclipse.jetty.util.log.class=org.eclipse.jetty.util.log.StdErrLog
org.eclipse.jetty.alpn.LEVEL=DEBUG
org.eclipse.jetty.http2.LEVEL=DEBUG
# org.eclipse.jetty.alpn.LEVEL=DEBUG
# org.eclipse.jetty.http2.LEVEL=DEBUG

View File

@ -257,8 +257,11 @@ public class ProxyServletFailureTest
public void testProxyRequestStallsContentServerIdlesTimeout() throws Exception
{
final byte[] content = new byte[]{'C', '0', 'F', 'F', 'E', 'E'};
int expected = -1;
if (proxyServlet instanceof AsyncProxyServlet)
{
// TODO should this be a 502 also???
expected = 500;
proxyServlet = new AsyncProxyServlet()
{
@Override
@ -281,6 +284,7 @@ public class ProxyServletFailureTest
}
else
{
expected = 502;
proxyServlet = new ProxyServlet()
{
@Override
@ -310,7 +314,7 @@ public class ProxyServletFailureTest
.content(new BytesContentProvider(content))
.send();
Assert.assertEquals(500, response.getStatus());
Assert.assertEquals(expected, response.getStatus());
}
}

View File

@ -255,12 +255,13 @@ public class AsyncRequestReadTest
BufferedReader in = new BufferedReader(new InputStreamReader(socket.getInputStream()));
assertThat(in.readLine(),containsString("HTTP/1.1 200 OK"));
assertThat(in.readLine(),containsString("Content-Length:"));
assertThat(in.readLine(),containsString("Content-Length: 11"));
assertThat(in.readLine(),containsString("Server:"));
in.readLine();
assertThat(in.readLine(),containsString("XXXXXXX"));
assertThat(in.readLine(),containsString("HTTP/1.1 200 OK"));
assertThat(in.readLine(),containsString("Connection: close"));
assertThat(in.readLine(),containsString("Content-Length: 11"));
assertThat(in.readLine(),containsString("Server:"));
in.readLine();
assertThat(in.readLine(),containsString("XXXXXXX"));

View File

@ -266,6 +266,51 @@ public class HttpConnectionTest
checkContains(response,offset,"/R1");
}
@Test
public void testEmptyNotPersistent() throws Exception
{
String response=connector.getResponse("GET /R1?empty=true HTTP/1.0\r\n"+
"Host: localhost\r\n"+
"\r\n");
int offset=0;
offset = checkContains(response,offset,"HTTP/1.1 200");
checkNotContained(response,offset,"Content-Length");
response=connector.getResponse("GET /R1?empty=true HTTP/1.1\r\n"+
"Host: localhost\r\n"+
"Connection: close\r\n"+
"\r\n");
offset=0;
offset = checkContains(response,offset,"HTTP/1.1 200");
checkContains(response,offset,"Connection: close");
checkNotContained(response,offset,"Content-Length");
}
@Test
public void testEmptyPersistent() throws Exception
{
String response=connector.getResponse("GET /R1?empty=true HTTP/1.0\r\n"+
"Host: localhost\r\n"+
"Connection: keep-alive\r\n"+
"\r\n");
int offset=0;
offset = checkContains(response,offset,"HTTP/1.1 200");
checkContains(response,offset,"Content-Length: 0");
checkNotContained(response,offset,"Connection: close");
response=connector.getResponse("GET /R1?empty=true HTTP/1.1\r\n"+
"Host: localhost\r\n"+
"\r\n");
offset=0;
offset = checkContains(response,offset,"HTTP/1.1 200");
checkContains(response,offset,"Content-Length: 0");
checkNotContained(response,offset,"Connection: close");
}
@Test
public void testEmptyChunk() throws Exception
{

View File

@ -132,6 +132,7 @@ public class HttpManyWaysToAsyncCommitTest extends AbstractHttpTest
SimpleHttpResponse response = executeRequest();
assertThat("response code is 200", response.getCode(), is("200"));
if (HttpVersion.HTTP_1_1.asString().equals(httpVersion))
assertHeader(response, "content-length", "0");
assertThat("no exceptions", handler.failure(), is(nullValue()));
}

View File

@ -102,6 +102,8 @@ public class HttpManyWaysToCommitTest extends AbstractHttpTest
SimpleHttpResponse response = executeRequest();
assertThat("response code is 200", response.getCode(), is("200"));
if (HttpVersion.HTTP_1_1.asString().equals(httpVersion))
assertHeader(response, "content-length", "0");
}

View File

@ -31,6 +31,8 @@ import org.eclipse.jetty.http.HttpTester;
import org.eclipse.jetty.http.HttpVersion;
import org.eclipse.jetty.server.handler.AbstractHandler;
import org.eclipse.jetty.toolchain.test.TestTracker;
import org.eclipse.jetty.util.log.Log;
import org.eclipse.jetty.util.log.Logger;
import org.hamcrest.Matchers;
import org.junit.Assert;
import org.junit.Rule;
@ -55,7 +57,10 @@ public class HttpVersionCustomizerTest
public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException
{
baseRequest.setHandled(true);
response.setStatus(500);
Assert.assertEquals(HttpVersion.HTTP_1_1.asString(), request.getProtocol());
response.setStatus(200);
response.getWriter().println("OK");
}
});
server.start();

View File

@ -1096,7 +1096,8 @@ public class RequestTest
200, TimeUnit.MILLISECONDS
);
assertThat(response, Matchers.containsString("200"));
assertThat(response, Matchers.containsString("Connection: TE,Other"));
assertThat(response, Matchers.containsString("Connection: TE"));
assertThat(response, Matchers.containsString("Connection: Other"));
assertThat(response, Matchers.containsString("Hello World"));
response=_connector.getResponse(

View File

@ -85,8 +85,18 @@ public class SSLEngineTest
private static final String REQUEST1=REQUEST1_HEADER+REQUEST_CONTENT.getBytes().length+"\n\n"+REQUEST_CONTENT;
/** The expected response. */
private static final String RESPONSE0="HTTP/1.1 200 OK\n"+"Content-Length: "+HELLO_WORLD.length()+"\n"+"Server: Jetty("+JETTY_VERSION+")\n"+'\n'+HELLO_WORLD;
private static final String RESPONSE1="HTTP/1.1 200 OK\n"+"Connection: close\n"+"Server: Jetty("+JETTY_VERSION+")\n"+'\n'+HELLO_WORLD;
private static final String RESPONSE0="HTTP/1.1 200 OK\n"+
"Content-Length: "+HELLO_WORLD.length()+"\n"+
"Server: Jetty("+JETTY_VERSION+")\n"+
'\n'+
HELLO_WORLD;
private static final String RESPONSE1="HTTP/1.1 200 OK\n"+
"Connection: close\n"+
"Content-Length: "+HELLO_WORLD.length()+"\n"+
"Server: Jetty("+JETTY_VERSION+")\n"+
'\n'+
HELLO_WORLD;
private static final int BODY_SIZE=300;

View File

@ -34,7 +34,6 @@ import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpServletResponseWrapper;
import org.eclipse.jetty.http.HttpParser;
import org.eclipse.jetty.server.Handler;
import org.eclipse.jetty.server.HttpChannel;
import org.eclipse.jetty.server.HttpConnectionFactory;
@ -169,9 +168,7 @@ public class AsyncContextTest
BufferedReader br = new BufferedReader(new StringReader(responseString));
assertEquals("HTTP/1.1 500 Server Error", br.readLine());
br.readLine();// connection close
br.readLine();// server
br.readLine();// empty
readHeader(br);
Assert.assertEquals("ERROR: /error", br.readLine());
Assert.assertEquals("PathInfo= /IOE", br.readLine());
Assert.assertEquals("EXCEPTION: org.eclipse.jetty.server.QuietServletException: java.io.IOException: Test", br.readLine());
@ -192,9 +189,7 @@ public class AsyncContextTest
BufferedReader br = new BufferedReader(new StringReader(responseString));
assertEquals("HTTP/1.1 200 OK",br.readLine());
br.readLine();// connection close
br.readLine();// server
br.readLine();// empty
readHeader(br);
Assert.assertEquals("error servlet","completeBeforeThrow",br.readLine());
}
@ -273,10 +268,15 @@ public class AsyncContextTest
@Test
public void testDispatch() throws Exception
{
String request = "GET /ctx/forward HTTP/1.1\r\n" + "Host: localhost\r\n" + "Content-Type: application/x-www-form-urlencoded\r\n" + "Connection: close\r\n"
+ "\r\n";
String request =
"GET /ctx/forward HTTP/1.1\r\n" +
"Host: localhost\r\n" +
"Content-Type: application/x-www-form-urlencoded\r\n" +
"Connection: close\r\n" +
"\r\n";
String responseString = _connector.getResponses(request);
String responseString = _connector.getResponse(request);
System.err.println(responseString);
BufferedReader br = parseHeader(responseString);
assertThat("!ForwardingServlet", br.readLine(), equalTo("Dispatched back to ForwardingServlet"));
}
@ -300,15 +300,18 @@ public class AsyncContextTest
private BufferedReader parseHeader(String responseString) throws IOException
{
BufferedReader br = new BufferedReader(new StringReader(responseString));
assertEquals("HTTP/1.1 200 OK", br.readLine());
br.readLine();// connection close
br.readLine();// server
br.readLine();// empty
readHeader(br);
return br;
}
private void readHeader(BufferedReader br) throws IOException
{
String line = br.readLine();
while (line!=null && !line.isEmpty())
line = br.readLine();
}
private class ForwardingServlet extends HttpServlet
{
private static final long serialVersionUID = 1L;
@ -371,11 +374,7 @@ public class AsyncContextTest
BufferedReader br = new BufferedReader(new StringReader(responseString));
assertEquals("HTTP/1.1 500 Server Error", br.readLine());
br.readLine();// connection close
br.readLine();// server
br.readLine();// empty
readHeader(br);
Assert.assertEquals("error servlet", "ERROR: /error", br.readLine());
}
@ -392,9 +391,7 @@ public class AsyncContextTest
BufferedReader br = new BufferedReader(new StringReader(responseString));
assertEquals("HTTP/1.1 500 Server Error", br.readLine());
br.readLine();// connection close
br.readLine();// server
br.readLine();// empty
readHeader(br);
Assert.assertEquals("error servlet", "ERROR: /error", br.readLine());
Assert.assertEquals("error servlet", "PathInfo= /500", br.readLine());

View File

@ -115,7 +115,6 @@ public class DefaultServletRangesTest
"\r\n");
assertResponseContains("206 Partial", response);
assertResponseContains("Content-Type: text/plain", response);
assertResponseContains("Content-Length: 10", response);
assertResponseContains("Content-Range: bytes 0-9/80", response);
assertResponseContains(DATA.substring(0,10), response);
}
@ -133,7 +132,6 @@ public class DefaultServletRangesTest
"\r\n");
assertResponseContains("206 Partial", response);
assertResponseContains("Content-Type: text/plain", response);
assertResponseContains("Content-Length: 7", response);
assertResponseContains("Content-Range: bytes 3-9/80", response);
assertResponseContains(DATA.substring(3,10), response);
}
@ -156,7 +154,6 @@ public class DefaultServletRangesTest
assertResponseContains("Content-Range: bytes 0-9/80", response);
assertResponseContains("Content-Range: bytes 20-29/80", response);
assertResponseContains("Content-Range: bytes 40-49/80", response);
assertResponseContains("Content-Length: " + body.length(), response);
assertResponseContains(DATA.substring(0,10), response);
assertResponseContains(DATA.substring(20,30), response);
assertResponseContains(DATA.substring(40,50), response);
@ -177,7 +174,6 @@ public class DefaultServletRangesTest
assertResponseContains("206 Partial", response);
assertResponseNotContains("Content-Type: multipart/byteranges; boundary=", response);
assertResponseContains("Content-Range: bytes 20-79/80", response);
assertResponseContains("Content-Length: 60", response);
assertResponseContains(DATA.substring(60), response);
}
@ -194,7 +190,6 @@ public class DefaultServletRangesTest
assertResponseContains("206 Partial", response);
assertResponseNotContains("Content-Type: multipart/byteranges; boundary=", response);
assertResponseContains("Content-Range: bytes 60-79/80", response); // yes the spec says it is these bytes
assertResponseContains("Content-Length: 20", response);
assertResponseContains(DATA.substring(60), response);
}

View File

@ -591,7 +591,6 @@ public class DefaultServletTest
response = connector.getResponse("GET /context/data.txt HTTP/1.1\r\n" +
"Host: localhost\r\n" +
"Connection: close\r\n"+
"Range: bytes=0-9\r\n" +
"\r\n");
assertResponseContains("206 Partial", response);
@ -599,10 +598,17 @@ public class DefaultServletTest
assertResponseContains("Content-Length: 10", response);
assertResponseContains("Content-Range: bytes 0-9/80", response);
response = connector.getResponse("GET /context/data.txt HTTP/1.1\r\n" +
"Host: localhost\r\n" +
"Range: bytes=0-9\r\n" +
"Connection: close\r\n" +
"\r\n");
assertResponseContains("206 Partial", response);
assertResponseContains("Content-Type: text/plain", response);
assertResponseContains("Content-Range: bytes 0-9/80", response);
response = connector.getResponse("GET /context/data.txt HTTP/1.1\r\n" +
"Host: localhost\r\n" +
"Connection: close\r\n"+
"Range: bytes=0-9,20-29,40-49\r\n" +
"\r\n");
int start = response.indexOf("--jetty");
@ -617,7 +623,6 @@ public class DefaultServletTest
response = connector.getResponse("GET /context/data.txt HTTP/1.1\r\n" +
"Host: localhost\r\n" +
"Connection: close\r\n"+
"Range: bytes=0-9,20-29,40-49,70-79\r\n" +
"\r\n");
start = response.indexOf("--jetty");
@ -633,7 +638,6 @@ public class DefaultServletTest
response = connector.getResponse("GET /context/data.txt HTTP/1.1\r\n" +
"Host: localhost\r\n" +
"Connection: close\r\n"+
"Range: bytes=0-9,20-29,40-49,60-60,70-79\r\n" +
"\r\n");
start = response.indexOf("--jetty");
@ -655,7 +659,6 @@ public class DefaultServletTest
response = connector.getResponse("GET /context/nofilesuffix HTTP/1.1\r\n" +
"Host: localhost\r\n" +
"Connection: close\r\n"+
"\r\n");
assertResponseContains("200 OK", response);
assertResponseContains("Accept-Ranges: bytes", response);
@ -664,7 +667,6 @@ public class DefaultServletTest
response = connector.getResponse("GET /context/nofilesuffix HTTP/1.1\r\n" +
"Host: localhost\r\n" +
"Connection: close\r\n"+
"Range: bytes=0-9\r\n" +
"\r\n");
assertResponseContains("206 Partial", response);
@ -674,7 +676,6 @@ public class DefaultServletTest
response = connector.getResponse("GET /context/nofilesuffix HTTP/1.1\r\n" +
"Host: localhost\r\n" +
"Connection: close\r\n"+
"Range: bytes=0-9,20-29,40-49\r\n" +
"\r\n");
start = response.indexOf("--jetty");
@ -687,11 +688,8 @@ public class DefaultServletTest
assertResponseContains("Content-Length: " + body.length(), response);
assertTrue(body.endsWith(boundary + "--\r\n"));
response = connector.getResponse("GET /context/nofilesuffix HTTP/1.1\r\n" +
"Host: localhost\r\n" +
"Connection: close\r\n"+
"Range: bytes=0-9,20-29,40-49,60-60,70-79\r\n" +
"\r\n");
start = response.indexOf("--jetty");

View File

@ -165,7 +165,6 @@ public class DispatcherTest
String expected=
"HTTP/1.1 200 OK\r\n"+
"Content-Length: 0\r\n"+
"\r\n";
String responses = _connector.getResponses("GET /context/IncludeServlet?do=assertinclude&do=more&test=1 HTTP/1.0\n\n");
@ -182,7 +181,6 @@ public class DispatcherTest
String expected=
"HTTP/1.1 200 OK\r\n"+
"Content-Length: 0\r\n"+
"\r\n";
String responses = _connector.getResponses("GET /context/ForwardServlet/forwardpath?do=include HTTP/1.0\n\n");

View File

@ -1040,9 +1040,6 @@ public class MultipartFilterTest
public void testWithCharSet()
throws Exception
{
((StdErrLog)Log.getLogger(MultiPartFilter.class)).setDebugEnabled(true);
((StdErrLog)Log.getLogger(MultiPartInputStreamParser.class)).setDebugEnabled(true);
// generated and parsed test
HttpTester.Request request = HttpTester.newRequest();
HttpTester.Response response;