enhanced HTTP server configuration and response handling; added acceptorThreads, improved header management, and refined error page titles
This commit is contained in:
@@ -1,7 +1,7 @@
|
||||
package dev.relism;
|
||||
|
||||
import dev.relism.models.HeaderMap;
|
||||
import dev.relism.http.HttpMethod;
|
||||
import dev.relism.models.HeaderMap;
|
||||
import dev.relism.models.Request;
|
||||
import dev.relism.models.RequestLine;
|
||||
import dev.relism.routing.routers.fastpathrouter.FastPathViews;
|
||||
@@ -13,7 +13,7 @@ import java.io.InputStream;
|
||||
import java.util.Arrays;
|
||||
|
||||
/**
|
||||
* One instance per connection — the buffer is allocated once and reused across keep-alive
|
||||
* One instance per connection, the buffer is allocated once and reused across keep-alive
|
||||
* requests. Grows on demand (doubling, up to {@code maxHeaderBufferSize}). Zero String
|
||||
* allocations during parsing; paths, headers and protocol are exposed as {@link dev.relism.fpr.core.ByteView} slices.
|
||||
*/
|
||||
@@ -22,7 +22,10 @@ public class RequestParser {
|
||||
private static final int INITIAL_BUFFER_SIZE = 8192;
|
||||
|
||||
private final int maxHeaderBufferSize;
|
||||
private final HeaderMap headerMap = new HeaderMap();
|
||||
private byte[] buffer;
|
||||
private int bufBase = 0; // absolute start of valid data in buffer
|
||||
private int bufLen = 0; // number of valid bytes from bufBase
|
||||
|
||||
public RequestParser() { this(64 * 1024); }
|
||||
public RequestParser(int maxHeaderBufferSize) {
|
||||
@@ -31,30 +34,42 @@ public class RequestParser {
|
||||
}
|
||||
|
||||
public Request parse(InputStream in) throws IOException {
|
||||
int totalRead = 0;
|
||||
int headerEndIdx = -1;
|
||||
while (true) {
|
||||
if (totalRead == buffer.length) {
|
||||
if (buffer.length >= maxHeaderBufferSize)
|
||||
// Take ownership of any leftover bytes from the previous request, then reset so
|
||||
// early-returns leave the fields in a clean state.
|
||||
int base = bufBase;
|
||||
int totalRead = bufLen;
|
||||
bufBase = 0;
|
||||
bufLen = 0;
|
||||
|
||||
int headerEndIdx = totalRead > 0 ? findEndOfHeader(buffer, base, base + totalRead) : -1;
|
||||
while (headerEndIdx == -1) {
|
||||
if (base + totalRead == buffer.length) {
|
||||
if (base > 0) {
|
||||
// Compact: slide valid data to position 0 — rare path (~every N requests
|
||||
// where N = bufferSize / avgRequestSize rather than every request).
|
||||
System.arraycopy(buffer, base, buffer, 0, totalRead);
|
||||
base = 0;
|
||||
} else if (buffer.length >= maxHeaderBufferSize) {
|
||||
throw new IOException("Request headers exceed " + maxHeaderBufferSize + " bytes");
|
||||
buffer = Arrays.copyOf(buffer, Math.min(buffer.length * 2, maxHeaderBufferSize));
|
||||
} else {
|
||||
buffer = Arrays.copyOf(buffer, Math.min(buffer.length * 2, maxHeaderBufferSize));
|
||||
}
|
||||
}
|
||||
int n = in.read(buffer, totalRead, buffer.length - totalRead);
|
||||
int n = in.read(buffer, base + totalRead, buffer.length - base - totalRead);
|
||||
if (n <= 0) break;
|
||||
int prevTotal = totalRead;
|
||||
totalRead += n;
|
||||
headerEndIdx = findEndOfHeader(buffer, Math.max(0, prevTotal - 3), totalRead);
|
||||
if (headerEndIdx != -1) break;
|
||||
headerEndIdx = findEndOfHeader(buffer, base + Math.max(0, prevTotal - 3), base + totalRead);
|
||||
}
|
||||
if (totalRead <= 0) return null;
|
||||
if (headerEndIdx == -1) {
|
||||
throw new IOException("Request headers exceed " + maxHeaderBufferSize + " bytes");
|
||||
}
|
||||
|
||||
int methodEnd = find(buffer, 0, headerEndIdx, (byte) ' ');
|
||||
int methodEnd = find(buffer, base, headerEndIdx, (byte) ' ');
|
||||
if (methodEnd == -1) throw new IOException("Invalid request line (method)");
|
||||
|
||||
HttpMethod method = HttpMethod.fromBytes(buffer, 0, methodEnd);
|
||||
HttpMethod method = HttpMethod.fromBytes(buffer, base, methodEnd - base);
|
||||
if (method == null) throw new IOException("Unsupported HTTP method");
|
||||
|
||||
int pathStart = methodEnd + 1;
|
||||
@@ -74,9 +89,10 @@ public class RequestParser {
|
||||
|
||||
FastPathViews.RequestByteView protocolView = new FastPathViews.RequestByteView(buffer, protocolStart, protocolEnd - protocolStart);
|
||||
|
||||
HeaderMap headerMap = new HeaderMap(buffer);
|
||||
int current = find(buffer, protocolEnd, headerEndIdx, (byte) '\n') + 1;
|
||||
int contentLength = 0;
|
||||
int sectionStart = find(buffer, protocolEnd, headerEndIdx, (byte) '\n') + 1;
|
||||
int current = sectionStart;
|
||||
long contentLength = 0;
|
||||
boolean isChunked = false;
|
||||
|
||||
while (current < headerEndIdx) {
|
||||
int lineEnd = find(buffer, current, headerEndIdx + 1, (byte) '\r');
|
||||
@@ -87,20 +103,39 @@ public class RequestParser {
|
||||
int valueStart = colon + 1;
|
||||
while (valueStart < lineEnd && buffer[valueStart] == ' ') valueStart++;
|
||||
|
||||
headerMap.add(current, colon - current, valueStart, lineEnd - valueStart);
|
||||
|
||||
if (equalsIgnoreCase(buffer, current, colon, "content-length")) {
|
||||
contentLength = parseInt(buffer, valueStart, lineEnd);
|
||||
contentLength = parseLong(buffer, valueStart, lineEnd);
|
||||
} else if (equalsIgnoreCase(buffer, current, colon, "transfer-encoding")) {
|
||||
isChunked = equalsIgnoreCase(buffer, valueStart, lineEnd, "chunked");
|
||||
}
|
||||
}
|
||||
current = lineEnd + 2;
|
||||
}
|
||||
|
||||
headerMap.reset(buffer, sectionStart, headerEndIdx);
|
||||
|
||||
int bodyStart = headerEndIdx + 4;
|
||||
int preBufLen = totalRead - bodyStart;
|
||||
return Request.forParsed(
|
||||
new RequestLine(method, pathView, queryView, protocolView, headerMap),
|
||||
in, contentLength, buffer, bodyStart, preBufLen);
|
||||
int preBufLen = (base + totalRead) - bodyStart;
|
||||
|
||||
// Any bytes read beyond this request's body belong to the next request.
|
||||
// Store their absolute position in the buffer — no copy needed; the next parse()
|
||||
// call will read directly from bufBase without touching the data.
|
||||
if (!isChunked && contentLength == 0 && preBufLen > 0) {
|
||||
bufBase = bodyStart;
|
||||
bufLen = preBufLen;
|
||||
preBufLen = 0;
|
||||
} else if (!isChunked && contentLength > 0 && preBufLen > contentLength) {
|
||||
bufBase = bodyStart + (int) contentLength;
|
||||
bufLen = preBufLen - (int) contentLength;
|
||||
preBufLen = (int) contentLength;
|
||||
}
|
||||
|
||||
RequestLine requestLine = new RequestLine(method, pathView, queryView, protocolView, headerMap);
|
||||
|
||||
if (isChunked) {
|
||||
return Request.forParsed(requestLine, new ChunkedInputStream(in, buffer, bodyStart, preBufLen), -1L, null, 0, 0);
|
||||
}
|
||||
return Request.forParsed(requestLine, in, contentLength, buffer, bodyStart, preBufLen);
|
||||
}
|
||||
|
||||
private static int findEndOfHeader(byte[] buf, int from, int len) {
|
||||
@@ -130,8 +165,8 @@ public class RequestParser {
|
||||
return true;
|
||||
}
|
||||
|
||||
private static int parseInt(byte[] buf, int start, int end) {
|
||||
int value = 0;
|
||||
private static long parseLong(byte[] buf, int start, int end) {
|
||||
long value = 0;
|
||||
for (int i = start; i < end; i++) {
|
||||
byte c = buf[i];
|
||||
if (c >= '0' && c <= '9') value = value * 10 + (c - '0');
|
||||
|
||||
Reference in New Issue
Block a user