readMethodUriLine优化
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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 + '}';
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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) {
|
||||
|
||||
Reference in New Issue
Block a user