readMethodUriLine优化

This commit is contained in:
redkale
2024-10-05 17:53:49 +08:00
parent a2ac9ff896
commit d2b8ef4471
5 changed files with 184 additions and 53 deletions

View File

@@ -7,7 +7,9 @@ package org.redkale.net.http;
import java.nio.channels.CompletionHandler;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.security.SecureRandom;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import org.redkale.annotation.ConstructorParameters;
@@ -51,7 +53,10 @@ public class HttpContext extends Context {
final boolean sameHeader;
// 不带通配符的mapping url的缓存对象
final Map<ByteArray, String>[] uriPathCaches = new Map[100];
private final UriPathNode uriPathNode = new UriPathNode();
// 不带通配符的mapping url的缓存对象
private final Map<ByteArray, String>[] uriPathCaches = new Map[100];
public HttpContext(HttpContextConfig config) {
super(config);
@@ -66,6 +71,22 @@ public class HttpContext extends Context {
random.setSeed(Math.abs(System.nanoTime()));
}
ByteTreeNode<String> getUriPathNode() {
return uriPathNode;
}
void addUriPath(final String path) {
this.uriPathNode.put(path, path);
byte[] bs = path.getBytes(StandardCharsets.UTF_8);
int index = bs.length >= uriPathCaches.length ? 0 : bs.length;
Map<ByteArray, String> map = uriPathCaches[index];
if (map == null) {
map = new HashMap<>();
uriPathCaches[index] = map;
}
map.put(new ByteArray().put(bs), path);
}
String loadUriPath(ByteArray array, boolean latin1, Charset charset) {
int index = array.length() >= uriPathCaches.length ? 0 : array.length();
Map<ByteArray, String> map = uriPathCaches[index];
@@ -237,6 +258,23 @@ public class HttpContext extends Context {
return (Creator<H>) Creator.create(newClazz);
}
protected static class UriPathNode extends ByteTreeNode<String> {
public UriPathNode() {
super();
}
@Override
protected void put(String key, String value) {
super.put(key, value);
}
@Override
protected String subNodeValue(ByteTreeNode<String> node, String key, int subLen) {
return key.substring(0, subLen);
}
}
public static class HttpContextConfig extends ContextConfig {
// 是否延迟解析http-header

View File

@@ -6,7 +6,6 @@
package org.redkale.net.http;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.util.*;
import java.util.concurrent.locks.ReentrantLock;
import java.util.function.*;
@@ -511,16 +510,7 @@ public class HttpDispatcherServlet
getServlets().forEach(s -> {
s.postStart(context, getServletConf(s));
});
forEachMappingKey((k, s) -> {
byte[] bs = k.getBytes(StandardCharsets.UTF_8);
int index = bs.length >= context.uriPathCaches.length ? 0 : bs.length;
Map<ByteArray, String> map = context.uriPathCaches[index];
if (map == null) {
map = new HashMap<>();
context.uriPathCaches[index] = map;
}
map.put(new ByteArray().put(bs), k);
});
forEachMappingKey((k, s) -> context.addUriPath(k));
}
public HttpServlet findServletByTopic(String topic) {

View File

@@ -595,11 +595,12 @@ public class HttpRequest extends Request<HttpContext> {
}
// 解析 GET /xxx HTTP/1.1
private int readMethodUriLine(final ByteBuffer buf) {
protected int readMethodUriLine(final ByteBuffer buf) {
final ByteBuffer buffer = buf;
Charset charset = this.context.getCharset();
int remain = buffer.remaining();
int size;
byte b = 0;
ByteArray bytes = bodyBytes; // body当temp buffer使用
// 读method
if (this.method == null) {
@@ -654,17 +655,17 @@ public class HttpRequest extends Request<HttpContext> {
}
}
if (bigger) { // method长度大于4
for (; ; ) {
if (remain-- < 1) {
buffer.clear();
return 1;
}
byte b = buffer.get();
while (remain-- > 0) {
b = buffer.get();
if (b == ' ') {
break;
}
bytes.put(b);
}
if (b != ' ') {
buffer.clear();
return 1;
}
size = bytes.length();
byte[] content = bytes.content();
if (size == 3) {
@@ -710,28 +711,57 @@ public class HttpRequest extends Request<HttpContext> {
int qst = -1; // ?的位置
boolean decodeable = false;
boolean latin1 = true;
for (; ; ) {
if (remain-- < 1) {
buffer.clear();
return 1;
}
byte b = buffer.get();
boolean finding = true;
ByteTreeNode<String> pathNode = context.getUriPathNode();
while (remain-- > 0) {
b = buffer.get();
if (b == ' ') {
break;
}
if (b == '?' && qst < 0) {
qst = bytes.length();
} else if (!decodeable && (b == '+' || b == '%')) {
decodeable = true;
} else if (latin1 && (b < 0x20 || b >= 0x80)) {
latin1 = false;
finding = false;
}
bytes.put(b);
if (finding) {
ByteTreeNode<String> nextNode = pathNode.getNode(b);
if (nextNode == null) { // not match any path
nextNode = pathNode;
ByteArray tmp = headerBytes.clear();
tmp.put(b);
if (pathNode.getIndex() > 0) {
tmp.put(pathNode.getIndex());
while ((nextNode = nextNode.getParent()).getIndex() != 0) {
tmp.put(nextNode.getIndex());
}
}
for (int i = tmp.length() - 1; i >= 0; i--) {
bytes.put(tmp.get(i));
}
tmp.clear();
pathNode = null;
finding = false;
} else {
pathNode = nextNode;
}
} else {
if (!decodeable && (b == '+' || b == '%')) {
decodeable = true;
} else if (latin1 && (b < 0x20 || b >= 0x80)) {
latin1 = false;
}
bytes.put(b);
}
}
if (b != ' ') {
buffer.clear();
return 1;
}
size = bytes.length();
if (qst > 0) { // 带?参数
if (decodeable) { // 需要转义
if (qst >= 0) { // 带?参数
if (pathNode != null) {
this.requestPath = pathNode.getValue();
} else if (decodeable) { // 需要转义
this.requestPath = toDecodeString(bytes, 0, qst, charset);
} else {
this.requestPath = context.loadUriPath(bytes, qst, latin1, charset);
@@ -746,7 +776,9 @@ public class HttpRequest extends Request<HttpContext> {
.log(Level.WARNING, "HttpRequest.addParameter error: " + bytes.toString(), e);
}
} else { // 没有带?参数
if (decodeable) { // 需要转义
if (pathNode != null) {
this.requestPath = pathNode.getValue();
} else if (decodeable) { // 需要转义
this.requestPath = toDecodeString(bytes, 0, bytes.length(), charset);
} else {
this.requestPath = context.loadUriPath(bytes, latin1, charset);
@@ -756,13 +788,11 @@ public class HttpRequest extends Request<HttpContext> {
bytes.clear();
}
// 读protocol
for (; ; ) {
if (remain-- < 1) {
this.params.clear();
buffer.clear();
return 1;
}
byte b = buffer.get();
this.protocol = HTTP_1_1;
byte last = 0;
boolean has = !bytes.isEmpty();
while (remain-- > 0) {
b = buffer.get();
if (b == '\r') {
if (remain-- < 1) {
this.params.clear();
@@ -775,17 +805,28 @@ public class HttpRequest extends Request<HttpContext> {
}
break;
}
bytes.put(b);
last = b;
if (has) {
bytes.put(b);
}
}
size = bytes.length();
byte[] content = bytes.content();
if (size == 8 && content[0] == 'H' && content[5] == '1' && content[7] == '1') {
this.protocol = HTTP_1_1;
} else if (size == 8 && content[0] == 'H' && content[5] == '2' && content[7] == '0') {
if (b != '\r') {
this.params.clear();
buffer.clear();
return 1;
}
if (last == '0') {
this.protocol = HTTP_2_0;
} else {
this.protocol = bytes.toString(true, charset);
}
// size = bytes.length();
// byte[] content = bytes.content();
// if (size == 8 && content[0] == 'H' && content[5] == '1' && content[7] == '1') {
// this.protocol = HTTP_1_1;
// } else if (size == 8 && content[0] == 'H' && content[5] == '2' && content[7] == '0') {
// this.protocol = HTTP_2_0;
// } else {
// this.protocol = bytes.toString(true, charset);
// }
bytes.clear();
return 0;
}

View File

@@ -71,18 +71,30 @@ public class ByteTreeNode<T> {
}
protected void put(String key, T value) {
ByteTreeNode n = this;
ByteTreeNode<T> n = this;
int i = 0;
for (char ch : key.toCharArray()) {
if (ch >= nodes.length) {
throw new RedkaleException(key + " contains illegal char: " + ch);
}
ByteTreeNode s = n.nodes[ch];
i++;
ByteTreeNode<T> s = n.nodes[ch];
if (s == null) {
s = new ByteTreeNode(n, ch);
s.value = subNodeValue(s, key, i);
n.nodes[ch] = s;
}
n = s;
}
n.value = value;
}
protected T subNodeValue(ByteTreeNode<T> node, String key, int subLen) {
return null;
}
@Override
public String toString() {
return "ByteTreeNode{" + "index='" + (char) index + "', value=" + value + '}';
}
}

View File

@@ -4,6 +4,7 @@
*/
package org.redkale.test.httpparser;
import java.lang.reflect.Method;
import java.nio.ByteBuffer;
import org.junit.jupiter.api.*;
import org.redkale.net.http.HttpContext;
@@ -23,6 +24,7 @@ public class HttpRequestTest {
test.run1();
test.run2();
test.run3();
test.run4();
}
@Test
@@ -40,7 +42,6 @@ public class HttpRequestTest {
int r1 = req.readHeader(ByteBuffer.wrap(text1.getBytes()), -1);
Assertions.assertEquals(1, r1);
Assertions.assertEquals(3, req.headerHalfLen());
System.out.println(req.headerHalfLen());
text1 = REQ_TEXT.substring(sublen, sublen + 7);
int r2 = req.readHeader(ByteBuffer.wrap(text1.getBytes()), -1);
@@ -68,7 +69,6 @@ public class HttpRequestTest {
int r1 = req.readHeader(ByteBuffer.wrap(text1.getBytes()), -1);
Assertions.assertEquals(1, r1);
Assertions.assertEquals(3, req.headerHalfLen());
System.out.println(req.headerHalfLen());
text1 = REQ_TEXT.substring(sublen, sublen + 7);
int r2 = req.readHeader(ByteBuffer.wrap(text1.getBytes()), -1);
@@ -98,7 +98,6 @@ public class HttpRequestTest {
int r1 = req.readHeader(ByteBuffer.wrap(text1.getBytes()), phLength);
Assertions.assertEquals(1, r1);
Assertions.assertEquals(3, req.headerHalfLen());
System.out.println(req.headerHalfLen());
text1 = REQ_TEXT.substring(sublen, sublen + 7);
int r2 = req.readHeader(ByteBuffer.wrap(text1.getBytes()), phLength);
@@ -110,6 +109,57 @@ public class HttpRequestTest {
Assertions.assertEquals(0, r3);
}
@Test
public void run4() throws Exception {
HttpContext.HttpContextConfig httpConfig = new HttpContext.HttpContextConfig();
httpConfig.lazyHeader = true;
httpConfig.sameHeader = true;
httpConfig.maxHeader = 16 * 1024;
httpConfig.maxBody = 64 * 1024;
HttpContext context = new HttpContext(httpConfig);
Method method = HttpContext.class.getDeclaredMethod("addUriPath", String.class);
method.setAccessible(true);
method.invoke(context, "/test/sleep200");
method.invoke(context, "/test/aaa");
HttpRequestX req = new HttpRequestX(context);
String text = "GET /test/azzzz HTTP/1.1\r\nConnection: Keep-Alive\r\nContent-Length: 0\r\n\r\n";
int r1 = req.readHeader(ByteBuffer.wrap(text.getBytes()), 0);
Assertions.assertEquals(0, r1);
Assertions.assertEquals("/test/azzzz", req.getRequestPath());
req = new HttpRequestX(context);
text = "GET /test/aaaa HTTP/1.1\r\nConnection: Keep-Alive\r\nContent-Length: 0\r\n\r\n";
r1 = req.readHeader(ByteBuffer.wrap(text.getBytes()), 0);
Assertions.assertEquals(0, r1);
Assertions.assertEquals("/test/aaaa", req.getRequestPath());
req = new HttpRequestX(context);
text = "GET /test/sleep200 HTTP/1.1\r\nConnection: Keep-Alive\r\nContent-Length: 0\r\n\r\n";
r1 = req.readHeader(ByteBuffer.wrap(text.getBytes()), 0);
Assertions.assertEquals(0, r1);
Assertions.assertEquals("/test/sleep200", req.getRequestPath());
req = new HttpRequestX(context);
text = "GET /test/sleep201 HTTP/1.1\r\nConnection: Keep-Alive\r\nContent-Length: 0\r\n\r\n";
r1 = req.readHeader(ByteBuffer.wrap(text.getBytes()), 0);
Assertions.assertEquals(0, r1);
Assertions.assertEquals("/test/sleep201", req.getRequestPath());
req = new HttpRequestX(context);
text = "GET /test/sleep20? HTTP/1.1\r\nConnection: Keep-Alive\r\nContent-Length: 0\r\n\r\n";
r1 = req.readHeader(ByteBuffer.wrap(text.getBytes()), 0);
Assertions.assertEquals(0, r1);
Assertions.assertEquals("/test/sleep20", req.getRequestPath());
req = new HttpRequestX(context);
text = "GET /test/sleep20?n=haha HTTP/1.1\r\nConnection: Keep-Alive\r\nContent-Length: 0\r\n\r\n";
r1 = req.readHeader(ByteBuffer.wrap(text.getBytes()), 0);
Assertions.assertEquals(0, r1);
Assertions.assertEquals("/test/sleep20", req.getRequestPath());
Assertions.assertEquals("haha", req.getParameter("n"));
}
public static class HttpRequestX extends HttpRequest {
public HttpRequestX(HttpContext context) {