Skip to content

Commit

Permalink
Improve HTTP parsing long look-ahead (#11486)
Browse files Browse the repository at this point in the history
Use ByteBuffer.getLong to look for entire request (GET / HTTP/1.1) or response (HTTP/1.1 200 OK) line with 2 long lookups.  Failing that, a single long lookup is sufficient to determine the common methods and/or HttpVersion.

* Cleanup generator also

* Added a fallback int lookup
  • Loading branch information
gregw authored Mar 5, 2024
1 parent 77119e8 commit 80f912a
Show file tree
Hide file tree
Showing 4 changed files with 352 additions and 111 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -195,7 +195,7 @@ public Result generateRequest(MetaData.Request info, ByteBuffer header, ByteBuff
int pos = BufferUtil.flipToFill(header);
try
{
// generate ResponseLine
// generate request line
generateRequestLine(info, header);

if (info.getHttpVersion() == HttpVersion.HTTP_0_9)
Expand Down Expand Up @@ -520,7 +520,7 @@ private void generateResponseLine(MetaData.Response response, ByteBuffer header)
String reason = response.getReason();
if (preprepared != null)
{
if (reason == null)
if (reason == null || preprepared._reason.equals(reason))
header.put(preprepared._responseLine);
else
{
Expand Down Expand Up @@ -779,11 +779,9 @@ private static void putContentLength(ByteBuffer header, long contentLength)
}
}

@Deprecated(forRemoval = true)
public static byte[] getReasonBuffer(int code)
{
PreparedResponse status = code < __preprepared.length ? __preprepared[code] : null;
if (status != null)
return status._reason;
return null;
}

Expand All @@ -807,9 +805,16 @@ public String toString()
// Build cache of response lines for status
private static class PreparedResponse
{
byte[] _reason;
byte[] _schemeCode;
byte[] _responseLine;
final String _reason;
final byte[] _schemeCode;
final byte[] _responseLine;

private PreparedResponse(String reason, byte[] schemeCode, byte[] responseLine)
{
_reason = reason;
_schemeCode = schemeCode;
_responseLine = responseLine;
}
}

private static final PreparedResponse[] __preprepared = new PreparedResponse[HttpStatus.MAX_CODE + 1];
Expand Down Expand Up @@ -838,10 +843,7 @@ private static class PreparedResponse
line[versionLength + 5 + reason.length()] = HttpTokens.CARRIAGE_RETURN;
line[versionLength + 6 + reason.length()] = HttpTokens.LINE_FEED;

__preprepared[i] = new PreparedResponse();
__preprepared[i]._schemeCode = Arrays.copyOfRange(line, 0, versionLength + 5);
__preprepared[i]._reason = Arrays.copyOfRange(line, versionLength + 5, line.length - 2);
__preprepared[i]._responseLine = line;
__preprepared[i] = new PreparedResponse(reason, Arrays.copyOfRange(line, 0, versionLength + 5), line);
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -163,34 +163,34 @@ public String toString()
public static HttpMethod lookAheadGet(ByteBuffer buffer)
{
int len = buffer.remaining();
// Short cut for 3 char methods, mostly for GET optimisation
if (len > 3)
{
switch (buffer.getInt(buffer.position()))
{
case ACL_AS_INT:
return ACL;
case GET_AS_INT:
return GET;
case PRI_AS_INT:
return PRI;
case PUT_AS_INT:
return PUT;
case POST_AS_INT:
if (len > 4 && buffer.get(buffer.position() + 4) == ' ')
return POST;
break;
case HEAD_AS_INT:
if (len > 4 && buffer.get(buffer.position() + 4) == ' ')
return HEAD;
break;
default:
break;
}
}
// Shortcut for 3 or 4 char methods, mostly for GET optimisation
if (len > 4)
return lookAheadGet(buffer, buffer.getInt(buffer.position()));
return LOOK_AHEAD.getBest(buffer, 0, len);
}

/**
* Optimized lookup to find a method name and trailing space in a byte array.
*
* @param buffer buffer containing ISO-8859-1 characters, it is not modified, which must have at least 5 bytes remaining
* @param lookAhead The integer representation of the first 4 bytes of the buffer that has previously been fetched
* with the equivalent of {@code buffer.getInt(buffer.position())}
* @return An HttpMethod if a match or null if no easy match.
*/
static HttpMethod lookAheadGet(ByteBuffer buffer, int lookAhead)
{
return switch (lookAhead)
{
case ACL_AS_INT -> ACL;
case GET_AS_INT -> GET;
case PRI_AS_INT -> PRI;
case PUT_AS_INT -> PUT;
case POST_AS_INT -> (buffer.get(buffer.position() + 4) == ' ') ? POST : LOOK_AHEAD.getBest(buffer, 0, buffer.remaining());
case HEAD_AS_INT -> (buffer.get(buffer.position() + 4) == ' ') ? HEAD : LOOK_AHEAD.getBest(buffer, 0, buffer.remaining());
default -> LOOK_AHEAD.getBest(buffer, 0, buffer.remaining());
};
}

/**
* Converts the given String parameter to an HttpMethod.
* The string may differ from the Enum name as a '-' in the method
Expand Down
Loading

0 comments on commit 80f912a

Please sign in to comment.