diff --git a/gradle.properties b/gradle.properties index 7b04382..e34fc17 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,6 +1,6 @@ group = org.xbib name = netty-http -version = 4.1.51.3 +version = 4.1.51.4 gradle.wrapper.version = 6.4.1 netty.version = 4.1.51.Final diff --git a/netty-http-client-api/src/main/java/org/xbib/netty/http/client/api/Request.java b/netty-http-client-api/src/main/java/org/xbib/netty/http/client/api/Request.java index 44b125a..51ff36f 100644 --- a/netty-http-client-api/src/main/java/org/xbib/netty/http/client/api/Request.java +++ b/netty-http-client-api/src/main/java/org/xbib/netty/http/client/api/Request.java @@ -609,10 +609,7 @@ public final class Request { if (url != null) { // add our URI parameters to the URL URL.Builder mutator = url.mutator(); - uriParameters.forEach((k, v) -> v.forEach(vv -> { - // no percent encoding - mutator.queryParam(k, vv); - })); + uriParameters.forEach(e -> mutator.queryParam(e.getKey(), e.getValue())); // calling build() performs percent encoding url = mutator.build(); String scheme = url.getScheme(); diff --git a/netty-http-common/src/main/java/org/xbib/netty/http/common/HttpParameters.java b/netty-http-common/src/main/java/org/xbib/netty/http/common/HttpParameters.java index 01913e1..f5efd49 100644 --- a/netty-http-common/src/main/java/org/xbib/netty/http/common/HttpParameters.java +++ b/netty-http-common/src/main/java/org/xbib/netty/http/common/HttpParameters.java @@ -4,9 +4,8 @@ import io.netty.handler.codec.http.HttpHeaderValues; import org.xbib.net.PercentDecoder; import org.xbib.net.PercentEncoder; import org.xbib.net.PercentEncoders; +import org.xbib.netty.http.common.util.CaseInsensitiveParameters; import org.xbib.netty.http.common.util.LimitedSet; -import org.xbib.netty.http.common.util.LimitedTreeMap; - import java.nio.charset.CharacterCodingException; import java.nio.charset.Charset; import java.nio.charset.MalformedInputException; @@ -15,11 +14,8 @@ import java.nio.charset.UnmappableCharacterException; import java.util.ArrayList; import java.util.Collection; import java.util.Iterator; -import java.util.LinkedHashMap; import java.util.List; import java.util.Map; -import java.util.Set; -import java.util.SortedSet; /** * A limited multi-map of HTTP request parameters. Each key references a @@ -31,26 +27,22 @@ import java.util.SortedSet; * being useful for message signing; it's not a general purpose collection class * to handle request parameters. */ -public class HttpParameters implements Map> { +public class HttpParameters extends /*LinkedHashSetMultiMap*/ CaseInsensitiveParameters { private static final String EQUALS = "="; private static final String AMPERSAND = "&"; - private final int maxParam; - private final int sizeLimit; private final int elementSizeLimit; - private final LimitedTreeMap map; - private final PercentEncoder percentEncoder; - private final PercentDecoder percentDecoder; - private final CharSequence contentType; + private final Charset encoding; + public HttpParameters() { this(1024, 1024, 65536, HttpHeaderValues.APPLICATION_X_WWW_FORM_URLENCODED, StandardCharsets.UTF_8); @@ -68,96 +60,28 @@ public class HttpParameters implements Map> { public HttpParameters(int maxParam, int sizeLimit, int elementSizeLimit, CharSequence contentType, Charset charset) { - this.maxParam = maxParam; this.sizeLimit = sizeLimit; this.elementSizeLimit = elementSizeLimit; - this.map = new LimitedTreeMap<>(maxParam); this.percentEncoder = PercentEncoders.getQueryEncoder(charset); - this.percentDecoder = new PercentDecoder(); + PercentDecoder percentDecoder = new PercentDecoder(); this.contentType = contentType; + this.encoding = charset; } - @Override - public SortedSet put(String key, SortedSet value) { - return map.put(key, value); + public CharSequence getContentType() { + return contentType; } - @Override - public SortedSet get(Object key) { - return map.get(key); + public Charset getEncoding() { + return encoding; } - @Override - public void putAll(Map> m) { - map.putAll(m); - } - - @Override - public boolean containsKey(Object key) { - return map.containsKey(key); - } - - @Override - public boolean containsValue(Object value) { - if (value instanceof String) { - for (Set values : map.values()) { - if (values.contains(value)) { - return true; - } - } - } - return false; - } - - @Override - public int size() { - int count = 0; - for (String key : map.keySet()) { - count += map.get(key).size(); - } - return count; - } - - @Override - public boolean isEmpty() { - return map.isEmpty(); - } - - @Override - public void clear() { - map.clear(); - } - - @Override - public SortedSet remove(Object key) { - return map.remove(key); - } - - @Override - public Set keySet() { - return map.keySet(); - } - - @Override - public Collection> values() { - return map.values(); - } - - @Override - public Set>> entrySet() { - return map.entrySet(); - } - - public SortedSet put(String key, SortedSet values, boolean percentEncode) { - if (percentEncode) { - remove(key); - for (String v : values) { - add(key, v, percentEncode); - } - return get(key); - } else { - return map.put(key, values); + public Collection put(String key, Collection values, boolean percentEncode) { + remove(key); + for (String v : values) { + add(key, v, percentEncode); } + return getAll(key); } /** @@ -167,14 +91,26 @@ public class HttpParameters implements Map> { * @param value the parameter value * @return the value */ - public String addRaw(String key, String value) { + public HttpParameters addRaw(String key, String value) { return add(key, value, false); } - public String add(String key, String value) { + public HttpParameters add(String key, String value) { return add(key, value, true); } + /** + * Convenience method to allow for storing null values. {@link #put} doesn't + * allow null values, because that would be ambiguous. + * + * @param key the parameter name + * @param nullString can be anything, but probably... null? + * @return null + */ + public HttpParameters addNull(String key, String nullString) { + return addRaw(key, nullString); + } + /** * Convenience method to add a single value for the parameter specified by * 'key'. @@ -185,36 +121,15 @@ public class HttpParameters implements Map> { * inserted into the map * @return the value */ - private String add(String key, String value, boolean percentEncode) { - String v = null; + private HttpParameters add(String key, String value, boolean percentEncode) { try { String k = percentEncode ? percentEncoder.encode(key) : key; - SortedSet values = map.get(k); - if (values == null) { - values = new LimitedSet<>(sizeLimit, elementSizeLimit); - map.put(k, values); - } - - if (value != null) { - v = percentEncode ? percentEncoder.encode(value) : value; - values.add(v); - } + String v = percentEncode ? percentEncoder.encode(value) : value; + super.add(k, v); } catch (CharacterCodingException e) { throw new IllegalArgumentException(e); } - return v; - } - - /** - * Convenience method to allow for storing null values. {@link #put} doesn't - * allow null values, because that would be ambiguous. - * - * @param key the parameter name - * @param nullString can be anything, but probably... null? - * @return null - */ - public String addNull(String key, String nullString) { - return addRaw(key, nullString); + return this; } public void addAll(String[] keyValuePairs, boolean percentEncode) { @@ -223,16 +138,6 @@ public class HttpParameters implements Map> { } } - public void addAll(Map> m, boolean percentEncode) { - if (percentEncode) { - for (String key : m.keySet()) { - put(key, m.get(key), true); - } - } else { - map.putAll(m); - } - } - /** * Convenience method to merge a {@code Map>}. * @@ -240,53 +145,20 @@ public class HttpParameters implements Map> { */ public void addMap(Map> m) { for (String key : m.keySet()) { - SortedSet vals = get(key); + Collection vals = getAll(key); if (vals == null) { vals = new LimitedSet<>(sizeLimit, elementSizeLimit); - put(key, vals); + for (String v : vals) { + super.add(key, v); + } } vals.addAll(m.get(key)); } } - public String getFirst(String key) { - SortedSet values = map.get(key); - if (values == null || values.isEmpty()) { - return null; - } - return values.first(); - } - - /** - * Returns the first value from the set of all values for the given - * parameter name. If the key passed to this method contains special - * characters, you must first percent encode it, otherwise the lookup will fail - * (that's because upon storing values in this map, keys get - * percent-encoded). - * - * @param key the parameter name (must be percent encoded if it contains unsafe - * characters!) - * @return the first value found for this parameter - * @throws MalformedInputException if input is malformed - * @throws UnmappableCharacterException if characters are unmappable - */ - public String getFirstDecoded(String key) - throws MalformedInputException, UnmappableCharacterException { - SortedSet values = map.get(key); - if (values == null || values.isEmpty()) { - return null; - } - String value = values.first(); - return percentDecoder.decode(value); - } - - public CharSequence getContentType() { - return contentType; - } - public String getAsQueryString(boolean percentEncode) throws MalformedInputException, UnmappableCharacterException { List list = new ArrayList<>(); - for (String key : keySet()) { + for (String key : super.names()) { list.add(getAsQueryString(key, percentEncode)); } return String.join(AMPERSAND, list); @@ -320,7 +192,7 @@ public class HttpParameters implements Map> { public String getAsQueryString(String key, boolean percentEncode) throws MalformedInputException, UnmappableCharacterException { String k = percentEncode ? percentEncoder.encode(key) : key; - SortedSet values = map.get(k); + Collection values = getAll(k); if (values == null) { return k + EQUALS; } @@ -336,26 +208,4 @@ public class HttpParameters implements Map> { } return sb.toString(); } - - public String getAsHeaderElement(String key) { - String value = getFirst(key); - if (value == null) { - return null; - } - return key + "=\"" + value + "\""; - } - - public HttpParameters getOAuthParameters() { - HttpParameters oauthParams = - new HttpParameters(maxParam, sizeLimit, elementSizeLimit, contentType, StandardCharsets.UTF_8); - entrySet().stream().filter(entry -> entry.getKey().startsWith("oauth_") || entry.getKey().startsWith("x_oauth_")) - .forEach(entry -> oauthParams.put(entry.getKey(), entry.getValue())); - return oauthParams; - } - - @Override - public String toString() { - return new LinkedHashMap<>(this).toString(); - } - } diff --git a/netty-http-common/src/main/java/org/xbib/netty/http/common/util/AbstractMultiMap.java b/netty-http-common/src/main/java/org/xbib/netty/http/common/util/AbstractMultiMap.java new file mode 100644 index 0000000..b950be2 --- /dev/null +++ b/netty-http-common/src/main/java/org/xbib/netty/http/common/util/AbstractMultiMap.java @@ -0,0 +1,157 @@ +package org.xbib.netty.http.common.util; + +import java.util.Collection; +import java.util.Iterator; +import java.util.Map; +import java.util.Set; + +/** + * Abstract multi map. + * + * @param the key type parameter + * @param the value type parameter + */ +abstract class AbstractMultiMap implements MultiMap { + + private final Map> map; + + AbstractMultiMap() { + this(null); + } + + private AbstractMultiMap(MultiMap map) { + this.map = newMap(); + if (map != null) { + putAll(map); + } + } + + @Override + public int size() { + return map.size(); + } + + @Override + public void clear() { + map.clear(); + } + + @Override + public boolean isEmpty() { + return map.isEmpty(); + } + + @Override + public boolean containsKey(K key) { + return map.containsKey(key); + } + + @Override + public Set keySet() { + return map.keySet(); + } + + @Override + public boolean put(K key, V value) { + Collection set = map.get(key); + if (set == null) { + set = newValues(); + set.add(value); + map.put(key, set); + return true; + } else { + set.add(value); + return false; + } + } + + @Override + public void putAll(K key, Iterable values) { + if (values == null) { + return; + } + Collection set = map.computeIfAbsent(key, k -> newValues()); + for (V v : values) { + set.add(v); + } + } + + @Override + public Collection get(K key) { + return map.get(key); + } + + @Override + public Collection remove(K key) { + return map.remove(key); + } + + @Override + public boolean remove(K key, V value) { + Collection set = map.get(key); + return set != null && set.remove(value); + } + + @Override + public void putAll(MultiMap map) { + if (map != null) { + for (K key : map.keySet()) { + putAll(key, map.get(key)); + } + } + } + + @Override + public Map> asMap() { + return map; + } + + @Override + public String getString(K key) { + Collection v = get(key); + return v != null ? v.iterator().next().toString() : null; + } + + @Override + public String getString(K key, String defaultValue) { + Collection collection = get(key); + Iterator iterator = collection != null ? collection.iterator() : null; + V v = iterator != null ? iterator.next() : null; + return v != null ? v.toString() : defaultValue; + } + + @Override + public Integer getInteger(K key, int defaultValue) { + Collection collection = get(key); + Iterator iterator = collection != null ? collection.iterator() : null; + V v = iterator != null ? iterator.next() : null; + return v != null ? Integer.parseInt(v.toString()) : defaultValue; + } + + @Override + public Boolean getBoolean(K key, boolean defaultValue) { + Collection collection = get(key); + Iterator iterator = collection != null ? collection.iterator() : null; + V v = iterator != null ? iterator.next() : null; + return v != null ? Boolean.parseBoolean(v.toString()) : defaultValue; + } + + @Override + public boolean equals(Object obj) { + return obj instanceof AbstractMultiMap && map.equals(((AbstractMultiMap) obj).map); + } + + @Override + public int hashCode() { + return map.hashCode(); + } + + @Override + public String toString() { + return map.toString(); + } + + abstract Collection newValues(); + + abstract Map> newMap(); +} diff --git a/netty-http-common/src/main/java/org/xbib/netty/http/common/util/CaseInsensitiveParameters.java b/netty-http-common/src/main/java/org/xbib/netty/http/common/util/CaseInsensitiveParameters.java new file mode 100644 index 0000000..49878b9 --- /dev/null +++ b/netty-http-common/src/main/java/org/xbib/netty/http/common/util/CaseInsensitiveParameters.java @@ -0,0 +1,358 @@ +package org.xbib.netty.http.common.util; + +import java.util.Arrays; +import java.util.Iterator; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.function.Consumer; + +/** + * This multi-map implementation has case insensitive keys + * and can be used to hold some HTTP headers prior to making an HTTP request. + * + */ +public class CaseInsensitiveParameters implements ParameterMap { + + private static final int BUCKET_SIZE = 17; + + private static int hash(String name) { + int h = 0; + for (int i = name.length() - 1; i >= 0; i--) { + char c = name.charAt(i); + if (c >= 'A' && c <= 'Z') { + c += 32; + } + h = 31 * h + c; + } + + if (h > 0) { + return h; + } else if (h == Integer.MIN_VALUE) { + return Integer.MAX_VALUE; + } else { + return -h; + } + } + + private ParameterMap set0(Iterable> map) { + clear(); + for (Map.Entry entry : map) { + add(entry.getKey(), entry.getValue()); + } + return this; + } + + @Override + public ParameterMap setAll(ParameterMap headers) { + return set0(headers); + } + + @Override + public int size() { + return names().size(); + } + + private static boolean eq(String name1, String name2) { + int nameLen = name1.length(); + if (nameLen != name2.length()) { + return false; + } + for (int i = nameLen - 1; i >= 0; i--) { + char c1 = name1.charAt(i); + char c2 = name2.charAt(i); + if (c1 != c2) { + if (c1 >= 'A' && c1 <= 'Z') { + c1 += 32; + } + if (c2 >= 'A' && c2 <= 'Z') { + c2 += 32; + } + if (c1 != c2) { + return false; + } + } + } + return true; + } + + private static int index(int hash) { + return hash % BUCKET_SIZE; + } + + private final MapEntry[] entries = new MapEntry[BUCKET_SIZE]; + + private final MapEntry head = new MapEntry(-1, null, null); + + public CaseInsensitiveParameters() { + head.before = head.after = head; + } + + @Override + public ParameterMap add(final String name, final String strVal) { + int h = hash(name); + int i = index(h); + add0(h, i, name, strVal); + return this; + } + + @Override + public ParameterMap add(String name, Iterable values) { + int h = hash(name); + int i = index(h); + for (String vstr : values) { + add0(h, i, name, vstr); + } + return this; + } + + @Override + public ParameterMap addAll(ParameterMap headers) { + for (Map.Entry entry : headers.entries()) { + add(entry.getKey(), entry.getValue()); + } + return this; + } + + @Override + public ParameterMap addAll(Map map) { + for (Map.Entry entry : map.entrySet()) { + add(entry.getKey(), entry.getValue()); + } + return this; + } + + + @Override + public ParameterMap remove(final String name) { + Objects.requireNonNull(name, "name"); + int h = hash(name); + int i = index(h); + remove0(h, i, name); + return this; + } + + @Override + public ParameterMap set(final String name, final String strVal) { + int h = hash(name); + int i = index(h); + remove0(h, i, name); + add0(h, i, name, strVal); + return this; + } + + @Override + public ParameterMap set(final String name, final Iterable values) { + Objects.requireNonNull(values, "values"); + + int h = hash(name); + int i = index(h); + + remove0(h, i, name); + for (String v : values) { + if (v == null) { + break; + } + add0(h, i, name, v); + } + + return this; + } + + @Override + public ParameterMap clear() { + Arrays.fill(entries, null); + head.before = head.after = head; + return this; + } + + @Override + public String get(final String name) { + Objects.requireNonNull(name, "name"); + + int h = hash(name); + int i = index(h); + MapEntry e = entries[i]; + String value = null; + while (e != null) { + if (e.hash == h && eq(name, e.key)) { + value = e.getValue(); + } + e = e.next; + } + return value; + } + + @Override + public List getAll(final String name) { + Objects.requireNonNull(name, "name"); + LinkedList values = new LinkedList<>(); + int h = hash(name); + int i = index(h); + MapEntry e = entries[i]; + while (e != null) { + if (e.hash == h && eq(name, e.key)) { + values.addFirst(e.getValue()); + } + e = e.next; + } + return values; + } + + @Override + public void forEach(Consumer> action) { + MapEntry e = head.after; + while (e != head) { + action.accept(e); + e = e.after; + } + } + + @Override + public List> entries() { + List> all = new LinkedList<>(); + MapEntry e = head.after; + while (e != head) { + all.add(e); + e = e.after; + } + return all; + } + + @Override + public Iterator> iterator() { + return entries().iterator(); + } + + @Override + public boolean contains(String name) { + return get(name) != null; + } + + @Override + public boolean isEmpty() { + return head == head.after; + } + + @Override + public List names() { + List names = new LinkedList<>(); + MapEntry e = head.after; + while (e != head) { + if (!names.contains(e.getKey())) { + names.add(e.getKey()); + } + e = e.after; + } + names.sort(String.CASE_INSENSITIVE_ORDER); + return names; + } + + @Override + public String toString() { + StringBuilder sb = new StringBuilder(); + for (Map.Entry entry : this) { + sb.append(entry).append('\n'); + } + return sb.toString(); + } + + private void add0(int h, int i, final String name, final String value) { + MapEntry e = entries[i]; + MapEntry newEntry; + entries[i] = newEntry = new MapEntry(h, name, value); + newEntry.next = e; + newEntry.addBefore(head); + } + + private void remove0(int h, int i, String name) { + MapEntry e = entries[i]; + if (e == null) { + return; + } + + for (;;) { + if (e.hash == h && eq(name, e.key)) { + e.remove(); + MapEntry next = e.next; + if (next != null) { + entries[i] = next; + e = next; + } else { + entries[i] = null; + return; + } + } else { + break; + } + } + + for (;;) { + MapEntry next = e.next; + if (next == null) { + break; + } + if (next.hash == h && eq(name, next.key)) { + e.next = next.next; + next.remove(); + } else { + e = next; + } + } + } + + private static final class MapEntry implements Map.Entry { + + final int hash; + + final String key; + + String value; + + MapEntry next; + + MapEntry before, after; + + MapEntry(int hash, String key, String value) { + this.hash = hash; + this.key = key; + this.value = value; + } + + void remove() { + before.after = after; + after.before = before; + } + + void addBefore(MapEntry e) { + after = e; + before = e.before; + before.after = this; + after.before = this; + } + + @Override + public String getKey() { + return key; + } + + @Override + public String getValue() { + return value; + } + + @Override + public String setValue(String value) { + Objects.requireNonNull(value, "value"); + String oldValue = this.value; + this.value = value; + return oldValue; + } + + @Override + public String toString() { + return getKey() + ": " + getValue(); + } + } +} diff --git a/netty-http-common/src/main/java/org/xbib/netty/http/common/util/LinkedHashSetMultiMap.java b/netty-http-common/src/main/java/org/xbib/netty/http/common/util/LinkedHashSetMultiMap.java new file mode 100644 index 0000000..fda5641 --- /dev/null +++ b/netty-http-common/src/main/java/org/xbib/netty/http/common/util/LinkedHashSetMultiMap.java @@ -0,0 +1,29 @@ +package org.xbib.netty.http.common.util; + +import java.util.Collection; +import java.util.LinkedHashMap; +import java.util.LinkedHashSet; +import java.util.Map; + +/** + * Linked multi map. + * + * @param the key type parameter + * @param the value type parameter + */ +public class LinkedHashSetMultiMap extends AbstractMultiMap { + + public LinkedHashSetMultiMap() { + super(); + } + + @Override + Collection newValues() { + return new LinkedHashSet<>(); + } + + @Override + Map> newMap() { + return new LinkedHashMap<>(); + } +} diff --git a/netty-http-common/src/main/java/org/xbib/netty/http/common/util/MultiMap.java b/netty-http-common/src/main/java/org/xbib/netty/http/common/util/MultiMap.java new file mode 100644 index 0000000..b3fadc4 --- /dev/null +++ b/netty-http-common/src/main/java/org/xbib/netty/http/common/util/MultiMap.java @@ -0,0 +1,46 @@ +package org.xbib.netty.http.common.util; + +import java.util.Collection; +import java.util.Map; +import java.util.Set; + +/** + * MultiMap interface. + * + * @param the key type parameter + * @param the value type parameter + */ +public interface MultiMap { + + void clear(); + + int size(); + + boolean isEmpty(); + + boolean containsKey(K key); + + Collection get(K key); + + Set keySet(); + + boolean put(K key, V value); + + void putAll(K key, Iterable values); + + void putAll(MultiMap map); + + Collection remove(K key); + + boolean remove(K key, V value); + + Map> asMap(); + + String getString(K key); + + String getString(K key, String defaultValue); + + Integer getInteger(K key, int defaultValue); + + Boolean getBoolean(K key, boolean defaultValue); +} diff --git a/netty-http-common/src/main/java/org/xbib/netty/http/common/util/ParameterMap.java b/netty-http-common/src/main/java/org/xbib/netty/http/common/util/ParameterMap.java new file mode 100644 index 0000000..1ab72cf --- /dev/null +++ b/netty-http-common/src/main/java/org/xbib/netty/http/common/util/ParameterMap.java @@ -0,0 +1,44 @@ +package org.xbib.netty.http.common.util; + +import java.util.List; +import java.util.Map; + +public interface ParameterMap extends Iterable> { + + String get(String name); + + List getAll(String name); + + List> entries(); + + boolean contains(String name); + + default boolean contains(String name, String value, boolean caseInsensitive) { + return getAll(name).stream() + .anyMatch(val -> caseInsensitive ? val.equalsIgnoreCase(value) : val.equals(value)); + } + + boolean isEmpty(); + + List names(); + + ParameterMap add(String name, String value); + + ParameterMap add(String name, Iterable values); + + ParameterMap addAll(ParameterMap map); + + ParameterMap addAll(Map map); + + ParameterMap set(String name, String value); + + ParameterMap set(String name, Iterable values); + + ParameterMap setAll(ParameterMap map); + + ParameterMap remove(String name); + + ParameterMap clear(); + + int size(); +} diff --git a/netty-http-common/src/test/java/org/xbib/netty/http/common/test/HttpParametersTest.java b/netty-http-common/src/test/java/org/xbib/netty/http/common/test/HttpParametersTest.java index b36f5b6..0a608e9 100644 --- a/netty-http-common/src/test/java/org/xbib/netty/http/common/test/HttpParametersTest.java +++ b/netty-http-common/src/test/java/org/xbib/netty/http/common/test/HttpParametersTest.java @@ -11,13 +11,32 @@ import static org.junit.jupiter.api.Assertions.assertEquals; class HttpParametersTest { @Test - void testParameters() throws MalformedInputException, UnmappableCharacterException { + void testSimpleParameter() throws MalformedInputException, UnmappableCharacterException { HttpParameters httpParameters = new HttpParameters(); httpParameters.addRaw("a", "b"); String query = httpParameters.getAsQueryString(false); assertEquals("a=b", query); } + @Test + void testSimpleParameters() throws MalformedInputException, UnmappableCharacterException { + HttpParameters httpParameters = new HttpParameters(); + httpParameters.addRaw("a", "b"); + httpParameters.addRaw("c", "d"); + String query = httpParameters.getAsQueryString(false); + assertEquals("a=b&c=d", query); + } + + @Test + void testMultiParameters() throws MalformedInputException, UnmappableCharacterException { + HttpParameters httpParameters = new HttpParameters(); + httpParameters.addRaw("a", "b"); + httpParameters.addRaw("a", "c"); + httpParameters.addRaw("a", "d"); + String query = httpParameters.getAsQueryString(false); + assertEquals("a=b&a=c&a=d", query); + } + @Test void testUtf8() throws MalformedInputException, UnmappableCharacterException { HttpParameters httpParameters = new HttpParameters("text/plain; charset=utf-8"); diff --git a/netty-http-server/src/test/java/org/xbib/netty/http/server/test/http1/PostTest.java b/netty-http-server/src/test/java/org/xbib/netty/http/server/test/http1/PostTest.java index 7c7f3fc..7d3b1c0 100644 --- a/netty-http-server/src/test/java/org/xbib/netty/http/server/test/http1/PostTest.java +++ b/netty-http-server/src/test/java/org/xbib/netty/http/server/test/http1/PostTest.java @@ -11,7 +11,6 @@ import org.xbib.netty.http.common.HttpParameters; import org.xbib.netty.http.common.HttpResponse; import org.xbib.netty.http.server.HttpServerDomain; import org.xbib.netty.http.server.Server; -import org.xbib.netty.http.server.api.ServerResponse; import org.xbib.netty.http.server.test.NettyHttpTestExtension; import java.nio.charset.StandardCharsets; import java.util.concurrent.atomic.AtomicBoolean; @@ -36,10 +35,10 @@ class PostTest { .singleEndpoint("/post", "/**", (req, resp) -> { HttpParameters parameters = req.getParameters(); logger.log(Level.INFO, "got request " + parameters.toString() + ", sending OK"); - if ("Hello World".equals(parameters.getFirst("withspace"))) { + if ("Hello World".equals(parameters.get("withspace"))) { success2.set(true); } - if ("Jörg".equals(parameters.getFirst("name"))) { + if ("Jörg".equals(parameters.get("name"))) { success3.set(true); } resp.getBuilder().setStatus(HttpResponseStatus.OK).build().flush(); @@ -86,10 +85,10 @@ class PostTest { .singleEndpoint("/post", "/**", (req, resp) -> { HttpParameters parameters = req.getParameters(); logger.log(Level.INFO, "got request " + parameters.toString() + ", sending OK"); - if ("Hello World".equals(parameters.getFirst("withspace"))) { + if ("Hello World".equals(parameters.get("withspace"))) { success2.set(true); } - if ("Jörg".equals(parameters.getFirst("name"))) { + if ("Jörg".equals(parameters.get("name"))) { success3.set(true); } resp.getBuilder().setStatus(HttpResponseStatus.OK).build().flush(); @@ -137,13 +136,13 @@ class PostTest { .singleEndpoint("/post", "/**", (req, resp) -> { HttpParameters parameters = req.getParameters(); logger.log(Level.INFO, "got request " + parameters.toString() + ", sending OK"); - if ("Hello World".equals(parameters.getFirst("withplus"))) { + if ("Hello World".equals(parameters.get("withplus"))) { success2.set(true); } - if ("Jörg".equals(parameters.getFirst("name"))) { + if ("Jörg".equals(parameters.get("name"))) { success3.set(true); } - if ("my value".equals(parameters.getFirst("my param"))) { + if ("my value".equals(parameters.get("my param"))) { success4.set(true); } resp.getBuilder().setStatus(HttpResponseStatus.OK).build().flush(); @@ -194,13 +193,13 @@ class PostTest { .singleEndpoint("/post", "/**", (req, resp) -> { HttpParameters parameters = req.getParameters(); logger.log(Level.INFO, "got request " + parameters.toString() + ", sending OK"); - if ("Hello World".equals(parameters.getFirst("withoutplus"))) { + if ("Hello World".equals(parameters.get("withoutplus"))) { success2.set(true); } - if ("Jörg".equals(parameters.getFirst("name"))) { + if ("Jörg".equals(parameters.get("name"))) { success3.set(true); } - if ("my value".equals(parameters.getFirst("my param"))) { + if ("my value".equals(parameters.get("my param"))) { success4.set(true); } resp.getBuilder().setStatus(HttpResponseStatus.OK).build().flush(); @@ -251,10 +250,10 @@ class PostTest { .singleEndpoint("/post", "/**", (req, resp) -> { HttpParameters parameters = req.getParameters(); logger.log(Level.INFO, "got request " + parameters.toString() + ", sending OK"); - if ("myÿvalue".equals(parameters.getFirst("my param"))) { + if ("myÿvalue".equals(parameters.get("my param"))) { success1.set(true); } - if ("bÿc".equals(parameters.getFirst("a"))) { + if ("bÿc".equals(parameters.get("a"))) { success2.set(true); } resp.getBuilder().setStatus(HttpResponseStatus.OK).build().flush(); diff --git a/netty-http-server/src/test/java/org/xbib/netty/http/server/test/http2/PostTest.java b/netty-http-server/src/test/java/org/xbib/netty/http/server/test/http2/PostTest.java index b32d338..b27f281 100644 --- a/netty-http-server/src/test/java/org/xbib/netty/http/server/test/http2/PostTest.java +++ b/netty-http-server/src/test/java/org/xbib/netty/http/server/test/http2/PostTest.java @@ -36,10 +36,10 @@ class PostTest { .singleEndpoint("/post", "/**", (req, resp) -> { HttpParameters parameters = req.getParameters(); logger.log(Level.INFO, "got request " + parameters.toString() + " , sending, OK"); - if ("Hello World".equals(parameters.getFirst("withspace"))) { + if ("Hello World".equals(parameters.get("withspace"))) { success2.set(true); } - if ("Jörg".equals(parameters.getFirst("name"))) { + if ("Jörg".equals(parameters.get("name"))) { success3.set(true); } resp.getBuilder().setStatus(HttpResponseStatus.OK).build().flush(); @@ -86,10 +86,10 @@ class PostTest { .singleEndpoint("/post", "/**", (req, resp) -> { HttpParameters parameters = req.getParameters(); logger.log(Level.INFO, "got request " + parameters.toString() + " , sending, OK"); - if ("Hello World".equals(parameters.getFirst("withspace"))) { + if ("Hello World".equals(parameters.get("withspace"))) { success2.set(true); } - if ("Jörg".equals(parameters.getFirst("name"))) { + if ("Jörg".equals(parameters.get("name"))) { success3.set(true); } resp.getBuilder().setStatus(HttpResponseStatus.OK).build().flush();