From d2b8ef4471a2c404d6ca69bbd4dc916b2b479964 Mon Sep 17 00:00:00 2001 From: redkale Date: Sat, 5 Oct 2024 17:53:49 +0800 Subject: [PATCH] =?UTF-8?q?readMethodUriLine=E4=BC=98=E5=8C=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../org/redkale/net/http/HttpContext.java | 40 ++++++- .../net/http/HttpDispatcherServlet.java | 12 +- .../org/redkale/net/http/HttpRequest.java | 113 ++++++++++++------ .../java/org/redkale/util/ByteTreeNode.java | 16 ++- .../test/httpparser/HttpRequestTest.java | 56 ++++++++- 5 files changed, 184 insertions(+), 53 deletions(-) diff --git a/src/main/java/org/redkale/net/http/HttpContext.java b/src/main/java/org/redkale/net/http/HttpContext.java index c2065d103..b48c2dafa 100644 --- a/src/main/java/org/redkale/net/http/HttpContext.java +++ b/src/main/java/org/redkale/net/http/HttpContext.java @@ -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[] uriPathCaches = new Map[100]; + private final UriPathNode uriPathNode = new UriPathNode(); + + // 不带通配符的mapping url的缓存对象 + private final Map[] 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 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 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 map = uriPathCaches[index]; @@ -237,6 +258,23 @@ public class HttpContext extends Context { return (Creator) Creator.create(newClazz); } + protected static class UriPathNode extends ByteTreeNode { + + public UriPathNode() { + super(); + } + + @Override + protected void put(String key, String value) { + super.put(key, value); + } + + @Override + protected String subNodeValue(ByteTreeNode node, String key, int subLen) { + return key.substring(0, subLen); + } + } + public static class HttpContextConfig extends ContextConfig { // 是否延迟解析http-header diff --git a/src/main/java/org/redkale/net/http/HttpDispatcherServlet.java b/src/main/java/org/redkale/net/http/HttpDispatcherServlet.java index c864f3549..a5f138cc9 100644 --- a/src/main/java/org/redkale/net/http/HttpDispatcherServlet.java +++ b/src/main/java/org/redkale/net/http/HttpDispatcherServlet.java @@ -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 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) { diff --git a/src/main/java/org/redkale/net/http/HttpRequest.java b/src/main/java/org/redkale/net/http/HttpRequest.java index 543650363..328f8b3de 100644 --- a/src/main/java/org/redkale/net/http/HttpRequest.java +++ b/src/main/java/org/redkale/net/http/HttpRequest.java @@ -595,11 +595,12 @@ public class HttpRequest extends Request { } // 解析 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 { } } 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 { 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 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 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 { .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 { 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 { } 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; } diff --git a/src/main/java/org/redkale/util/ByteTreeNode.java b/src/main/java/org/redkale/util/ByteTreeNode.java index da0233bb0..005d4f543 100644 --- a/src/main/java/org/redkale/util/ByteTreeNode.java +++ b/src/main/java/org/redkale/util/ByteTreeNode.java @@ -71,18 +71,30 @@ public class ByteTreeNode { } protected void put(String key, T value) { - ByteTreeNode n = this; + ByteTreeNode 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 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 node, String key, int subLen) { + return null; + } + + @Override + public String toString() { + return "ByteTreeNode{" + "index='" + (char) index + "', value=" + value + '}'; + } } diff --git a/src/test/java/org/redkale/test/httpparser/HttpRequestTest.java b/src/test/java/org/redkale/test/httpparser/HttpRequestTest.java index f6f146c12..51ae7e43a 100644 --- a/src/test/java/org/redkale/test/httpparser/HttpRequestTest.java +++ b/src/test/java/org/redkale/test/httpparser/HttpRequestTest.java @@ -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) {