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