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.channels.CompletionHandler;
import java.nio.charset.Charset; import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.security.SecureRandom; import java.security.SecureRandom;
import java.util.HashMap;
import java.util.Map; import java.util.Map;
import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentHashMap;
import org.redkale.annotation.ConstructorParameters; import org.redkale.annotation.ConstructorParameters;
@@ -51,7 +53,10 @@ public class HttpContext extends Context {
final boolean sameHeader; final boolean sameHeader;
// 不带通配符的mapping url的缓存对象 // 不带通配符的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) { public HttpContext(HttpContextConfig config) {
super(config); super(config);
@@ -66,6 +71,22 @@ public class HttpContext extends Context {
random.setSeed(Math.abs(System.nanoTime())); 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) { String loadUriPath(ByteArray array, boolean latin1, Charset charset) {
int index = array.length() >= uriPathCaches.length ? 0 : array.length(); int index = array.length() >= uriPathCaches.length ? 0 : array.length();
Map<ByteArray, String> map = uriPathCaches[index]; Map<ByteArray, String> map = uriPathCaches[index];
@@ -237,6 +258,23 @@ public class HttpContext extends Context {
return (Creator<H>) Creator.create(newClazz); 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 { public static class HttpContextConfig extends ContextConfig {
// 是否延迟解析http-header // 是否延迟解析http-header

View File

@@ -6,7 +6,6 @@
package org.redkale.net.http; package org.redkale.net.http;
import java.io.IOException; import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.util.*; import java.util.*;
import java.util.concurrent.locks.ReentrantLock; import java.util.concurrent.locks.ReentrantLock;
import java.util.function.*; import java.util.function.*;
@@ -511,16 +510,7 @@ public class HttpDispatcherServlet
getServlets().forEach(s -> { getServlets().forEach(s -> {
s.postStart(context, getServletConf(s)); s.postStart(context, getServletConf(s));
}); });
forEachMappingKey((k, s) -> { forEachMappingKey((k, s) -> context.addUriPath(k));
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);
});
} }
public HttpServlet findServletByTopic(String topic) { public HttpServlet findServletByTopic(String topic) {

View File

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

View File

@@ -71,18 +71,30 @@ public class ByteTreeNode<T> {
} }
protected void put(String key, T value) { protected void put(String key, T value) {
ByteTreeNode n = this; ByteTreeNode<T> n = this;
int i = 0;
for (char ch : key.toCharArray()) { for (char ch : key.toCharArray()) {
if (ch >= nodes.length) { if (ch >= nodes.length) {
throw new RedkaleException(key + " contains illegal char: " + ch); throw new RedkaleException(key + " contains illegal char: " + ch);
} }
ByteTreeNode s = n.nodes[ch]; i++;
ByteTreeNode<T> s = n.nodes[ch];
if (s == null) { if (s == null) {
s = new ByteTreeNode(n, ch); s = new ByteTreeNode(n, ch);
s.value = subNodeValue(s, key, i);
n.nodes[ch] = s; n.nodes[ch] = s;
} }
n = s; n = s;
} }
n.value = value; 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; package org.redkale.test.httpparser;
import java.lang.reflect.Method;
import java.nio.ByteBuffer; import java.nio.ByteBuffer;
import org.junit.jupiter.api.*; import org.junit.jupiter.api.*;
import org.redkale.net.http.HttpContext; import org.redkale.net.http.HttpContext;
@@ -23,6 +24,7 @@ public class HttpRequestTest {
test.run1(); test.run1();
test.run2(); test.run2();
test.run3(); test.run3();
test.run4();
} }
@Test @Test
@@ -40,7 +42,6 @@ public class HttpRequestTest {
int r1 = req.readHeader(ByteBuffer.wrap(text1.getBytes()), -1); int r1 = req.readHeader(ByteBuffer.wrap(text1.getBytes()), -1);
Assertions.assertEquals(1, r1); Assertions.assertEquals(1, r1);
Assertions.assertEquals(3, req.headerHalfLen()); Assertions.assertEquals(3, req.headerHalfLen());
System.out.println(req.headerHalfLen());
text1 = REQ_TEXT.substring(sublen, sublen + 7); text1 = REQ_TEXT.substring(sublen, sublen + 7);
int r2 = req.readHeader(ByteBuffer.wrap(text1.getBytes()), -1); 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); int r1 = req.readHeader(ByteBuffer.wrap(text1.getBytes()), -1);
Assertions.assertEquals(1, r1); Assertions.assertEquals(1, r1);
Assertions.assertEquals(3, req.headerHalfLen()); Assertions.assertEquals(3, req.headerHalfLen());
System.out.println(req.headerHalfLen());
text1 = REQ_TEXT.substring(sublen, sublen + 7); text1 = REQ_TEXT.substring(sublen, sublen + 7);
int r2 = req.readHeader(ByteBuffer.wrap(text1.getBytes()), -1); 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); int r1 = req.readHeader(ByteBuffer.wrap(text1.getBytes()), phLength);
Assertions.assertEquals(1, r1); Assertions.assertEquals(1, r1);
Assertions.assertEquals(3, req.headerHalfLen()); Assertions.assertEquals(3, req.headerHalfLen());
System.out.println(req.headerHalfLen());
text1 = REQ_TEXT.substring(sublen, sublen + 7); text1 = REQ_TEXT.substring(sublen, sublen + 7);
int r2 = req.readHeader(ByteBuffer.wrap(text1.getBytes()), phLength); int r2 = req.readHeader(ByteBuffer.wrap(text1.getBytes()), phLength);
@@ -110,6 +109,57 @@ public class HttpRequestTest {
Assertions.assertEquals(0, r3); 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 static class HttpRequestX extends HttpRequest {
public HttpRequestX(HttpContext context) { public HttpRequestX(HttpContext context) {