mirror of https://github.com/grpc/grpc-java.git
core: change Metadata internals to avoid garbage creation
Before: Benchmark (headerCount) Mode Cnt Score Error Units InboundHeadersBenchmark.defaultHeaders_clientHandler N/A avgt 10 240.879 ± 4.903 ns/op InboundHeadersBenchmark.defaultHeaders_serverHandler N/A avgt 10 882.354 ± 16.177 ns/op InboundHeadersBenchmark.grpcHeaders_clientHandler N/A avgt 10 208.068 ± 5.380 ns/op InboundHeadersBenchmark.grpcHeaders_serverHandler N/A avgt 10 477.604 ± 8.200 ns/op OutboundHeadersBenchmark.convertClientHeaders 1 sample 234233 125.232 ± 11.903 ns/op OutboundHeadersBenchmark.convertClientHeaders 5 sample 344367 264.343 ± 18.318 ns/op OutboundHeadersBenchmark.convertClientHeaders 10 sample 392273 439.640 ± 2.589 ns/op OutboundHeadersBenchmark.convertClientHeaders 20 sample 221506 855.115 ± 38.899 ns/op OutboundHeadersBenchmark.convertServerHeaders 1 sample 253676 111.941 ± 2.742 ns/op OutboundHeadersBenchmark.convertServerHeaders 5 sample 368499 248.255 ± 2.601 ns/op OutboundHeadersBenchmark.convertServerHeaders 10 sample 390015 439.651 ± 11.040 ns/op OutboundHeadersBenchmark.convertServerHeaders 20 sample 221807 840.435 ± 21.667 ns/op OutboundHeadersBenchmark.encodeClientHeaders 1 sample 230139 432.866 ± 25.503 ns/op OutboundHeadersBenchmark.encodeClientHeaders 5 sample 226901 765.095 ± 19.969 ns/op OutboundHeadersBenchmark.encodeClientHeaders 10 sample 260495 1268.239 ± 21.850 ns/op OutboundHeadersBenchmark.encodeClientHeaders 20 sample 311526 2059.973 ± 23.503 ns/op After: Benchmark (headerCount) Mode Cnt Score Error Units InboundHeadersBenchmark.defaultHeaders_clientHandler N/A avgt 10 104.317 ± 1.973 ns/op InboundHeadersBenchmark.defaultHeaders_serverHandler N/A avgt 10 395.666 ± 11.056 ns/op InboundHeadersBenchmark.grpcHeaders_clientHandler N/A avgt 10 64.147 ± 4.076 ns/op InboundHeadersBenchmark.grpcHeaders_serverHandler N/A avgt 10 228.299 ± 2.874 ns/op OutboundHeadersBenchmark.convertClientHeaders 1 sample 252451 102.718 ± 2.714 ns/op OutboundHeadersBenchmark.convertClientHeaders 5 sample 239976 225.812 ± 38.824 ns/op OutboundHeadersBenchmark.convertClientHeaders 10 sample 258119 364.475 ± 57.217 ns/op OutboundHeadersBenchmark.convertClientHeaders 20 sample 260138 676.950 ± 36.243 ns/op OutboundHeadersBenchmark.convertServerHeaders 1 sample 276064 105.371 ± 1.859 ns/op OutboundHeadersBenchmark.convertServerHeaders 5 sample 255128 190.970 ± 16.475 ns/op OutboundHeadersBenchmark.convertServerHeaders 10 sample 272923 366.769 ± 28.204 ns/op OutboundHeadersBenchmark.convertServerHeaders 20 sample 264797 641.961 ± 18.879 ns/op OutboundHeadersBenchmark.encodeClientHeaders 1 sample 226078 425.262 ± 3.481 ns/op OutboundHeadersBenchmark.encodeClientHeaders 5 sample 253606 675.488 ± 26.001 ns/op OutboundHeadersBenchmark.encodeClientHeaders 10 sample 286556 1157.014 ± 12.923 ns/op OutboundHeadersBenchmark.encodeClientHeaders 20 sample 345649 1874.806 ± 36.227 ns/op
This commit is contained in:
parent
16c07ba434
commit
141eed5ed0
|
|
@ -71,10 +71,27 @@ public final class InternalMetadata {
|
|||
/**
|
||||
* Copy of StandardCharsets, which is only available on Java 1.7 and above.
|
||||
*/
|
||||
@Internal
|
||||
public static final Charset US_ASCII = Charset.forName("US-ASCII");
|
||||
|
||||
@Internal
|
||||
public static <T> Key<T> keyOf(String name, TrustedAsciiMarshaller<T> marshaller) {
|
||||
return Metadata.Key.of(name, marshaller);
|
||||
}
|
||||
|
||||
|
||||
@Internal
|
||||
public static Metadata newMetadata(byte[]... binaryValues) {
|
||||
return new Metadata(binaryValues);
|
||||
}
|
||||
|
||||
@Internal
|
||||
public static Metadata newMetadata(int usedNames, byte[]... binaryValues) {
|
||||
return new Metadata(usedNames, binaryValues);
|
||||
}
|
||||
|
||||
@Internal
|
||||
public static byte[][] serialize(Metadata md) {
|
||||
return md.serialize();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -37,28 +37,32 @@ import static com.google.common.base.Preconditions.checkNotNull;
|
|||
|
||||
import com.google.common.annotations.VisibleForTesting;
|
||||
import com.google.common.base.Preconditions;
|
||||
import com.google.common.io.BaseEncoding;
|
||||
|
||||
import io.grpc.InternalMetadata.TrustedAsciiMarshaller;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.util.Arrays;
|
||||
import java.util.BitSet;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.Iterator;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.Map;
|
||||
import java.util.NoSuchElementException;
|
||||
import java.util.Set;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
import javax.annotation.concurrent.NotThreadSafe;
|
||||
|
||||
/**
|
||||
* Provides access to read and write metadata values to be exchanged during a call.
|
||||
* <p>
|
||||
* This class is not thread safe, implementations should ensure that header reads and writes
|
||||
* do not occur in multiple threads concurrently.
|
||||
* </p>
|
||||
*
|
||||
* <p>This class is not thread safe, implementations should ensure that header reads and writes do
|
||||
* not occur in multiple threads concurrently.
|
||||
*/
|
||||
@NotThreadSafe
|
||||
public final class Metadata {
|
||||
|
|
@ -75,11 +79,11 @@ public final class Metadata {
|
|||
*
|
||||
* <p>This should be used when raw bytes are favored over un-serialized version of object. Can be
|
||||
* helpful in situations where more processing to bytes is needed on application side, avoids
|
||||
* double encoding/decoding.</p>
|
||||
* double encoding/decoding.
|
||||
*
|
||||
* <p>Both {@link BinaryMarshaller#toBytes} and {@link BinaryMarshaller#parseBytes} methods do
|
||||
* not return a copy of the byte array. Do _not_ modify the byte arrays of either the arguments
|
||||
* or return values.</p>
|
||||
* <p>Both {@link BinaryMarshaller#toBytes} and {@link BinaryMarshaller#parseBytes} methods do not
|
||||
* return a copy of the byte array. Do _not_ modify the byte arrays of either the arguments or
|
||||
* return values.
|
||||
*/
|
||||
public static final BinaryMarshaller<byte[]> BINARY_BYTE_MARSHALLER =
|
||||
new BinaryMarshaller<byte[]>() {
|
||||
|
|
@ -116,10 +120,9 @@ public final class Metadata {
|
|||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Simple metadata marshaller that encodes an integer as a signed decimal string.
|
||||
*/
|
||||
static final AsciiMarshaller<Integer> INTEGER_MARSHALLER = new AsciiMarshaller<Integer>() {
|
||||
/** Simple metadata marshaller that encodes an integer as a signed decimal string. */
|
||||
static final AsciiMarshaller<Integer> INTEGER_MARSHALLER =
|
||||
new AsciiMarshaller<Integer>() {
|
||||
|
||||
@Override
|
||||
public String toAsciiString(Integer value) {
|
||||
|
|
@ -132,87 +135,149 @@ public final class Metadata {
|
|||
}
|
||||
};
|
||||
|
||||
/** All value lists can be added to. No value list may be empty. */
|
||||
// Use LinkedHashMap for consistent ordering for tests.
|
||||
private final Map<String, List<MetadataEntry>> store =
|
||||
new LinkedHashMap<String, List<MetadataEntry>>();
|
||||
|
||||
/** The number of headers stored by this metadata. */
|
||||
private int storeCount;
|
||||
|
||||
/**
|
||||
* Constructor called by the transport layer when it receives binary metadata.
|
||||
* Constructor called by the transport layer when it receives binary metadata. Metadata will
|
||||
* mutate the passed in array.
|
||||
*/
|
||||
// TODO(louiscryan): Convert to use ByteString so we can cache transformations
|
||||
@Internal
|
||||
public Metadata(byte[]... binaryValues) {
|
||||
checkArgument(binaryValues.length % 2 == 0,
|
||||
"Odd number of key-value pairs: %s", binaryValues.length);
|
||||
for (int i = 0; i < binaryValues.length; i += 2) {
|
||||
// The transport might provide an array with null values at the end.
|
||||
if (binaryValues[i] == null) {
|
||||
break;
|
||||
}
|
||||
String name = new String(binaryValues[i], US_ASCII);
|
||||
storeAdd(name, new MetadataEntry(name.endsWith(BINARY_HEADER_SUFFIX), binaryValues[i + 1]));
|
||||
}
|
||||
Metadata(byte[]... binaryValues) {
|
||||
this(binaryValues.length / 2, binaryValues);
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructor called by the application layer when it wants to send metadata.
|
||||
* Constructor called by the transport layer when it receives binary metadata. Metadata will
|
||||
* mutate the passed in array.
|
||||
*
|
||||
* @param usedNames the number of
|
||||
*/
|
||||
Metadata(int usedNames, byte[]... binaryValues) {
|
||||
assert (binaryValues.length & 1) == 0 : "Odd number of key-value pairs " + binaryValues.length;
|
||||
size = usedNames;
|
||||
namesAndValues = binaryValues;
|
||||
}
|
||||
|
||||
private byte[][] namesAndValues;
|
||||
// The unscaled number of headers present.
|
||||
private int size;
|
||||
|
||||
private byte[] name(int i) {
|
||||
return namesAndValues[i * 2];
|
||||
}
|
||||
|
||||
private void name(int i, byte[] name) {
|
||||
namesAndValues[i * 2] = name;
|
||||
}
|
||||
|
||||
private byte[] value(int i) {
|
||||
return namesAndValues[i * 2 + 1];
|
||||
}
|
||||
|
||||
private void value(int i, byte[] value) {
|
||||
namesAndValues[i * 2 + 1] = value;
|
||||
}
|
||||
|
||||
private int cap() {
|
||||
return namesAndValues != null ? namesAndValues.length : 0;
|
||||
}
|
||||
|
||||
// The scaled version of size.
|
||||
private int len() {
|
||||
return size * 2;
|
||||
}
|
||||
|
||||
private boolean isEmpty() {
|
||||
/** checks when {@link #namesAndValues} is null or has no elements */
|
||||
return size == 0;
|
||||
}
|
||||
|
||||
/** Constructor called by the application layer when it wants to send metadata. */
|
||||
public Metadata() {}
|
||||
|
||||
private void storeAdd(String name, MetadataEntry value) {
|
||||
List<MetadataEntry> values = store.get(name);
|
||||
if (values == null) {
|
||||
// We expect there to be usually unique header values, so prefer smaller arrays.
|
||||
values = new ArrayList<MetadataEntry>(1);
|
||||
store.put(name, values);
|
||||
}
|
||||
storeCount++;
|
||||
values.add(value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the total number of key-value headers in this metadata, including duplicates.
|
||||
*/
|
||||
/** Returns the total number of key-value headers in this metadata, including duplicates. */
|
||||
@Internal
|
||||
public int headerCount() {
|
||||
return storeCount;
|
||||
return size;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if a value is defined for the given key.
|
||||
*/
|
||||
/** Returns true if a value is defined for the given key. */
|
||||
public boolean containsKey(Key<?> key) {
|
||||
return store.containsKey(key.name());
|
||||
for (int i = 0; i < size; i++) {
|
||||
if (bytesEqual(key.asciiName(), name(i))) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the last metadata entry added with the name 'name' parsed as T.
|
||||
*
|
||||
* @return the parsed metadata entry or null if there are none.
|
||||
*/
|
||||
public <T> T get(Key<T> key) {
|
||||
List<MetadataEntry> values = store.get(key.name());
|
||||
if (values == null) {
|
||||
for (int i = size - 1; i >= 0; i--) {
|
||||
if (bytesEqual(key.asciiName(), name(i))) {
|
||||
return key.parseBytes(value(i));
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
MetadataEntry metadataEntry = values.get(values.size() - 1);
|
||||
return metadataEntry.getParsed(key);
|
||||
|
||||
private final class IterableAt<T> implements Iterable<T> {
|
||||
private final Key<T> key;
|
||||
private int startIdx;
|
||||
|
||||
private IterableAt(Key<T> key, int startIdx) {
|
||||
this.key = key;
|
||||
this.startIdx = startIdx;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Iterator<T> iterator() {
|
||||
return new Iterator<T>() {
|
||||
private boolean hasNext = true;
|
||||
private int idx = startIdx;
|
||||
|
||||
@Override
|
||||
public boolean hasNext() {
|
||||
if (hasNext) {
|
||||
return true;
|
||||
}
|
||||
for (; idx < size; idx++) {
|
||||
if (bytesEqual(key.asciiName(), name(idx))) {
|
||||
hasNext = true;
|
||||
return hasNext;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public T next() {
|
||||
if (hasNext()) {
|
||||
hasNext = false;
|
||||
return key.parseBytes(value(idx++));
|
||||
}
|
||||
throw new NoSuchElementException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void remove() {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns all the metadata entries named 'name', in the order they were received,
|
||||
* parsed as T or null if there are none. The iterator is not guaranteed to be "live." It may or
|
||||
* may not be accurate if Metadata is mutated.
|
||||
* Returns all the metadata entries named 'name', in the order they were received, parsed as T or
|
||||
* null if there are none. The iterator is not guaranteed to be "live." It may or may not be
|
||||
* accurate if Metadata is mutated.
|
||||
*/
|
||||
public <T> Iterable<T> getAll(Key<T> key) {
|
||||
if (containsKey(key)) {
|
||||
/* This is unmodifiable currently, but could be made to support remove() in the future. If
|
||||
* removal support is added, the {@link #storeCount} variable needs to be updated
|
||||
* appropriately. */
|
||||
return new ValueIterable<T>(key, store.get(key.name()));
|
||||
public <T> Iterable<T> getAll(final Key<T> key) {
|
||||
for (int i = 0; i < size; i++) {
|
||||
if (bytesEqual(key.asciiName(), name(i))) {
|
||||
return new IterableAt<T>(key, i);
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
|
@ -222,8 +287,17 @@ public final class Metadata {
|
|||
*
|
||||
* @return unmodifiable Set of keys
|
||||
*/
|
||||
@SuppressWarnings("deprecation") // The String ctor is deprecated, but fast.
|
||||
public Set<String> keys() {
|
||||
return Collections.unmodifiableSet(store.keySet());
|
||||
if (isEmpty()) {
|
||||
return Collections.emptySet();
|
||||
}
|
||||
Set<String> ks = new HashSet<String>(size);
|
||||
for (int i = 0; i < size; i++) {
|
||||
ks.add(new String(name(i), 0 /* hibyte */));
|
||||
}
|
||||
// immutable in case we decide to change the implementation later.
|
||||
return Collections.unmodifiableSet(ks);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -235,7 +309,25 @@ public final class Metadata {
|
|||
public <T> void put(Key<T> key, T value) {
|
||||
Preconditions.checkNotNull(key, "key");
|
||||
Preconditions.checkNotNull(value, "value");
|
||||
storeAdd(key.name, new MetadataEntry(key, value));
|
||||
maybeExpand();
|
||||
name(size, key.asciiName());
|
||||
value(size, key.toBytes(value));
|
||||
size++;
|
||||
}
|
||||
|
||||
private void maybeExpand() {
|
||||
if (len() == 0 || len() == cap()) {
|
||||
expand(Math.max(len() * 2, 8));
|
||||
}
|
||||
}
|
||||
|
||||
// Expands to exactly the desired capacity.
|
||||
private void expand(int newCapacity) {
|
||||
byte[][] newNamesAndValues = new byte[newCapacity][];
|
||||
if (!isEmpty()) {
|
||||
System.arraycopy(namesAndValues, 0, newNamesAndValues, 0, len());
|
||||
}
|
||||
namesAndValues = newNamesAndValues;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -249,32 +341,50 @@ public final class Metadata {
|
|||
public <T> boolean remove(Key<T> key, T value) {
|
||||
Preconditions.checkNotNull(key, "key");
|
||||
Preconditions.checkNotNull(value, "value");
|
||||
List<MetadataEntry> values = store.get(key.name());
|
||||
if (values == null) {
|
||||
return false;
|
||||
}
|
||||
for (int i = 0; i < values.size(); i++) {
|
||||
MetadataEntry entry = values.get(i);
|
||||
if (!value.equals(entry.getParsed(key))) {
|
||||
for (int i = 0; i < size; i++) {
|
||||
if (!bytesEqual(key.asciiName(), name(i))) {
|
||||
continue;
|
||||
}
|
||||
values.remove(i);
|
||||
storeCount--;
|
||||
@SuppressWarnings("unchecked")
|
||||
T stored = key.parseBytes(value(i));
|
||||
if (!value.equals(stored)) {
|
||||
continue;
|
||||
}
|
||||
int writeIdx = i * 2;
|
||||
int readIdx = (i + 1) * 2;
|
||||
int readLen = len() - readIdx;
|
||||
System.arraycopy(namesAndValues, readIdx, namesAndValues, writeIdx, readLen);
|
||||
size -= 1;
|
||||
name(size, null);
|
||||
value(size, null);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove all values for the given key. If there were no values, {@code null} is returned.
|
||||
*/
|
||||
/** Remove all values for the given key. If there were no values, {@code null} is returned. */
|
||||
public <T> Iterable<T> removeAll(Key<T> key) {
|
||||
List<MetadataEntry> values = store.remove(key.name());
|
||||
if (values == null) {
|
||||
if (isEmpty()) {
|
||||
return null;
|
||||
}
|
||||
storeCount -= values.size();
|
||||
return new ValueIterable<T>(key, values);
|
||||
int writeIdx = 0;
|
||||
int readIdx = 0;
|
||||
List<T> ret = null;
|
||||
for (; readIdx < size; readIdx++) {
|
||||
if (bytesEqual(key.asciiName(), name(readIdx))) {
|
||||
ret = ret != null ? ret : new LinkedList<T>();
|
||||
ret.add(key.parseBytes(value(readIdx)));
|
||||
continue;
|
||||
}
|
||||
name(writeIdx, name(readIdx));
|
||||
value(writeIdx, value(readIdx));
|
||||
writeIdx++;
|
||||
}
|
||||
int newSize = writeIdx;
|
||||
// Multiply by two since namesAndValues is interleaved.
|
||||
Arrays.fill(namesAndValues, writeIdx * 2, len(), null);
|
||||
size = newSize;
|
||||
return ret;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -283,8 +393,23 @@ public final class Metadata {
|
|||
*/
|
||||
@ExperimentalApi
|
||||
public <T> void discardAll(Key<T> key) {
|
||||
List<MetadataEntry> removed = store.remove(key.name());
|
||||
storeCount -= removed != null ? removed.size() : 0;
|
||||
if (isEmpty()) {
|
||||
return;
|
||||
}
|
||||
int writeIdx = 0;
|
||||
int readIdx = 0;
|
||||
for (; readIdx < size; readIdx++) {
|
||||
if (bytesEqual(key.asciiName(), name(readIdx))) {
|
||||
continue;
|
||||
}
|
||||
name(writeIdx, name(readIdx));
|
||||
value(writeIdx, value(readIdx));
|
||||
writeIdx++;
|
||||
}
|
||||
int newSize = writeIdx;
|
||||
// Multiply by two since namesAndValues is interleaved.
|
||||
Arrays.fill(namesAndValues, writeIdx * 2, len(), null);
|
||||
size = newSize;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -302,70 +427,76 @@ public final class Metadata {
|
|||
*
|
||||
* <p>This method is intended for transport use only.
|
||||
*/
|
||||
@Internal
|
||||
public byte[][] serialize() {
|
||||
// 2x for keys + values
|
||||
byte[][] serialized = new byte[storeCount * 2][];
|
||||
int i = 0;
|
||||
for (Map.Entry<String, List<MetadataEntry>> storeEntry : store.entrySet()) {
|
||||
// Foreach allocates an iterator per.
|
||||
List<MetadataEntry> values = storeEntry.getValue();
|
||||
for (int k = 0; k < values.size(); k++) {
|
||||
serialized[i++] = values.get(k).key != null
|
||||
? values.get(k).key.asciiName() : storeEntry.getKey().getBytes(US_ASCII);
|
||||
serialized[i++] = values.get(k).getSerialized();
|
||||
}
|
||||
@Nullable
|
||||
byte[][] serialize() {
|
||||
if (len() == cap()) {
|
||||
return namesAndValues;
|
||||
}
|
||||
byte[][] serialized = new byte[len()][];
|
||||
System.arraycopy(namesAndValues, 0, serialized, 0, len());
|
||||
return serialized;
|
||||
}
|
||||
|
||||
/**
|
||||
* Perform a simple merge of two sets of metadata.
|
||||
*/
|
||||
/** Perform a simple merge of two sets of metadata. */
|
||||
public void merge(Metadata other) {
|
||||
Preconditions.checkNotNull(other, "other");
|
||||
for (Map.Entry<String, List<MetadataEntry>> keyEntry : other.store.entrySet()) {
|
||||
for (int i = 0; i < keyEntry.getValue().size(); i++) {
|
||||
// Must copy the MetadataEntries since they are mutated. If the two Metadata objects are
|
||||
// used from different threads it would cause thread-safety issues.
|
||||
storeAdd(keyEntry.getKey(), new MetadataEntry(keyEntry.getValue().get(i)));
|
||||
if (other.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
int remaining = cap() - len();
|
||||
if (isEmpty() || remaining < other.len()) {
|
||||
expand(len() + other.len() - remaining);
|
||||
}
|
||||
System.arraycopy(other.namesAndValues, 0, namesAndValues, len(), other.len());
|
||||
size += other.size;
|
||||
}
|
||||
|
||||
/**
|
||||
* Merge values for the given set of keys into this set of metadata.
|
||||
*/
|
||||
/** Merge values for the given set of keys into this set of metadata. */
|
||||
public void merge(Metadata other, Set<Key<?>> keys) {
|
||||
Preconditions.checkNotNull(other, "other");
|
||||
// Use ByteBuffer for equals and hashCode.
|
||||
Map<ByteBuffer, Key<?>> asciiKeys = new HashMap<ByteBuffer, Key<?>>(keys.size());
|
||||
for (Key<?> key : keys) {
|
||||
List<MetadataEntry> values = other.store.get(key.name());
|
||||
if (values == null) {
|
||||
continue;
|
||||
asciiKeys.put(ByteBuffer.wrap(key.asciiName()), key);
|
||||
}
|
||||
for (int i = 0; i < values.size(); i++) {
|
||||
// Must copy the MetadataEntries since they are mutated. If the two Metadata objects are
|
||||
// used from different threads it would cause thread-safety issues.
|
||||
storeAdd(key.name(), new MetadataEntry(values.get(i)));
|
||||
for (int i = 0; i < other.size; i++) {
|
||||
ByteBuffer wrappedNamed = ByteBuffer.wrap(other.name(i));
|
||||
if (asciiKeys.containsKey(wrappedNamed)) {
|
||||
maybeExpand();
|
||||
name(size, other.name(i));
|
||||
value(size, other.value(i));
|
||||
size++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "Metadata(" + toStringInternal() + ")";
|
||||
StringBuilder sb = new StringBuilder("Metadata(");
|
||||
for (int i = 0; i < size; i++) {
|
||||
if (i != 0) {
|
||||
sb.append(',');
|
||||
}
|
||||
String headerName = new String(name(i), US_ASCII);
|
||||
sb.append(headerName).append('=');
|
||||
if (headerName.endsWith(BINARY_HEADER_SUFFIX)) {
|
||||
sb.append(BaseEncoding.base64().encode(value(i)));
|
||||
} else {
|
||||
String headerValue = new String(value(i), US_ASCII);
|
||||
sb.append(headerValue);
|
||||
}
|
||||
}
|
||||
return sb.append(')').toString();
|
||||
}
|
||||
|
||||
private String toStringInternal() {
|
||||
return store.toString();
|
||||
private boolean bytesEqual(byte[] left, byte[] right) {
|
||||
return Arrays.equals(left, right);
|
||||
}
|
||||
|
||||
/**
|
||||
* Marshaller for metadata values that are serialized into raw binary.
|
||||
*/
|
||||
/** Marshaller for metadata values that are serialized into raw binary. */
|
||||
public interface BinaryMarshaller<T> {
|
||||
/**
|
||||
* Serialize a metadata value to bytes.
|
||||
*
|
||||
* @param value to serialize
|
||||
* @return serialized version of value
|
||||
*/
|
||||
|
|
@ -373,6 +504,7 @@ public final class Metadata {
|
|||
|
||||
/**
|
||||
* Parse a serialized metadata value from bytes.
|
||||
*
|
||||
* @param serialized value of metadata to parse
|
||||
* @return a parsed instance of type T
|
||||
*/
|
||||
|
|
@ -382,9 +514,10 @@ public final class Metadata {
|
|||
/**
|
||||
* Marshaller for metadata values that are serialized into ASCII strings that contain only
|
||||
* following characters:
|
||||
*
|
||||
* <ul>
|
||||
* <li>Space: {@code 0x20}, but must not be at the beginning or at the end of the value.
|
||||
* Leading or trailing whitespace may not be preserved.</li>
|
||||
* <li>Space: {@code 0x20}, but must not be at the beginning or at the end of the value. Leading
|
||||
* or trailing whitespace may not be preserved.
|
||||
* <li>ASCII visible characters ({@code 0x21-0x7E}).
|
||||
* </ul>
|
||||
*
|
||||
|
|
@ -404,6 +537,7 @@ public final class Metadata {
|
|||
|
||||
/**
|
||||
* Parse a serialized metadata value from an ASCII string.
|
||||
*
|
||||
* @param serialized value of metadata to parse
|
||||
* @return a parsed instance of type T
|
||||
*/
|
||||
|
|
@ -416,22 +550,23 @@ public final class Metadata {
|
|||
* <h3>Valid characters in key names</h3>
|
||||
*
|
||||
* <p>Only the following ASCII characters are allowed in the names of keys:
|
||||
*
|
||||
* <ul>
|
||||
* <li>digits: {@code 0-9}</li>
|
||||
* <li>uppercase letters: {@code A-Z} (normalized to lower)</li>
|
||||
* <li>lowercase letters: {@code a-z}</li>
|
||||
* <li>special characters: {@code -_.}</li>
|
||||
* <li>digits: {@code 0-9}
|
||||
* <li>uppercase letters: {@code A-Z} (normalized to lower)
|
||||
* <li>lowercase letters: {@code a-z}
|
||||
* <li>special characters: {@code -_.}
|
||||
* </ul>
|
||||
*
|
||||
* <p>This is a a strict subset of the HTTP field-name rules. Applications may not send or
|
||||
* receive metadata with invalid key names. However, the gRPC library may preserve any metadata
|
||||
* received even if it does not conform to the above limitations. Additionally, if metadata
|
||||
* contains non conforming field names, they will still be sent. In this way, unknown metadata
|
||||
* fields are parsed, serialized and preserved, but never interpreted. They are similar to
|
||||
* protobuf unknown fields.
|
||||
* <p>This is a a strict subset of the HTTP field-name rules. Applications may not send or receive
|
||||
* metadata with invalid key names. However, the gRPC library may preserve any metadata received
|
||||
* even if it does not conform to the above limitations. Additionally, if metadata contains non
|
||||
* conforming field names, they will still be sent. In this way, unknown metadata fields are
|
||||
* parsed, serialized and preserved, but never interpreted. They are similar to protobuf unknown
|
||||
* fields.
|
||||
*
|
||||
* <p>Note this has to be the subset of valid HTTP/2 token characters as defined in RFC7230
|
||||
* Section 3.2.6 and RFC5234 Section B.1</p>
|
||||
* Section 3.2.6 and RFC5234 Section B.1
|
||||
*
|
||||
* @see <a href="https://github.com/grpc/grpc/blob/master/doc/PROTOCOL-HTTP2.md">Wire Spec</a>
|
||||
* @see <a href="https://tools.ietf.org/html/rfc7230#section-3.2.6">RFC7230</a>
|
||||
|
|
@ -496,8 +631,8 @@ public final class Metadata {
|
|||
continue;
|
||||
}
|
||||
|
||||
checkArgument(VALID_T_CHARS.get(tChar),
|
||||
"Invalid character '%s' in key name '%s'", tChar, n);
|
||||
checkArgument(
|
||||
VALID_T_CHARS.get(tChar), "Invalid character '%s' in key name '%s'", tChar, n);
|
||||
}
|
||||
return n;
|
||||
}
|
||||
|
|
@ -560,6 +695,7 @@ public final class Metadata {
|
|||
|
||||
/**
|
||||
* Serialize a metadata value to bytes.
|
||||
*
|
||||
* @param value to serialize
|
||||
* @return serialized version of value
|
||||
*/
|
||||
|
|
@ -567,6 +703,7 @@ public final class Metadata {
|
|||
|
||||
/**
|
||||
* Parse a serialized metadata value from bytes.
|
||||
*
|
||||
* @param serialized value of metadata to parse
|
||||
* @return a parsed instance of type T
|
||||
*/
|
||||
|
|
@ -576,14 +713,14 @@ public final class Metadata {
|
|||
private static class BinaryKey<T> extends Key<T> {
|
||||
private final BinaryMarshaller<T> marshaller;
|
||||
|
||||
/**
|
||||
* Keys have a name and a binary marshaller used for serialization.
|
||||
*/
|
||||
/** Keys have a name and a binary marshaller used for serialization. */
|
||||
private BinaryKey(String name, BinaryMarshaller<T> marshaller) {
|
||||
super(name);
|
||||
checkArgument(name.endsWith(BINARY_HEADER_SUFFIX),
|
||||
checkArgument(
|
||||
name.endsWith(BINARY_HEADER_SUFFIX),
|
||||
"Binary header is named %s. It must end with %s",
|
||||
name, BINARY_HEADER_SUFFIX);
|
||||
name,
|
||||
BINARY_HEADER_SUFFIX);
|
||||
checkArgument(name.length() > BINARY_HEADER_SUFFIX.length(), "empty key name");
|
||||
this.marshaller = checkNotNull(marshaller, "marshaller is null");
|
||||
}
|
||||
|
|
@ -602,15 +739,14 @@ public final class Metadata {
|
|||
private static class AsciiKey<T> extends Key<T> {
|
||||
private final AsciiMarshaller<T> marshaller;
|
||||
|
||||
/**
|
||||
* Keys have a name and an ASCII marshaller used for serialization.
|
||||
*/
|
||||
/** Keys have a name and an ASCII marshaller used for serialization. */
|
||||
private AsciiKey(String name, AsciiMarshaller<T> marshaller) {
|
||||
super(name);
|
||||
Preconditions.checkArgument(
|
||||
!name.endsWith(BINARY_HEADER_SUFFIX),
|
||||
"ASCII header is named %s. Only binary headers may end with %s",
|
||||
name, BINARY_HEADER_SUFFIX);
|
||||
name,
|
||||
BINARY_HEADER_SUFFIX);
|
||||
this.marshaller = Preconditions.checkNotNull(marshaller, "marshaller");
|
||||
}
|
||||
|
||||
|
|
@ -628,15 +764,14 @@ public final class Metadata {
|
|||
private static final class TrustedAsciiKey<T> extends Key<T> {
|
||||
private final TrustedAsciiMarshaller<T> marshaller;
|
||||
|
||||
/**
|
||||
* Keys have a name and an ASCII marshaller used for serialization.
|
||||
*/
|
||||
/** Keys have a name and an ASCII marshaller used for serialization. */
|
||||
private TrustedAsciiKey(String name, TrustedAsciiMarshaller<T> marshaller) {
|
||||
super(name);
|
||||
Preconditions.checkArgument(
|
||||
!name.endsWith(BINARY_HEADER_SUFFIX),
|
||||
"ASCII header is named %s. Only binary headers may end with %s",
|
||||
name, BINARY_HEADER_SUFFIX);
|
||||
name,
|
||||
BINARY_HEADER_SUFFIX);
|
||||
this.marshaller = Preconditions.checkNotNull(marshaller, "marshaller");
|
||||
}
|
||||
|
||||
|
|
@ -650,112 +785,4 @@ public final class Metadata {
|
|||
return marshaller.parseAsciiString(serialized);
|
||||
}
|
||||
}
|
||||
|
||||
private static class MetadataEntry {
|
||||
Object parsed;
|
||||
|
||||
@SuppressWarnings("rawtypes")
|
||||
Key key;
|
||||
boolean isBinary;
|
||||
byte[] serializedBinary;
|
||||
|
||||
/**
|
||||
* Constructor used when application layer adds a parsed value.
|
||||
*/
|
||||
private MetadataEntry(Key<?> key, Object parsed) {
|
||||
this.parsed = Preconditions.checkNotNull(parsed, "parsed");
|
||||
this.key = Preconditions.checkNotNull(key, "key");
|
||||
this.isBinary = key instanceof BinaryKey;
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructor used when reading a value from the transport.
|
||||
*/
|
||||
private MetadataEntry(boolean isBinary, byte[] serialized) {
|
||||
Preconditions.checkNotNull(serialized, "serialized");
|
||||
this.serializedBinary = serialized;
|
||||
this.isBinary = isBinary;
|
||||
}
|
||||
|
||||
/**
|
||||
* Copy constructor.
|
||||
*/
|
||||
private MetadataEntry(MetadataEntry entry) {
|
||||
this.parsed = entry.parsed;
|
||||
this.key = entry.key;
|
||||
this.isBinary = entry.isBinary;
|
||||
this.serializedBinary = entry.serializedBinary;
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
public <T> T getParsed(Key<T> key) {
|
||||
T value = (T) parsed;
|
||||
if (value != null) {
|
||||
if (this.key != key) {
|
||||
// Keys don't match so serialize using the old key
|
||||
serializedBinary = this.key.toBytes(value);
|
||||
} else {
|
||||
return value;
|
||||
}
|
||||
}
|
||||
this.key = key;
|
||||
if (serializedBinary != null) {
|
||||
value = key.parseBytes(serializedBinary);
|
||||
}
|
||||
parsed = value;
|
||||
return value;
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
public byte[] getSerialized() {
|
||||
return serializedBinary =
|
||||
serializedBinary == null
|
||||
? key.toBytes(parsed) : serializedBinary;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
if (!isBinary) {
|
||||
return new String(getSerialized(), US_ASCII);
|
||||
} else {
|
||||
// Assume that the toString of an Object is better than a binary encoding.
|
||||
if (parsed != null) {
|
||||
return "" + parsed;
|
||||
} else {
|
||||
return Arrays.toString(serializedBinary);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static class ValueIterable<T> implements Iterable<T> {
|
||||
private final Key<T> key;
|
||||
private final Iterable<MetadataEntry> entries;
|
||||
|
||||
public ValueIterable(Key<T> key, Iterable<MetadataEntry> entries) {
|
||||
this.key = key;
|
||||
this.entries = entries;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Iterator<T> iterator() {
|
||||
final Iterator<MetadataEntry> iterator = entries.iterator();
|
||||
class ValueIterator implements Iterator<T> {
|
||||
@Override public boolean hasNext() {
|
||||
return iterator.hasNext();
|
||||
}
|
||||
|
||||
@Override public T next() {
|
||||
return iterator.next().getParsed(key);
|
||||
}
|
||||
|
||||
@Override public void remove() {
|
||||
// Not implemented to not need to conditionally update {@link #storeCount}.
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
}
|
||||
|
||||
return new ValueIterator();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -35,6 +35,7 @@ import static com.google.common.base.Charsets.US_ASCII;
|
|||
|
||||
import com.google.common.io.BaseEncoding;
|
||||
|
||||
import io.grpc.InternalMetadata;
|
||||
import io.grpc.Metadata;
|
||||
|
||||
import java.util.Arrays;
|
||||
|
|
@ -61,7 +62,11 @@ public final class TransportFrameUtil {
|
|||
* @return the interleaved keys and values.
|
||||
*/
|
||||
public static byte[][] toHttp2Headers(Metadata headers) {
|
||||
byte[][] serializedHeaders = headers.serialize();
|
||||
byte[][] serializedHeaders = InternalMetadata.serialize(headers);
|
||||
// TODO(carl-mastrangelo): eventually remove this once all callers are updated.
|
||||
if (serializedHeaders == null) {
|
||||
return new byte[][]{};
|
||||
}
|
||||
int k = 0;
|
||||
for (int i = 0; i < serializedHeaders.length; i += 2) {
|
||||
byte[] key = serializedHeaders[i];
|
||||
|
|
|
|||
|
|
@ -38,7 +38,6 @@ import static org.junit.Assert.assertEquals;
|
|||
import static org.junit.Assert.assertFalse;
|
||||
import static org.junit.Assert.assertNotEquals;
|
||||
import static org.junit.Assert.assertNull;
|
||||
import static org.junit.Assert.assertSame;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
import static org.junit.Assert.fail;
|
||||
|
||||
|
|
@ -125,13 +124,21 @@ public class MetadataTest {
|
|||
assertEquals(null, metadata.get(KEY));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void discardAll_empty() {
|
||||
Metadata metadata = new Metadata();
|
||||
metadata.discardAll(KEY);
|
||||
assertEquals(null, metadata.getAll(KEY));
|
||||
assertEquals(null, metadata.get(KEY));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetAllNoRemove() {
|
||||
Fish lance = new Fish(LANCE);
|
||||
Metadata metadata = new Metadata();
|
||||
metadata.put(KEY, lance);
|
||||
Iterator<Fish> i = metadata.getAll(KEY).iterator();
|
||||
assertSame(lance, i.next());
|
||||
assertEquals(lance, i.next());
|
||||
|
||||
thrown.expect(UnsupportedOperationException.class);
|
||||
i.remove();
|
||||
|
|
@ -142,20 +149,18 @@ public class MetadataTest {
|
|||
Fish lance = new Fish(LANCE);
|
||||
Metadata metadata = new Metadata();
|
||||
metadata.put(KEY, lance);
|
||||
// Should be able to read same instance out
|
||||
assertSame(lance, metadata.get(KEY));
|
||||
assertEquals(lance, metadata.get(KEY));
|
||||
Iterator<Fish> fishes = metadata.<Fish>getAll(KEY).iterator();
|
||||
assertTrue(fishes.hasNext());
|
||||
assertSame(fishes.next(), lance);
|
||||
assertEquals(fishes.next(), lance);
|
||||
assertFalse(fishes.hasNext());
|
||||
byte[][] serialized = metadata.serialize();
|
||||
assertEquals(2, serialized.length);
|
||||
assertEquals(new String(serialized[0], US_ASCII), "test-bin");
|
||||
assertArrayEquals(LANCE_BYTES, serialized[1]);
|
||||
assertSame(lance, metadata.get(KEY));
|
||||
// Serialized instance should be cached too
|
||||
assertSame(serialized[0], metadata.serialize()[0]);
|
||||
assertSame(serialized[1], metadata.serialize()[1]);
|
||||
assertEquals(lance, metadata.get(KEY));
|
||||
assertEquals(serialized[0], metadata.serialize()[0]);
|
||||
assertEquals(serialized[1], metadata.serialize()[1]);
|
||||
}
|
||||
|
||||
@Test
|
||||
|
|
@ -164,7 +169,7 @@ public class MetadataTest {
|
|||
Fish lance = raw.get(KEY);
|
||||
assertEquals(lance, new Fish(LANCE));
|
||||
// Reading again should return the same parsed instance
|
||||
assertSame(lance, raw.get(KEY));
|
||||
assertEquals(lance, raw.get(KEY));
|
||||
}
|
||||
|
||||
@Test
|
||||
|
|
@ -199,7 +204,7 @@ public class MetadataTest {
|
|||
|
||||
Iterator<Fish> fishes = h1.<Fish>getAll(KEY).iterator();
|
||||
assertTrue(fishes.hasNext());
|
||||
assertSame(fishes.next(), lance);
|
||||
assertEquals(fishes.next(), lance);
|
||||
assertFalse(fishes.hasNext());
|
||||
}
|
||||
|
||||
|
|
@ -242,15 +247,27 @@ public class MetadataTest {
|
|||
Metadata h = new Metadata();
|
||||
h.put(KEY, new Fish("binary"));
|
||||
h.put(Metadata.Key.of("test", Metadata.ASCII_STRING_MARSHALLER), "ascii");
|
||||
assertEquals("Metadata({test-bin=[Fish(binary)], test=[ascii]})", h.toString());
|
||||
assertEquals("Metadata(test-bin=YmluYXJ5,test=ascii)", h.toString());
|
||||
|
||||
Metadata t = new Metadata();
|
||||
t.put(Metadata.Key.of("test", Metadata.ASCII_STRING_MARSHALLER), "ascii");
|
||||
assertEquals("Metadata({test=[ascii]})", t.toString());
|
||||
assertEquals("Metadata(test=ascii)", t.toString());
|
||||
|
||||
t = new Metadata("test".getBytes(US_ASCII), "ascii".getBytes(US_ASCII),
|
||||
"test-bin".getBytes(US_ASCII), "binary".getBytes(US_ASCII));
|
||||
assertEquals("Metadata({test=[ascii], test-bin=[[98, 105, 110, 97, 114, 121]]})", t.toString());
|
||||
assertEquals("Metadata(test=ascii,test-bin=YmluYXJ5)", t.toString());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void verifyToString_usingBinary() {
|
||||
Metadata h = new Metadata();
|
||||
h.put(KEY, new Fish("binary"));
|
||||
h.put(Metadata.Key.of("test", Metadata.ASCII_STRING_MARSHALLER), "ascii");
|
||||
assertEquals("Metadata(test-bin=YmluYXJ5,test=ascii)", h.toString());
|
||||
|
||||
Metadata t = new Metadata();
|
||||
t.put(Metadata.Key.of("test", Metadata.ASCII_STRING_MARSHALLER), "ascii");
|
||||
assertEquals("Metadata(test=ascii)", t.toString());
|
||||
}
|
||||
|
||||
@Test
|
||||
|
|
|
|||
|
|
@ -41,6 +41,7 @@ import static org.junit.Assert.fail;
|
|||
|
||||
import com.google.common.io.BaseEncoding;
|
||||
|
||||
import io.grpc.InternalMetadata;
|
||||
import io.grpc.Metadata;
|
||||
import io.grpc.Metadata.BinaryMarshaller;
|
||||
import io.grpc.Metadata.Key;
|
||||
|
|
@ -110,7 +111,7 @@ public class TransportFrameUtilTest {
|
|||
headers.put(BINARY_STRING_WITHOUT_SUFFIX, NONCOMPLIANT_ASCII_STRING);
|
||||
byte[][] http2Headers = TransportFrameUtil.toHttp2Headers(headers);
|
||||
byte[][] rawSerialized = TransportFrameUtil.toRawSerializedHeaders(http2Headers);
|
||||
Metadata recoveredHeaders = new Metadata(rawSerialized);
|
||||
Metadata recoveredHeaders = InternalMetadata.newMetadata(rawSerialized);
|
||||
assertEquals(COMPLIANT_ASCII_STRING, recoveredHeaders.get(PLAIN_STRING));
|
||||
assertEquals(NONCOMPLIANT_ASCII_STRING, recoveredHeaders.get(BINARY_STRING));
|
||||
assertNull(recoveredHeaders.get(BINARY_STRING_WITHOUT_SUFFIX));
|
||||
|
|
|
|||
|
|
@ -40,6 +40,7 @@ import static io.netty.util.CharsetUtil.UTF_8;
|
|||
import com.google.common.annotations.VisibleForTesting;
|
||||
import com.google.common.base.Preconditions;
|
||||
|
||||
import io.grpc.InternalMetadata;
|
||||
import io.grpc.Metadata;
|
||||
import io.grpc.Status;
|
||||
import io.grpc.internal.GrpcUtil;
|
||||
|
|
@ -91,9 +92,10 @@ class Utils {
|
|||
|
||||
public static Metadata convertHeaders(Http2Headers http2Headers) {
|
||||
if (http2Headers instanceof GrpcHttp2InboundHeaders) {
|
||||
return new Metadata(((GrpcHttp2InboundHeaders) http2Headers).namesAndValues());
|
||||
GrpcHttp2InboundHeaders h = (GrpcHttp2InboundHeaders) http2Headers;
|
||||
return InternalMetadata.newMetadata(h.numHeaders(), h.namesAndValues());
|
||||
}
|
||||
return new Metadata(convertHeadersToArray(http2Headers));
|
||||
return InternalMetadata.newMetadata(convertHeadersToArray(http2Headers));
|
||||
}
|
||||
|
||||
private static byte[][] convertHeadersToArray(Http2Headers http2Headers) {
|
||||
|
|
@ -141,9 +143,10 @@ class Utils {
|
|||
|
||||
public static Metadata convertTrailers(Http2Headers http2Headers) {
|
||||
if (http2Headers instanceof GrpcHttp2InboundHeaders) {
|
||||
return new Metadata(((GrpcHttp2InboundHeaders) http2Headers).namesAndValues());
|
||||
GrpcHttp2InboundHeaders h = (GrpcHttp2InboundHeaders) http2Headers;
|
||||
return InternalMetadata.newMetadata(h.numHeaders(), h.namesAndValues());
|
||||
}
|
||||
return new Metadata(convertHeadersToArray(http2Headers));
|
||||
return InternalMetadata.newMetadata(convertHeadersToArray(http2Headers));
|
||||
}
|
||||
|
||||
public static Http2Headers convertTrailers(Metadata trailers, boolean headersSent) {
|
||||
|
|
|
|||
|
|
@ -33,6 +33,7 @@ package io.grpc.okhttp;
|
|||
|
||||
import com.google.common.base.Preconditions;
|
||||
|
||||
import io.grpc.InternalMetadata;
|
||||
import io.grpc.Metadata;
|
||||
import io.grpc.internal.TransportFrameUtil;
|
||||
import io.grpc.okhttp.internal.CipherSuite;
|
||||
|
|
@ -49,11 +50,11 @@ class Utils {
|
|||
static final int CONNECTION_STREAM_ID = 0;
|
||||
|
||||
public static Metadata convertHeaders(List<Header> http2Headers) {
|
||||
return new Metadata(convertHeadersToArray(http2Headers));
|
||||
return InternalMetadata.newMetadata(convertHeadersToArray(http2Headers));
|
||||
}
|
||||
|
||||
public static Metadata convertTrailers(List<Header> http2Headers) {
|
||||
return new Metadata(convertHeadersToArray(http2Headers));
|
||||
return InternalMetadata.newMetadata(convertHeadersToArray(http2Headers));
|
||||
}
|
||||
|
||||
private static byte[][] convertHeadersToArray(List<Header> http2Headers) {
|
||||
|
|
|
|||
|
|
@ -572,14 +572,17 @@ public abstract class AbstractTransportTest {
|
|||
clientHeaders.put(asciiKey, "dupvalue");
|
||||
clientHeaders.put(asciiKey, "dupvalue");
|
||||
clientHeaders.put(binaryKey, "äbinaryclient");
|
||||
Metadata clientHeadersCopy = new Metadata();
|
||||
|
||||
clientHeadersCopy.merge(clientHeaders);
|
||||
ClientStream clientStream = client.newStream(methodDescriptor, clientHeaders);
|
||||
clientStream.start(mockClientStreamListener);
|
||||
StreamCreation serverStreamCreation
|
||||
= serverTransportListener.takeStreamOrFail(TIMEOUT_MS, TimeUnit.MILLISECONDS);
|
||||
assertEquals(methodDescriptor.getFullMethodName(), serverStreamCreation.method);
|
||||
assertEquals(Lists.newArrayList(clientHeaders.getAll(asciiKey)),
|
||||
assertEquals(Lists.newArrayList(clientHeadersCopy.getAll(asciiKey)),
|
||||
Lists.newArrayList(serverStreamCreation.headers.getAll(asciiKey)));
|
||||
assertEquals(Lists.newArrayList(clientHeaders.getAll(binaryKey)),
|
||||
assertEquals(Lists.newArrayList(clientHeadersCopy.getAll(binaryKey)),
|
||||
Lists.newArrayList(serverStreamCreation.headers.getAll(binaryKey)));
|
||||
ServerStream serverStream = serverStreamCreation.stream;
|
||||
ServerStreamListener mockServerStreamListener = serverStreamCreation.listener;
|
||||
|
|
@ -605,11 +608,13 @@ public abstract class AbstractTransportTest {
|
|||
serverHeaders.put(asciiKey, "dupvalue");
|
||||
serverHeaders.put(asciiKey, "dupvalue");
|
||||
serverHeaders.put(binaryKey, "äbinaryserver");
|
||||
Metadata serverHeadersCopy = new Metadata();
|
||||
serverHeadersCopy.merge(serverHeaders);
|
||||
serverStream.writeHeaders(serverHeaders);
|
||||
verify(mockClientStreamListener, timeout(TIMEOUT_MS)).headersRead(metadataCaptor.capture());
|
||||
assertEquals(Lists.newArrayList(serverHeaders.getAll(asciiKey)),
|
||||
assertEquals(Lists.newArrayList(serverHeadersCopy.getAll(asciiKey)),
|
||||
Lists.newArrayList(metadataCaptor.getValue().getAll(asciiKey)));
|
||||
assertEquals(Lists.newArrayList(serverHeaders.getAll(binaryKey)),
|
||||
assertEquals(Lists.newArrayList(serverHeadersCopy.getAll(binaryKey)),
|
||||
Lists.newArrayList(metadataCaptor.getValue().getAll(binaryKey)));
|
||||
|
||||
clientStream.request(1);
|
||||
|
|
|
|||
Loading…
Reference in New Issue