Multiple optimizations in the HttpObjectDecoder
* Minimize allocation of StringBuilder and also minimize char array copy * Try to detect HttpVersion without calling toUpperCase() for performance reasons
This commit is contained in:
parent
e09aea5902
commit
acb28e3ac8
@ -98,10 +98,25 @@ import java.util.List;
|
|||||||
*/
|
*/
|
||||||
public abstract class HttpObjectDecoder extends ReplayingDecoder<HttpObjectDecoder.State> {
|
public abstract class HttpObjectDecoder extends ReplayingDecoder<HttpObjectDecoder.State> {
|
||||||
|
|
||||||
|
private static final ThreadLocal<StringBuilder> BUILDERS = new ThreadLocal<StringBuilder>() {
|
||||||
|
@Override
|
||||||
|
protected StringBuilder initialValue() {
|
||||||
|
return new StringBuilder(512);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public StringBuilder get() {
|
||||||
|
StringBuilder builder = super.get();
|
||||||
|
builder.setLength(0);
|
||||||
|
return builder;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
private final int maxInitialLineLength;
|
private final int maxInitialLineLength;
|
||||||
private final int maxHeaderSize;
|
private final int maxHeaderSize;
|
||||||
private final int maxChunkSize;
|
private final int maxChunkSize;
|
||||||
private final boolean chunkedSupported;
|
private final boolean chunkedSupported;
|
||||||
|
|
||||||
private ByteBuf content;
|
private ByteBuf content;
|
||||||
private HttpMessage message;
|
private HttpMessage message;
|
||||||
private long chunkSize;
|
private long chunkSize;
|
||||||
@ -315,8 +330,8 @@ public abstract class HttpObjectDecoder extends ReplayingDecoder<HttpObjectDecod
|
|||||||
* read chunk, read and ignore the CRLF and repeat until 0
|
* read chunk, read and ignore the CRLF and repeat until 0
|
||||||
*/
|
*/
|
||||||
case READ_CHUNK_SIZE: try {
|
case READ_CHUNK_SIZE: try {
|
||||||
String line = readLine(buffer, maxInitialLineLength);
|
StringBuilder line = readLine(buffer, maxInitialLineLength);
|
||||||
int chunkSize = getChunkSize(line);
|
int chunkSize = getChunkSize(line.toString());
|
||||||
this.chunkSize = chunkSize;
|
this.chunkSize = chunkSize;
|
||||||
if (chunkSize == 0) {
|
if (chunkSize == 0) {
|
||||||
checkpoint(State.READ_CHUNK_FOOTER);
|
checkpoint(State.READ_CHUNK_FOOTER);
|
||||||
@ -565,15 +580,15 @@ public abstract class HttpObjectDecoder extends ReplayingDecoder<HttpObjectDecod
|
|||||||
final HttpMessage message = this.message;
|
final HttpMessage message = this.message;
|
||||||
final HttpHeaders headers = message.headers();
|
final HttpHeaders headers = message.headers();
|
||||||
|
|
||||||
String line = readHeader(buffer);
|
StringBuilder line = readHeader(buffer);
|
||||||
String name = null;
|
String name = null;
|
||||||
String value = null;
|
String value = null;
|
||||||
if (!line.isEmpty()) {
|
if (line.length() > 0) {
|
||||||
headers.clear();
|
headers.clear();
|
||||||
do {
|
do {
|
||||||
char firstChar = line.charAt(0);
|
char firstChar = line.charAt(0);
|
||||||
if (name != null && (firstChar == ' ' || firstChar == '\t')) {
|
if (name != null && (firstChar == ' ' || firstChar == '\t')) {
|
||||||
value = value + ' ' + line.trim();
|
value = value + ' ' + line.toString().trim();
|
||||||
} else {
|
} else {
|
||||||
if (name != null) {
|
if (name != null) {
|
||||||
headers.add(name, value);
|
headers.add(name, value);
|
||||||
@ -584,7 +599,7 @@ public abstract class HttpObjectDecoder extends ReplayingDecoder<HttpObjectDecod
|
|||||||
}
|
}
|
||||||
|
|
||||||
line = readHeader(buffer);
|
line = readHeader(buffer);
|
||||||
} while (!line.isEmpty());
|
} while (line.length() > 0);
|
||||||
|
|
||||||
// Add the last header.
|
// Add the last header.
|
||||||
if (name != null) {
|
if (name != null) {
|
||||||
@ -609,9 +624,9 @@ public abstract class HttpObjectDecoder extends ReplayingDecoder<HttpObjectDecod
|
|||||||
|
|
||||||
private LastHttpContent readTrailingHeaders(ByteBuf buffer) {
|
private LastHttpContent readTrailingHeaders(ByteBuf buffer) {
|
||||||
headerSize = 0;
|
headerSize = 0;
|
||||||
String line = readHeader(buffer);
|
StringBuilder line = readHeader(buffer);
|
||||||
String lastHeader = null;
|
String lastHeader = null;
|
||||||
if (!line.isEmpty()) {
|
if (line.length() > 0) {
|
||||||
LastHttpContent trailer = new DefaultLastHttpContent(Unpooled.EMPTY_BUFFER);
|
LastHttpContent trailer = new DefaultLastHttpContent(Unpooled.EMPTY_BUFFER);
|
||||||
do {
|
do {
|
||||||
char firstChar = line.charAt(0);
|
char firstChar = line.charAt(0);
|
||||||
@ -619,7 +634,7 @@ public abstract class HttpObjectDecoder extends ReplayingDecoder<HttpObjectDecod
|
|||||||
List<String> current = trailer.trailingHeaders().getAll(lastHeader);
|
List<String> current = trailer.trailingHeaders().getAll(lastHeader);
|
||||||
if (!current.isEmpty()) {
|
if (!current.isEmpty()) {
|
||||||
int lastPos = current.size() - 1;
|
int lastPos = current.size() - 1;
|
||||||
String newString = current.get(lastPos) + line.trim();
|
String newString = current.get(lastPos) + line.toString().trim();
|
||||||
current.set(lastPos, newString);
|
current.set(lastPos, newString);
|
||||||
} else {
|
} else {
|
||||||
// Content-Length, Transfer-Encoding, or Trailer
|
// Content-Length, Transfer-Encoding, or Trailer
|
||||||
@ -636,7 +651,7 @@ public abstract class HttpObjectDecoder extends ReplayingDecoder<HttpObjectDecod
|
|||||||
}
|
}
|
||||||
|
|
||||||
line = readHeader(buffer);
|
line = readHeader(buffer);
|
||||||
} while (!line.isEmpty());
|
} while (line.length() > 0);
|
||||||
|
|
||||||
return trailer;
|
return trailer;
|
||||||
}
|
}
|
||||||
@ -644,8 +659,8 @@ public abstract class HttpObjectDecoder extends ReplayingDecoder<HttpObjectDecod
|
|||||||
return LastHttpContent.EMPTY_LAST_CONTENT;
|
return LastHttpContent.EMPTY_LAST_CONTENT;
|
||||||
}
|
}
|
||||||
|
|
||||||
private String readHeader(ByteBuf buffer) {
|
private StringBuilder readHeader(ByteBuf buffer) {
|
||||||
StringBuilder sb = new StringBuilder(64);
|
StringBuilder sb = BUILDERS.get();
|
||||||
int headerSize = this.headerSize;
|
int headerSize = this.headerSize;
|
||||||
|
|
||||||
loop:
|
loop:
|
||||||
@ -680,7 +695,7 @@ public abstract class HttpObjectDecoder extends ReplayingDecoder<HttpObjectDecod
|
|||||||
}
|
}
|
||||||
|
|
||||||
this.headerSize = headerSize;
|
this.headerSize = headerSize;
|
||||||
return sb.toString();
|
return sb;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected abstract boolean isDecodingRequest();
|
protected abstract boolean isDecodingRequest();
|
||||||
@ -700,18 +715,18 @@ public abstract class HttpObjectDecoder extends ReplayingDecoder<HttpObjectDecod
|
|||||||
return Integer.parseInt(hex, 16);
|
return Integer.parseInt(hex, 16);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static String readLine(ByteBuf buffer, int maxLineLength) {
|
private static StringBuilder readLine(ByteBuf buffer, int maxLineLength) {
|
||||||
StringBuilder sb = new StringBuilder(64);
|
StringBuilder sb = BUILDERS.get();
|
||||||
int lineLength = 0;
|
int lineLength = 0;
|
||||||
while (true) {
|
while (true) {
|
||||||
byte nextByte = buffer.readByte();
|
byte nextByte = buffer.readByte();
|
||||||
if (nextByte == HttpConstants.CR) {
|
if (nextByte == HttpConstants.CR) {
|
||||||
nextByte = buffer.readByte();
|
nextByte = buffer.readByte();
|
||||||
if (nextByte == HttpConstants.LF) {
|
if (nextByte == HttpConstants.LF) {
|
||||||
return sb.toString();
|
return sb;
|
||||||
}
|
}
|
||||||
} else if (nextByte == HttpConstants.LF) {
|
} else if (nextByte == HttpConstants.LF) {
|
||||||
return sb.toString();
|
return sb;
|
||||||
} else {
|
} else {
|
||||||
if (lineLength >= maxLineLength) {
|
if (lineLength >= maxLineLength) {
|
||||||
// TODO: Respond with Bad Request and discard the traffic
|
// TODO: Respond with Bad Request and discard the traffic
|
||||||
@ -728,7 +743,7 @@ public abstract class HttpObjectDecoder extends ReplayingDecoder<HttpObjectDecod
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static String[] splitInitialLine(String sb) {
|
private static String[] splitInitialLine(StringBuilder sb) {
|
||||||
int aStart;
|
int aStart;
|
||||||
int aEnd;
|
int aEnd;
|
||||||
int bStart;
|
int bStart;
|
||||||
@ -751,7 +766,7 @@ public abstract class HttpObjectDecoder extends ReplayingDecoder<HttpObjectDecod
|
|||||||
cStart < cEnd? sb.substring(cStart, cEnd) : "" };
|
cStart < cEnd? sb.substring(cStart, cEnd) : "" };
|
||||||
}
|
}
|
||||||
|
|
||||||
private static String[] splitHeader(String sb) {
|
private static String[] splitHeader(StringBuilder sb) {
|
||||||
final int length = sb.length();
|
final int length = sb.length();
|
||||||
int nameStart;
|
int nameStart;
|
||||||
int nameEnd;
|
int nameEnd;
|
||||||
@ -789,7 +804,7 @@ public abstract class HttpObjectDecoder extends ReplayingDecoder<HttpObjectDecod
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
private static int findNonWhitespace(String sb, int offset) {
|
private static int findNonWhitespace(CharSequence sb, int offset) {
|
||||||
int result;
|
int result;
|
||||||
for (result = offset; result < sb.length(); result ++) {
|
for (result = offset; result < sb.length(); result ++) {
|
||||||
if (!Character.isWhitespace(sb.charAt(result))) {
|
if (!Character.isWhitespace(sb.charAt(result))) {
|
||||||
@ -799,7 +814,7 @@ public abstract class HttpObjectDecoder extends ReplayingDecoder<HttpObjectDecod
|
|||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static int findWhitespace(String sb, int offset) {
|
private static int findWhitespace(CharSequence sb, int offset) {
|
||||||
int result;
|
int result;
|
||||||
for (result = offset; result < sb.length(); result ++) {
|
for (result = offset; result < sb.length(); result ++) {
|
||||||
if (Character.isWhitespace(sb.charAt(result))) {
|
if (Character.isWhitespace(sb.charAt(result))) {
|
||||||
@ -809,7 +824,7 @@ public abstract class HttpObjectDecoder extends ReplayingDecoder<HttpObjectDecod
|
|||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static int findEndOfString(String sb) {
|
private static int findEndOfString(CharSequence sb) {
|
||||||
int result;
|
int result;
|
||||||
for (result = sb.length(); result > 0; result --) {
|
for (result = sb.length(); result > 0; result --) {
|
||||||
if (!Character.isWhitespace(sb.charAt(result - 1))) {
|
if (!Character.isWhitespace(sb.charAt(result - 1))) {
|
||||||
|
@ -28,6 +28,9 @@ public class HttpVersion implements Comparable<HttpVersion> {
|
|||||||
private static final Pattern VERSION_PATTERN =
|
private static final Pattern VERSION_PATTERN =
|
||||||
Pattern.compile("(\\S+)/(\\d+)\\.(\\d+)");
|
Pattern.compile("(\\S+)/(\\d+)\\.(\\d+)");
|
||||||
|
|
||||||
|
private static final String HTTP_1_0_STRING = "HTTP/1.0";
|
||||||
|
private static final String HTTP_1_1_STRING = "HTTP/1.1";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* HTTP/1.0
|
* HTTP/1.0
|
||||||
*/
|
*/
|
||||||
@ -51,14 +54,39 @@ public class HttpVersion implements Comparable<HttpVersion> {
|
|||||||
throw new NullPointerException("text");
|
throw new NullPointerException("text");
|
||||||
}
|
}
|
||||||
|
|
||||||
text = text.trim().toUpperCase();
|
text = text.trim();
|
||||||
if ("HTTP/1.1".equals(text)) {
|
// Try to match without convert to uppercase first as this is what 99% of all clients
|
||||||
|
// will send anyway. Also there is a change to the RFC to make it clear that it is
|
||||||
|
// expected to be case-sensitive
|
||||||
|
//
|
||||||
|
// See:
|
||||||
|
// * http://trac.tools.ietf.org/wg/httpbis/trac/ticket/1
|
||||||
|
// * http://trac.tools.ietf.org/wg/httpbis/trac/wiki
|
||||||
|
//
|
||||||
|
// TODO: Remove the uppercase conversion in 4.1.0 as the RFC state it must be HTTP (uppercase)
|
||||||
|
// See https://github.com/netty/netty/issues/1682
|
||||||
|
//
|
||||||
|
HttpVersion version = version0(text);
|
||||||
|
if (version == null) {
|
||||||
|
text = text.toUpperCase();
|
||||||
|
// try again after convert to uppercase
|
||||||
|
version = version0(text);
|
||||||
|
if (version == null) {
|
||||||
|
// still no match, construct a new one
|
||||||
|
version = new HttpVersion(text, true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return version;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static HttpVersion version0(String text) {
|
||||||
|
if (HTTP_1_1_STRING.equals(text)) {
|
||||||
return HTTP_1_1;
|
return HTTP_1_1;
|
||||||
}
|
}
|
||||||
if ("HTTP/1.0".equals(text)) {
|
if (HTTP_1_0_STRING.equals(text)) {
|
||||||
return HTTP_1_0;
|
return HTTP_1_0;
|
||||||
}
|
}
|
||||||
return new HttpVersion(text, true);
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
private final String protocolName;
|
private final String protocolName;
|
||||||
|
Loading…
Reference in New Issue
Block a user