switch to HTTP parameter map
This commit is contained in:
parent
d42f928113
commit
97d6f4dc6a
11 changed files with 711 additions and 212 deletions
|
@ -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
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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<String, SortedSet<String>> {
|
||||
public class HttpParameters extends /*LinkedHashSetMultiMap<String, String>*/ 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<String, String> 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<String, SortedSet<String>> {
|
|||
|
||||
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<String> put(String key, SortedSet<String> value) {
|
||||
return map.put(key, value);
|
||||
public CharSequence getContentType() {
|
||||
return contentType;
|
||||
}
|
||||
|
||||
@Override
|
||||
public SortedSet<String> get(Object key) {
|
||||
return map.get(key);
|
||||
public Charset getEncoding() {
|
||||
return encoding;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void putAll(Map<? extends String, ? extends SortedSet<String>> 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<String> 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<String> remove(Object key) {
|
||||
return map.remove(key);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Set<String> keySet() {
|
||||
return map.keySet();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Collection<SortedSet<String>> values() {
|
||||
return map.values();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Set<Entry<String, SortedSet<String>>> entrySet() {
|
||||
return map.entrySet();
|
||||
}
|
||||
|
||||
public SortedSet<String> put(String key, SortedSet<String> values, boolean percentEncode) {
|
||||
if (percentEncode) {
|
||||
public Collection<String> put(String key, Collection<String> values, boolean percentEncode) {
|
||||
remove(key);
|
||||
for (String v : values) {
|
||||
add(key, v, percentEncode);
|
||||
}
|
||||
return get(key);
|
||||
} else {
|
||||
return map.put(key, values);
|
||||
}
|
||||
return getAll(key);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -167,14 +91,26 @@ public class HttpParameters implements Map<String, SortedSet<String>> {
|
|||
* @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<String, SortedSet<String>> {
|
|||
* 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<String> 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<String, SortedSet<String>> {
|
|||
}
|
||||
}
|
||||
|
||||
public void addAll(Map<? extends String, ? extends SortedSet<String>> 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<String, List<String>>}.
|
||||
*
|
||||
|
@ -240,53 +145,20 @@ public class HttpParameters implements Map<String, SortedSet<String>> {
|
|||
*/
|
||||
public void addMap(Map<String, List<String>> m) {
|
||||
for (String key : m.keySet()) {
|
||||
SortedSet<String> vals = get(key);
|
||||
Collection<String> 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<String> 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<String> 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<String> 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<String, SortedSet<String>> {
|
|||
public String getAsQueryString(String key, boolean percentEncode)
|
||||
throws MalformedInputException, UnmappableCharacterException {
|
||||
String k = percentEncode ? percentEncoder.encode(key) : key;
|
||||
SortedSet<String> values = map.get(k);
|
||||
Collection<String> values = getAll(k);
|
||||
if (values == null) {
|
||||
return k + EQUALS;
|
||||
}
|
||||
|
@ -336,26 +208,4 @@ public class HttpParameters implements Map<String, SortedSet<String>> {
|
|||
}
|
||||
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();
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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 <K> the key type parameter
|
||||
* @param <V> the value type parameter
|
||||
*/
|
||||
abstract class AbstractMultiMap<K, V> implements MultiMap<K, V> {
|
||||
|
||||
private final Map<K, Collection<V>> map;
|
||||
|
||||
AbstractMultiMap() {
|
||||
this(null);
|
||||
}
|
||||
|
||||
private AbstractMultiMap(MultiMap<K, V> 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<K> keySet() {
|
||||
return map.keySet();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean put(K key, V value) {
|
||||
Collection<V> 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<V> values) {
|
||||
if (values == null) {
|
||||
return;
|
||||
}
|
||||
Collection<V> set = map.computeIfAbsent(key, k -> newValues());
|
||||
for (V v : values) {
|
||||
set.add(v);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Collection<V> get(K key) {
|
||||
return map.get(key);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Collection<V> remove(K key) {
|
||||
return map.remove(key);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean remove(K key, V value) {
|
||||
Collection<V> set = map.get(key);
|
||||
return set != null && set.remove(value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void putAll(MultiMap<K, V> map) {
|
||||
if (map != null) {
|
||||
for (K key : map.keySet()) {
|
||||
putAll(key, map.get(key));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<K, Collection<V>> asMap() {
|
||||
return map;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getString(K key) {
|
||||
Collection<V> v = get(key);
|
||||
return v != null ? v.iterator().next().toString() : null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getString(K key, String defaultValue) {
|
||||
Collection<V> collection = get(key);
|
||||
Iterator<V> 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<V> collection = get(key);
|
||||
Iterator<V> 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<V> collection = get(key);
|
||||
Iterator<V> 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<V> newValues();
|
||||
|
||||
abstract Map<K, Collection<V>> newMap();
|
||||
}
|
|
@ -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.Entry<String, String>> map) {
|
||||
clear();
|
||||
for (Map.Entry<String, String> 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<String> 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<String, String> entry : headers.entries()) {
|
||||
add(entry.getKey(), entry.getValue());
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ParameterMap addAll(Map<String, String> map) {
|
||||
for (Map.Entry<String, String> 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<String> 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<String> getAll(final String name) {
|
||||
Objects.requireNonNull(name, "name");
|
||||
LinkedList<String> 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<? super Map.Entry<String, String>> action) {
|
||||
MapEntry e = head.after;
|
||||
while (e != head) {
|
||||
action.accept(e);
|
||||
e = e.after;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<Map.Entry<String, String>> entries() {
|
||||
List<Map.Entry<String, String>> all = new LinkedList<>();
|
||||
MapEntry e = head.after;
|
||||
while (e != head) {
|
||||
all.add(e);
|
||||
e = e.after;
|
||||
}
|
||||
return all;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Iterator<Map.Entry<String, String>> 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<String> names() {
|
||||
List<String> 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<String, String> 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<String, String> {
|
||||
|
||||
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();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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 <K> the key type parameter
|
||||
* @param <V> the value type parameter
|
||||
*/
|
||||
public class LinkedHashSetMultiMap<K, V> extends AbstractMultiMap<K, V> {
|
||||
|
||||
public LinkedHashSetMultiMap() {
|
||||
super();
|
||||
}
|
||||
|
||||
@Override
|
||||
Collection<V> newValues() {
|
||||
return new LinkedHashSet<>();
|
||||
}
|
||||
|
||||
@Override
|
||||
Map<K, Collection<V>> newMap() {
|
||||
return new LinkedHashMap<>();
|
||||
}
|
||||
}
|
|
@ -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 <K> the key type parameter
|
||||
* @param <V> the value type parameter
|
||||
*/
|
||||
public interface MultiMap<K, V> {
|
||||
|
||||
void clear();
|
||||
|
||||
int size();
|
||||
|
||||
boolean isEmpty();
|
||||
|
||||
boolean containsKey(K key);
|
||||
|
||||
Collection<V> get(K key);
|
||||
|
||||
Set<K> keySet();
|
||||
|
||||
boolean put(K key, V value);
|
||||
|
||||
void putAll(K key, Iterable<V> values);
|
||||
|
||||
void putAll(MultiMap<K, V> map);
|
||||
|
||||
Collection<V> remove(K key);
|
||||
|
||||
boolean remove(K key, V value);
|
||||
|
||||
Map<K, Collection<V>> asMap();
|
||||
|
||||
String getString(K key);
|
||||
|
||||
String getString(K key, String defaultValue);
|
||||
|
||||
Integer getInteger(K key, int defaultValue);
|
||||
|
||||
Boolean getBoolean(K key, boolean defaultValue);
|
||||
}
|
|
@ -0,0 +1,44 @@
|
|||
package org.xbib.netty.http.common.util;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
public interface ParameterMap extends Iterable<Map.Entry<String, String>> {
|
||||
|
||||
String get(String name);
|
||||
|
||||
List<String> getAll(String name);
|
||||
|
||||
List<Map.Entry<String, String>> 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<String> names();
|
||||
|
||||
ParameterMap add(String name, String value);
|
||||
|
||||
ParameterMap add(String name, Iterable<String> values);
|
||||
|
||||
ParameterMap addAll(ParameterMap map);
|
||||
|
||||
ParameterMap addAll(Map<String, String> map);
|
||||
|
||||
ParameterMap set(String name, String value);
|
||||
|
||||
ParameterMap set(String name, Iterable<String> values);
|
||||
|
||||
ParameterMap setAll(ParameterMap map);
|
||||
|
||||
ParameterMap remove(String name);
|
||||
|
||||
ParameterMap clear();
|
||||
|
||||
int size();
|
||||
}
|
|
@ -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");
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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();
|
||||
|
|
Loading…
Reference in a new issue