fix parameter/multimap handling with duplicates, add test
This commit is contained in:
parent
3c7e102765
commit
0a31fca8fe
7 changed files with 135 additions and 60 deletions
|
@ -1,3 +1,3 @@
|
||||||
group = org.xbib
|
group = org.xbib
|
||||||
name = net-http
|
name = net-http
|
||||||
version = 4.0.8
|
version = 4.0.9
|
||||||
|
|
|
@ -3,6 +3,7 @@ dependencies {
|
||||||
api libs.netty.codec.http2
|
api libs.netty.codec.http2
|
||||||
testImplementation project(':net-http-client-netty')
|
testImplementation project(':net-http-client-netty')
|
||||||
testImplementation project(':net-http-template-groovy')
|
testImplementation project(':net-http-template-groovy')
|
||||||
|
testImplementation libs.datastructures.json.tiny
|
||||||
}
|
}
|
||||||
|
|
||||||
test {
|
test {
|
||||||
|
|
|
@ -35,7 +35,7 @@ public class HttpRequest extends BaseHttpRequest {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public InputStream getInputStream() {
|
public InputStream getInputStream() {
|
||||||
return builder.byteBuffer != null ? new ByteBufferInputStream(builder.byteBuffer) : null;
|
return builder.getBody() != null ? new ByteBufferInputStream(builder.getBody()) : null;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -49,7 +49,7 @@ public class HttpRequest extends BaseHttpRequest {
|
||||||
return "HttpRequest[method=" + builder.getMethod() +
|
return "HttpRequest[method=" + builder.getMethod() +
|
||||||
",version=" + builder.getVersion() +
|
",version=" + builder.getVersion() +
|
||||||
",parameter=" + builder.getParameter() +
|
",parameter=" + builder.getParameter() +
|
||||||
",body=" + (builder.byteBuffer != null) +
|
",body=" + (builder.getBody() != null) +
|
||||||
"]";
|
"]";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -27,10 +27,6 @@ public class HttpRequestBuilder extends BaseHttpRequestBuilder {
|
||||||
|
|
||||||
private FullHttpRequest httpRequest;
|
private FullHttpRequest httpRequest;
|
||||||
|
|
||||||
protected ByteBuffer byteBuffer;
|
|
||||||
|
|
||||||
protected CharBuffer charBuffer;
|
|
||||||
|
|
||||||
protected HttpRequestBuilder() {
|
protected HttpRequestBuilder() {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -59,25 +55,12 @@ public class HttpRequestBuilder extends BaseHttpRequestBuilder {
|
||||||
if (fullHttpRequest.content() != null) {
|
if (fullHttpRequest.content() != null) {
|
||||||
ByteBuf byteBuf = fullHttpRequest.content();
|
ByteBuf byteBuf = fullHttpRequest.content();
|
||||||
byte[] bytes = ByteBufUtil.getBytes(byteBuf);
|
byte[] bytes = ByteBufUtil.getBytes(byteBuf);
|
||||||
byteBuffer = ByteBuffer.wrap(bytes);
|
this.byteBuffer = ByteBuffer.wrap(bytes);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public ByteBuffer getBody() {
|
|
||||||
return byteBuffer;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public CharBuffer getBodyAsChars(Charset charset) {
|
|
||||||
if (charBuffer == null) {
|
|
||||||
charBuffer = byteBuffer != null ? charset.decode(byteBuffer) : null;
|
|
||||||
}
|
|
||||||
return charBuffer;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public HttpRequestBuilder setAddress(HttpAddress httpAddress) {
|
public HttpRequestBuilder setAddress(HttpAddress httpAddress) {
|
||||||
super.setAddress(httpAddress);
|
super.setAddress(httpAddress);
|
||||||
|
|
|
@ -0,0 +1,70 @@
|
||||||
|
package org.xbib.net.http.netty.test;
|
||||||
|
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
import org.xbib.datastructures.api.Builder;
|
||||||
|
import org.xbib.datastructures.json.tiny.JsonBuilder;
|
||||||
|
import org.xbib.net.Parameter;
|
||||||
|
import org.xbib.net.http.HttpHeaders;
|
||||||
|
import org.xbib.net.http.HttpMethod;
|
||||||
|
import org.xbib.net.http.server.HttpRequest;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.nio.ByteBuffer;
|
||||||
|
import java.nio.charset.StandardCharsets;
|
||||||
|
import java.util.Objects;
|
||||||
|
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||||
|
|
||||||
|
public class HttpRequestTest {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testFormAsMultiMap() {
|
||||||
|
Parameter parameter = Parameter.builder().domain(Parameter.Domain.FORM)
|
||||||
|
.enableDuplicates()
|
||||||
|
.add("key", "key1")
|
||||||
|
.add("key", "key2")
|
||||||
|
.add("key", "key3")
|
||||||
|
.add("value", "value1")
|
||||||
|
.add("value", "")
|
||||||
|
.add("value", "")
|
||||||
|
.add("op", "and")
|
||||||
|
.add("op", "")
|
||||||
|
.add("op", "")
|
||||||
|
.build();
|
||||||
|
HttpHeaders httpHeaders = new HttpHeaders();
|
||||||
|
HttpRequest httpRequest = org.xbib.net.http.server.netty.HttpRequest.builder()
|
||||||
|
.setMethod(HttpMethod.POST)
|
||||||
|
.setParameter(parameter)
|
||||||
|
.setHeaders(httpHeaders)
|
||||||
|
.build();
|
||||||
|
assertEquals("{key=[key1, key2, key3], value=[value1, , ], op=[and, , ]}",
|
||||||
|
httpRequest.asMultiMap().toString());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testJsonBodyAsMultiMap() throws IOException {
|
||||||
|
// JSON keys overwrite each other
|
||||||
|
Builder builder = JsonBuilder.builder()
|
||||||
|
.beginMap()
|
||||||
|
.field("key", "key1")
|
||||||
|
.field("key", "key2")
|
||||||
|
.field("key", "key3")
|
||||||
|
.field("value", "value1")
|
||||||
|
.field("value", "")
|
||||||
|
.field("value", "")
|
||||||
|
.field("op", "and")
|
||||||
|
.field("op", "")
|
||||||
|
.field("op", "")
|
||||||
|
.endMap();
|
||||||
|
HttpHeaders httpHeaders = new HttpHeaders();
|
||||||
|
httpHeaders.add("content-type", "application/json");
|
||||||
|
HttpRequest httpRequest = org.xbib.net.http.server.netty.HttpRequest.builder()
|
||||||
|
.setMethod(HttpMethod.POST)
|
||||||
|
.setHeaders(httpHeaders)
|
||||||
|
.setBody(ByteBuffer.wrap(builder.build().getBytes(StandardCharsets.UTF_8)))
|
||||||
|
.build();
|
||||||
|
Objects.requireNonNull(httpRequest.getBody());
|
||||||
|
assertEquals("{key=[key3], value=[], op=[]}",
|
||||||
|
httpRequest.asMultiMap().toString());
|
||||||
|
}
|
||||||
|
}
|
|
@ -2,6 +2,7 @@ package org.xbib.net.http.server;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.net.InetSocketAddress;
|
import java.net.InetSocketAddress;
|
||||||
|
import java.nio.CharBuffer;
|
||||||
import java.nio.charset.MalformedInputException;
|
import java.nio.charset.MalformedInputException;
|
||||||
import java.nio.charset.StandardCharsets;
|
import java.nio.charset.StandardCharsets;
|
||||||
import java.nio.charset.UnmappableCharacterException;
|
import java.nio.charset.UnmappableCharacterException;
|
||||||
|
@ -184,12 +185,16 @@ public abstract class BaseHttpRequest implements HttpRequest {
|
||||||
@SuppressWarnings("unchecked")
|
@SuppressWarnings("unchecked")
|
||||||
@Override
|
@Override
|
||||||
public MultiMap<String, Object> asMultiMap() {
|
public MultiMap<String, Object> asMultiMap() {
|
||||||
PercentDecoder percentDecoder = new PercentDecoder();
|
|
||||||
MultiMap<String, Object> multiMap = new ParameterMap();
|
MultiMap<String, Object> multiMap = new ParameterMap();
|
||||||
|
Parameter parameter = getParameter();
|
||||||
|
try {
|
||||||
|
PercentDecoder percentDecoder = new PercentDecoder();
|
||||||
String contentType = getHeaders().get(HttpHeaderNames.CONTENT_TYPE);
|
String contentType = getHeaders().get(HttpHeaderNames.CONTENT_TYPE);
|
||||||
if (getMethod() == HttpMethod.POST &&
|
if (getMethod() == HttpMethod.POST &&
|
||||||
contentType != null && contentType.contains(HttpHeaderValues.APPLICATION_JSON)) {
|
contentType != null && contentType.contains(HttpHeaderValues.APPLICATION_JSON)) {
|
||||||
String bodyAsChars = getBodyAsChars(StandardCharsets.UTF_8).toString();
|
CharBuffer charBuffer = builder.getBodyAsChars(StandardCharsets.UTF_8);
|
||||||
|
if (charBuffer != null) {
|
||||||
|
String bodyAsChars = charBuffer.toString();
|
||||||
Map<String, Object> map = Json.toMap(bodyAsChars);
|
Map<String, Object> map = Json.toMap(bodyAsChars);
|
||||||
for (Map.Entry<String, Object> entry : map.entrySet()) {
|
for (Map.Entry<String, Object> entry : map.entrySet()) {
|
||||||
if (entry.getValue() instanceof Iterable) {
|
if (entry.getValue() instanceof Iterable) {
|
||||||
|
@ -198,20 +203,27 @@ public abstract class BaseHttpRequest implements HttpRequest {
|
||||||
multiMap.put(entry.getKey(), entry.getValue());
|
multiMap.put(entry.getKey(), entry.getValue());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
logger.log(Level.WARNING, "body is null in POST request where json is declared");
|
||||||
}
|
}
|
||||||
try {
|
} else {
|
||||||
toMultiMapEntry(getParameter().get(Parameter.Domain.PATH),
|
if (parameter != null) {
|
||||||
|
toMultiMapEntry(parameter.get(Parameter.Domain.FORM),
|
||||||
|
percentDecoder,
|
||||||
|
HttpHeaderValues.APPLICATION_X_WWW_FORM_URLENCODED.equals(contentType),
|
||||||
|
multiMap);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (parameter != null) {
|
||||||
|
toMultiMapEntry(parameter.get(Parameter.Domain.PATH),
|
||||||
percentDecoder,
|
percentDecoder,
|
||||||
false,
|
false,
|
||||||
multiMap);
|
multiMap);
|
||||||
toMultiMapEntry(getParameter().get(Parameter.Domain.FORM),
|
toMultiMapEntry(parameter.get(Parameter.Domain.QUERY),
|
||||||
percentDecoder,
|
|
||||||
HttpHeaderValues.APPLICATION_X_WWW_FORM_URLENCODED.equals(contentType),
|
|
||||||
multiMap);
|
|
||||||
toMultiMapEntry(getParameter().get(Parameter.Domain.QUERY),
|
|
||||||
percentDecoder,
|
percentDecoder,
|
||||||
HttpHeaderValues.APPLICATION_X_WWW_FORM_URLENCODED.equals(contentType),
|
HttpHeaderValues.APPLICATION_X_WWW_FORM_URLENCODED.equals(contentType),
|
||||||
multiMap);
|
multiMap);
|
||||||
|
}
|
||||||
} catch (ParameterException e) {
|
} catch (ParameterException e) {
|
||||||
logger.log(Level.WARNING, e.getMessage(), ExceptionFormatter.format(e));
|
logger.log(Level.WARNING, e.getMessage(), ExceptionFormatter.format(e));
|
||||||
}
|
}
|
||||||
|
@ -223,23 +235,29 @@ public abstract class BaseHttpRequest implements HttpRequest {
|
||||||
PercentDecoder percentDecoder,
|
PercentDecoder percentDecoder,
|
||||||
boolean isFormEncoded,
|
boolean isFormEncoded,
|
||||||
MultiMap<String, Object> multiMap) {
|
MultiMap<String, Object> multiMap) {
|
||||||
|
if (parameter == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
for (Pair<String, Object> entry : parameter) {
|
for (Pair<String, Object> entry : parameter) {
|
||||||
try {
|
try {
|
||||||
List<Object> list;
|
List<Object> list;
|
||||||
Object value = entry.getValue();
|
Object object = entry.getValue();
|
||||||
if (value instanceof List) {
|
if (object instanceof List) {
|
||||||
list = (List<Object>) value;
|
list = (List<Object>) object;
|
||||||
} else if (value != null) {
|
} else if (object != null) {
|
||||||
list = List.of(value);
|
list = List.of(object);
|
||||||
} else {
|
} else {
|
||||||
list = List.of();
|
list = List.of();
|
||||||
}
|
}
|
||||||
for (Object object : list) {
|
for (Object o : list) {
|
||||||
String string = object.toString();
|
String string = o.toString();
|
||||||
if (isFormEncoded) {
|
if (isFormEncoded) {
|
||||||
string = string.replace('+', ' ');
|
string = string.replace('+', ' ');
|
||||||
}
|
}
|
||||||
multiMap.put(entry.getKey(), percentDecoder.decode(string));
|
String name = entry.getKey();
|
||||||
|
String value = percentDecoder.decode(string);
|
||||||
|
logger.log(Level.INFO, "name = " + name + " value = " + value);
|
||||||
|
multiMap.put(name, value);
|
||||||
}
|
}
|
||||||
} catch (MalformedInputException | UnmappableCharacterException e) {
|
} catch (MalformedInputException | UnmappableCharacterException e) {
|
||||||
logger.log(Level.WARNING, "unable to percent decode parameter: " +
|
logger.log(Level.WARNING, "unable to percent decode parameter: " +
|
||||||
|
@ -255,7 +273,7 @@ public abstract class BaseHttpRequest implements HttpRequest {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected Collection<Object> newValues() {
|
protected Collection<Object> newValues() {
|
||||||
// keep values with multiple occurences
|
// keep values with multiple occurrences
|
||||||
return TinyList.builder();
|
return TinyList.builder();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,5 @@
|
||||||
package org.xbib.net.http.server;
|
package org.xbib.net.http.server;
|
||||||
|
|
||||||
import java.net.InetAddress;
|
|
||||||
import java.net.InetSocketAddress;
|
import java.net.InetSocketAddress;
|
||||||
import java.nio.ByteBuffer;
|
import java.nio.ByteBuffer;
|
||||||
import java.nio.CharBuffer;
|
import java.nio.CharBuffer;
|
||||||
|
@ -57,6 +56,8 @@ public abstract class BaseHttpRequestBuilder implements HttpRequestBuilder {
|
||||||
|
|
||||||
protected ByteBuffer byteBuffer;
|
protected ByteBuffer byteBuffer;
|
||||||
|
|
||||||
|
protected CharBuffer charBuffer;
|
||||||
|
|
||||||
protected boolean done;
|
protected boolean done;
|
||||||
|
|
||||||
protected List<Message> messages;
|
protected List<Message> messages;
|
||||||
|
@ -194,6 +195,13 @@ public abstract class BaseHttpRequestBuilder implements HttpRequestBuilder {
|
||||||
return byteBuffer;
|
return byteBuffer;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public CharBuffer getBodyAsChars(Charset charset) {
|
||||||
|
if (charBuffer == null) {
|
||||||
|
charBuffer = byteBuffer != null ? charset.decode(byteBuffer) : null;
|
||||||
|
}
|
||||||
|
return charBuffer;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public BaseHttpRequestBuilder setBaseURL(URL baseURL) {
|
public BaseHttpRequestBuilder setBaseURL(URL baseURL) {
|
||||||
if (done) {
|
if (done) {
|
||||||
|
@ -259,11 +267,6 @@ public abstract class BaseHttpRequestBuilder implements HttpRequestBuilder {
|
||||||
return requestPath;
|
return requestPath;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public CharBuffer getBodyAsChars(Charset charset) {
|
|
||||||
return byteBuffer != null ? charset.decode(byteBuffer) : null;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public BaseHttpRequestBuilder setParameter(Parameter parameter) {
|
public BaseHttpRequestBuilder setParameter(Parameter parameter) {
|
||||||
if (done) {
|
if (done) {
|
||||||
|
|
Loading…
Reference in a new issue