check byte order mark, enable duplicate values in form parameters
This commit is contained in:
parent
98b13c8dc7
commit
35d67784bb
4 changed files with 133 additions and 5 deletions
|
@ -1,5 +1,5 @@
|
||||||
group = org.xbib
|
group = org.xbib
|
||||||
name = net-http
|
name = net-http
|
||||||
version = 3.3.1
|
version = 3.3.2
|
||||||
|
|
||||||
org.gradle.warning.mode = ALL
|
org.gradle.warning.mode = ALL
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
package org.xbib.net.http.server.netty;
|
package org.xbib.net.http.server.netty;
|
||||||
|
|
||||||
|
import io.netty.buffer.ByteBuf;
|
||||||
import io.netty.buffer.ByteBufUtil;
|
import io.netty.buffer.ByteBufUtil;
|
||||||
import io.netty.handler.codec.http.FullHttpRequest;
|
import io.netty.handler.codec.http.FullHttpRequest;
|
||||||
import io.netty.handler.codec.http.multipart.FileUpload;
|
import io.netty.handler.codec.http.multipart.FileUpload;
|
||||||
|
@ -27,6 +28,8 @@ public class HttpRequestBuilder extends BaseHttpRequestBuilder {
|
||||||
|
|
||||||
protected ByteBuffer byteBuffer;
|
protected ByteBuffer byteBuffer;
|
||||||
|
|
||||||
|
protected CharBuffer charBuffer;
|
||||||
|
|
||||||
protected HttpRequestBuilder() {
|
protected HttpRequestBuilder() {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -47,9 +50,10 @@ public class HttpRequestBuilder extends BaseHttpRequestBuilder {
|
||||||
setMethod(HttpMethod.valueOf(fullHttpRequest.method().name()));
|
setMethod(HttpMethod.valueOf(fullHttpRequest.method().name()));
|
||||||
setRequestURI(fullHttpRequest.uri());
|
setRequestURI(fullHttpRequest.uri());
|
||||||
fullHttpRequest.headers().entries().forEach(e -> addHeader(e.getKey(), e.getValue()));
|
fullHttpRequest.headers().entries().forEach(e -> addHeader(e.getKey(), e.getValue()));
|
||||||
// read all bytes from request into a JDK ByteBuffer. This might be expensive.
|
|
||||||
if (fullHttpRequest.content() != null) {
|
if (fullHttpRequest.content() != null) {
|
||||||
byteBuffer = ByteBuffer.wrap(ByteBufUtil.getBytes(fullHttpRequest.content()));
|
ByteBuf byteBuf = fullHttpRequest.content();
|
||||||
|
byte[] bytes = ByteBufUtil.getBytes(byteBuf);
|
||||||
|
byteBuffer = ByteBuffer.wrap(bytes);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return this;
|
return this;
|
||||||
|
@ -62,7 +66,10 @@ public class HttpRequestBuilder extends BaseHttpRequestBuilder {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public CharBuffer getBodyAsChars(Charset charset) {
|
public CharBuffer getBodyAsChars(Charset charset) {
|
||||||
return byteBuffer != null ? charset.decode(byteBuffer) : null;
|
if (charBuffer == null) {
|
||||||
|
charBuffer = byteBuffer != null ? charset.decode(byteBuffer) : null;
|
||||||
|
}
|
||||||
|
return charBuffer;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
|
@ -0,0 +1,115 @@
|
||||||
|
package org.xbib.net.http.netty.test;
|
||||||
|
|
||||||
|
import io.netty.bootstrap.Bootstrap;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
import org.xbib.net.NetworkClass;
|
||||||
|
import org.xbib.net.Parameter;
|
||||||
|
import org.xbib.net.URL;
|
||||||
|
import org.xbib.net.http.HttpAddress;
|
||||||
|
import org.xbib.net.http.HttpHeaderNames;
|
||||||
|
import org.xbib.net.http.HttpHeaderValues;
|
||||||
|
import org.xbib.net.http.HttpMethod;
|
||||||
|
import org.xbib.net.http.HttpResponseStatus;
|
||||||
|
import org.xbib.net.http.client.netty.HttpRequest;
|
||||||
|
import org.xbib.net.http.client.netty.NettyHttpClient;
|
||||||
|
import org.xbib.net.http.client.netty.NettyHttpClientConfig;
|
||||||
|
import org.xbib.net.http.server.application.BaseApplication;
|
||||||
|
import org.xbib.net.http.server.domain.BaseHttpDomain;
|
||||||
|
import org.xbib.net.http.server.executor.BaseExecutor;
|
||||||
|
import org.xbib.net.http.server.executor.Executor;
|
||||||
|
import org.xbib.net.http.server.netty.NettyHttpServer;
|
||||||
|
import org.xbib.net.http.server.netty.NettyHttpServerConfig;
|
||||||
|
import org.xbib.net.http.server.route.BaseHttpRouter;
|
||||||
|
import org.xbib.net.http.server.route.HttpRouter;
|
||||||
|
import org.xbib.net.http.server.service.BaseHttpService;
|
||||||
|
|
||||||
|
import java.nio.charset.StandardCharsets;
|
||||||
|
import java.util.concurrent.atomic.AtomicBoolean;
|
||||||
|
import java.util.logging.Level;
|
||||||
|
import java.util.logging.Logger;
|
||||||
|
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||||
|
|
||||||
|
class NettyHttpServerByteOrderMarkTest {
|
||||||
|
|
||||||
|
private static final Logger logger = Logger.getLogger(NettyHttpServerByteOrderMarkTest.class.getName());
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testJsonPostRequest() throws Exception {
|
||||||
|
|
||||||
|
String body = "{\"syntax\":\"CQL\",\"offset\":0,\"size\":10,\"service\":[\"hbz\"],\"op\":[\"\",\"@and\",\"@and\"],\"key\":[\"bib.any\",\"bib.title\",\"bib.identifierISSN\"],\"query\":[\"linux einführung\",\"\",\"\"]}";
|
||||||
|
|
||||||
|
URL url = URL.from("http://localhost:8008/");
|
||||||
|
HttpAddress httpAddress1 = HttpAddress.http1(url);
|
||||||
|
NettyHttpServerConfig nettyHttpServerConfig = new NettyHttpServerConfig();
|
||||||
|
nettyHttpServerConfig.setServerName("NettyHttpServer",
|
||||||
|
Bootstrap.class.getPackage().getImplementationVersion());
|
||||||
|
nettyHttpServerConfig.setNetworkClass(NetworkClass.LOCAL);
|
||||||
|
nettyHttpServerConfig.setDebug(true);
|
||||||
|
nettyHttpServerConfig.setChunkWriteEnabled(true);
|
||||||
|
|
||||||
|
HttpRouter router = BaseHttpRouter.builder()
|
||||||
|
.addDomain(BaseHttpDomain.builder()
|
||||||
|
.setHttpAddress(httpAddress1)
|
||||||
|
.addService(BaseHttpService.builder()
|
||||||
|
.setPath("/")
|
||||||
|
.setMethod(HttpMethod.POST)
|
||||||
|
.setHandler(ctx -> {
|
||||||
|
logger.log(Level.FINEST, "handler starting");
|
||||||
|
String content = ctx.request().getBodyAsChars(StandardCharsets.UTF_8).toString();
|
||||||
|
logger.log(Level.FINEST, "got content = " + content);
|
||||||
|
logger.log(Level.FINEST, "got FORM params op = " + ctx.httpRequest().getParameter().getAll("op", Parameter.Domain.FORM));
|
||||||
|
logger.log(Level.FINEST, "got FORM params key = " + ctx.httpRequest().getParameter().getAll("key", Parameter.Domain.FORM));
|
||||||
|
logger.log(Level.FINEST, "got FORM params query = " + ctx.httpRequest().getParameter().getAll("query", Parameter.Domain.FORM));
|
||||||
|
ctx.response()
|
||||||
|
.setResponseStatus(HttpResponseStatus.OK)
|
||||||
|
.setHeader(HttpHeaderNames.CONTENT_TYPE, HttpHeaderValues.TEXT_PLAIN)
|
||||||
|
.setCharset(StandardCharsets.UTF_8);
|
||||||
|
ctx.write("parameter = " + ctx.httpRequest().getParameter().allToString() +
|
||||||
|
" local address = " + ctx.httpRequest().getLocalAddress() +
|
||||||
|
" remote address = " + ctx.httpRequest().getRemoteAddress() +
|
||||||
|
" attributes = " + ctx.getAttributes() +
|
||||||
|
" content = " + content
|
||||||
|
);
|
||||||
|
})
|
||||||
|
.build())
|
||||||
|
.build())
|
||||||
|
.build();
|
||||||
|
|
||||||
|
Executor executor = BaseExecutor.builder()
|
||||||
|
.build();
|
||||||
|
|
||||||
|
try (NettyHttpServer server = NettyHttpServer.builder()
|
||||||
|
.setHttpServerConfig(nettyHttpServerConfig)
|
||||||
|
.setApplication(BaseApplication.builder()
|
||||||
|
.setExecutor(executor)
|
||||||
|
.setRouter(router)
|
||||||
|
.build())
|
||||||
|
.build()) {
|
||||||
|
server.bind();
|
||||||
|
NettyHttpClientConfig config = new NettyHttpClientConfig()
|
||||||
|
.setGzipEnabled(true)
|
||||||
|
.setChunkWriteEnabled(true)
|
||||||
|
.setObjectAggregationEnabled(true)
|
||||||
|
.setDebug(true);
|
||||||
|
AtomicBoolean received = new AtomicBoolean();
|
||||||
|
try (NettyHttpClient client = NettyHttpClient.builder()
|
||||||
|
.setConfig(config)
|
||||||
|
.build()) {
|
||||||
|
HttpRequest request = HttpRequest.post()
|
||||||
|
.setURL(url)
|
||||||
|
.content(body, "application/json", StandardCharsets.UTF_8)
|
||||||
|
.setResponseListener(resp -> {
|
||||||
|
logger.log(Level.INFO, " status = " + resp.getStatus() +
|
||||||
|
" got response headers = " + resp.getHeaders() +
|
||||||
|
" got response body = " + resp.getBodyAsChars(StandardCharsets.UTF_8));
|
||||||
|
received.set(true);
|
||||||
|
})
|
||||||
|
.build();
|
||||||
|
client.execute(request).get().close();
|
||||||
|
}
|
||||||
|
|
||||||
|
assertTrue(received.get());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -38,6 +38,8 @@ public class BaseHttpServerContext implements HttpServerContext {
|
||||||
|
|
||||||
private static final String PATH_SEPARATOR = "/";
|
private static final String PATH_SEPARATOR = "/";
|
||||||
|
|
||||||
|
private static final String BOM = "\uffff";
|
||||||
|
|
||||||
private final Application application;
|
private final Application application;
|
||||||
|
|
||||||
private final HttpRequestBuilder httpRequestBuilder;
|
private final HttpRequestBuilder httpRequestBuilder;
|
||||||
|
@ -220,7 +222,8 @@ public class BaseHttpServerContext implements HttpServerContext {
|
||||||
.charset(charset, CodingErrorAction.REPLACE)
|
.charset(charset, CodingErrorAction.REPLACE)
|
||||||
.path(httpRequestBuilder.getRequestURI())
|
.path(httpRequestBuilder.getRequestURI())
|
||||||
.build();
|
.build();
|
||||||
ParameterBuilder formParameterBuilder = Parameter.builder().domain(Parameter.Domain.FORM);
|
ParameterBuilder formParameterBuilder = Parameter.builder().domain(Parameter.Domain.FORM)
|
||||||
|
.enableDuplicates();
|
||||||
// https://www.w3.org/TR/html4/interact/forms.html#h-17.13.4
|
// https://www.w3.org/TR/html4/interact/forms.html#h-17.13.4
|
||||||
if (HttpMethod.POST.equals(httpRequestBuilder.getMethod()) &&
|
if (HttpMethod.POST.equals(httpRequestBuilder.getMethod()) &&
|
||||||
(mimeType != null && mimeType.contains(HttpHeaderValues.APPLICATION_X_WWW_FORM_URLENCODED))) {
|
(mimeType != null && mimeType.contains(HttpHeaderValues.APPLICATION_X_WWW_FORM_URLENCODED))) {
|
||||||
|
@ -234,6 +237,9 @@ public class BaseHttpServerContext implements HttpServerContext {
|
||||||
if (contentType != null && contentType.contains(HttpHeaderValues.APPLICATION_JSON)) {
|
if (contentType != null && contentType.contains(HttpHeaderValues.APPLICATION_JSON)) {
|
||||||
String content = httpRequestBuilder.getBodyAsChars(StandardCharsets.UTF_8).toString();
|
String content = httpRequestBuilder.getBodyAsChars(StandardCharsets.UTF_8).toString();
|
||||||
try {
|
try {
|
||||||
|
if (content.startsWith(BOM)) {
|
||||||
|
content = content.substring(BOM.length());
|
||||||
|
}
|
||||||
Map<String, Object> map = Json.toMap(content);
|
Map<String, Object> map = Json.toMap(content);
|
||||||
for (Map.Entry<String, Object> entry : map.entrySet()) {
|
for (Map.Entry<String, Object> entry : map.entrySet()) {
|
||||||
if (entry.getValue() instanceof Iterable<?> iterable) {
|
if (entry.getValue() instanceof Iterable<?> iterable) {
|
||||||
|
|
Loading…
Reference in a new issue