mirror of https://github.com/grpc/grpc-java.git
1052 lines
32 KiB
Java
1052 lines
32 KiB
Java
/*
|
|
* Copyright 2014 The gRPC Authors
|
|
*
|
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
* you may not use this file except in compliance with the License.
|
|
* You may obtain a copy of the License at
|
|
*
|
|
* http://www.apache.org/licenses/LICENSE-2.0
|
|
*
|
|
* Unless required by applicable law or agreed to in writing, software
|
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
* See the License for the specific language governing permissions and
|
|
* limitations under the License.
|
|
*/
|
|
|
|
package io.grpc;
|
|
|
|
import static com.google.common.base.Preconditions.checkArgument;
|
|
import static com.google.common.base.Preconditions.checkNotNull;
|
|
import static java.nio.charset.StandardCharsets.US_ASCII;
|
|
|
|
import com.google.common.annotations.VisibleForTesting;
|
|
import com.google.common.base.Preconditions;
|
|
import com.google.common.io.BaseEncoding;
|
|
import com.google.common.io.ByteStreams;
|
|
import java.io.ByteArrayInputStream;
|
|
import java.io.IOException;
|
|
import java.io.InputStream;
|
|
import java.nio.ByteBuffer;
|
|
import java.util.ArrayList;
|
|
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.List;
|
|
import java.util.Locale;
|
|
import java.util.Map;
|
|
import java.util.NoSuchElementException;
|
|
import java.util.Set;
|
|
import java.util.logging.Level;
|
|
import java.util.logging.Logger;
|
|
import javax.annotation.Nullable;
|
|
import javax.annotation.concurrent.Immutable;
|
|
import javax.annotation.concurrent.NotThreadSafe;
|
|
|
|
/**
|
|
* Provides access to read and write metadata values to be exchanged during a call.
|
|
*
|
|
* <p>Keys are allowed to be associated with more than one value.
|
|
*
|
|
* <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 {
|
|
private static final Logger logger = Logger.getLogger(Metadata.class.getName());
|
|
|
|
/**
|
|
* All binary headers should have this suffix in their names. Vice versa.
|
|
*
|
|
* <p>Its value is {@code "-bin"}. An ASCII header's name must not end with this.
|
|
*/
|
|
public static final String BINARY_HEADER_SUFFIX = "-bin";
|
|
|
|
/**
|
|
* Simple metadata marshaller that encodes bytes as is.
|
|
*
|
|
* <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>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[]>() {
|
|
|
|
@Override
|
|
public byte[] toBytes(byte[] value) {
|
|
return value;
|
|
}
|
|
|
|
@Override
|
|
public byte[] parseBytes(byte[] serialized) {
|
|
return serialized;
|
|
}
|
|
};
|
|
|
|
/**
|
|
* Simple metadata marshaller that encodes strings as is.
|
|
*
|
|
* <p>This should be used with ASCII strings that only contain the characters listed in the class
|
|
* comment of {@link AsciiMarshaller}. Otherwise the output may be considered invalid and
|
|
* discarded by the transport, or the call may fail.
|
|
*/
|
|
public static final AsciiMarshaller<String> ASCII_STRING_MARSHALLER =
|
|
new AsciiMarshaller<String>() {
|
|
|
|
@Override
|
|
public String toAsciiString(String value) {
|
|
return value;
|
|
}
|
|
|
|
@Override
|
|
public String parseAsciiString(String serialized) {
|
|
return serialized;
|
|
}
|
|
};
|
|
|
|
static final BaseEncoding BASE64_ENCODING_OMIT_PADDING = BaseEncoding.base64().omitPadding();
|
|
|
|
/**
|
|
* Constructor called by the transport layer when it receives binary metadata. Metadata will
|
|
* mutate the passed in array.
|
|
*/
|
|
Metadata(byte[]... binaryValues) {
|
|
this(binaryValues.length / 2, binaryValues);
|
|
}
|
|
|
|
/**
|
|
* Constructor called by the transport layer when it receives binary metadata. Metadata will
|
|
* mutate the passed in array.
|
|
*
|
|
* @param usedNames the number of names
|
|
*/
|
|
Metadata(int usedNames, byte[]... binaryValues) {
|
|
this(usedNames, (Object[]) binaryValues);
|
|
}
|
|
|
|
/**
|
|
* Constructor called by the transport layer when it receives partially-parsed metadata.
|
|
* Metadata will mutate the passed in array.
|
|
*
|
|
* @param usedNames the number of names
|
|
* @param namesAndValues an array of interleaved names and values, with each name
|
|
* (at even indices) represented by a byte array, and values (at odd indices) as
|
|
* described by {@link InternalMetadata#newMetadataWithParsedValues}.
|
|
*/
|
|
Metadata(int usedNames, Object[] namesAndValues) {
|
|
assert (namesAndValues.length & 1) == 0
|
|
: "Odd number of key-value pairs " + namesAndValues.length;
|
|
size = usedNames;
|
|
this.namesAndValues = namesAndValues;
|
|
}
|
|
|
|
private Object[] namesAndValues;
|
|
// The unscaled number of headers present.
|
|
private int size;
|
|
|
|
private byte[] name(int i) {
|
|
return (byte[]) namesAndValues[i * 2];
|
|
}
|
|
|
|
private void name(int i, byte[] name) {
|
|
namesAndValues[i * 2] = name;
|
|
}
|
|
|
|
private Object value(int i) {
|
|
return namesAndValues[i * 2 + 1];
|
|
}
|
|
|
|
private void value(int i, byte[] value) {
|
|
namesAndValues[i * 2 + 1] = value;
|
|
}
|
|
|
|
private void value(int i, Object value) {
|
|
if (namesAndValues instanceof byte[][]) {
|
|
// Reallocate an array of Object.
|
|
expand(cap());
|
|
}
|
|
namesAndValues[i * 2 + 1] = value;
|
|
}
|
|
|
|
private byte[] valueAsBytes(int i) {
|
|
Object value = value(i);
|
|
if (value instanceof byte[]) {
|
|
return (byte[]) value;
|
|
} else {
|
|
return ((LazyValue<?>) value).toBytes();
|
|
}
|
|
}
|
|
|
|
private Object valueAsBytesOrStream(int i) {
|
|
Object value = value(i);
|
|
if (value instanceof byte[]) {
|
|
return value;
|
|
} else {
|
|
return ((LazyValue<?>) value).toStream();
|
|
}
|
|
}
|
|
|
|
private <T> T valueAsT(int i, Key<T> key) {
|
|
Object value = value(i);
|
|
if (value instanceof byte[]) {
|
|
return key.parseBytes((byte[]) value);
|
|
} else {
|
|
return ((LazyValue<?>) value).toObject(key);
|
|
}
|
|
}
|
|
|
|
private int cap() {
|
|
return namesAndValues != null ? namesAndValues.length : 0;
|
|
}
|
|
|
|
// The scaled version of size.
|
|
private int len() {
|
|
return size * 2;
|
|
}
|
|
|
|
/** checks when {@link #namesAndValues} is null or has no elements. */
|
|
private boolean isEmpty() {
|
|
return size == 0;
|
|
}
|
|
|
|
/** Constructor called by the application layer when it wants to send metadata. */
|
|
public Metadata() {}
|
|
|
|
/** Returns the total number of key-value headers in this metadata, including duplicates. */
|
|
int headerCount() {
|
|
return size;
|
|
}
|
|
|
|
/**
|
|
* Returns true if a value is defined for the given key.
|
|
*
|
|
* <p>This is done by linear search, so if it is followed by {@link #get} or {@link #getAll},
|
|
* prefer calling them directly and checking the return value against {@code null}.
|
|
*/
|
|
public boolean containsKey(Key<?> key) {
|
|
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.
|
|
*/
|
|
@Nullable
|
|
public <T> T get(Key<T> key) {
|
|
for (int i = size - 1; i >= 0; i--) {
|
|
if (bytesEqual(key.asciiName(), name(i))) {
|
|
return valueAsT(i, key);
|
|
}
|
|
}
|
|
return null;
|
|
}
|
|
|
|
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 valueAsT(idx++, key);
|
|
}
|
|
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.
|
|
*/
|
|
@Nullable
|
|
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<>(key, i);
|
|
}
|
|
}
|
|
return null;
|
|
}
|
|
|
|
/**
|
|
* Returns set of all keys in store.
|
|
*
|
|
* @return unmodifiable Set of keys
|
|
*/
|
|
@SuppressWarnings("deprecation") // The String ctor is deprecated, but fast.
|
|
public Set<String> keys() {
|
|
if (isEmpty()) {
|
|
return Collections.emptySet();
|
|
}
|
|
Set<String> ks = new HashSet<>(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);
|
|
}
|
|
|
|
/**
|
|
* Adds the {@code key, value} pair. If {@code key} already has values, {@code value} is added to
|
|
* the end. Duplicate values for the same key are permitted.
|
|
*
|
|
* @throws NullPointerException if key or value is null
|
|
*/
|
|
public <T> void put(Key<T> key, T value) {
|
|
Preconditions.checkNotNull(key, "key");
|
|
Preconditions.checkNotNull(value, "value");
|
|
maybeExpand();
|
|
name(size, key.asciiName());
|
|
if (key.serializesToStreams()) {
|
|
value(size, LazyValue.create(key, value));
|
|
} else {
|
|
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) {
|
|
Object[] newNamesAndValues = new Object[newCapacity];
|
|
if (!isEmpty()) {
|
|
System.arraycopy(namesAndValues, 0, newNamesAndValues, 0, len());
|
|
}
|
|
namesAndValues = newNamesAndValues;
|
|
}
|
|
|
|
/**
|
|
* Removes the first occurrence of {@code value} for {@code key}.
|
|
*
|
|
* @param key key for value
|
|
* @param value value
|
|
* @return {@code true} if {@code value} removed; {@code false} if {@code value} was not present
|
|
* @throws NullPointerException if {@code key} or {@code value} is null
|
|
*/
|
|
public <T> boolean remove(Key<T> key, T value) {
|
|
Preconditions.checkNotNull(key, "key");
|
|
Preconditions.checkNotNull(value, "value");
|
|
for (int i = 0; i < size; i++) {
|
|
if (!bytesEqual(key.asciiName(), name(i))) {
|
|
continue;
|
|
}
|
|
T stored = valueAsT(i, key);
|
|
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, (byte[]) null);
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
/** Remove all values for the given key. If there were no values, {@code null} is returned. */
|
|
public <T> Iterable<T> removeAll(Key<T> key) {
|
|
if (isEmpty()) {
|
|
return null;
|
|
}
|
|
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 ArrayList<T>();
|
|
ret.add(valueAsT(readIdx, key));
|
|
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;
|
|
}
|
|
|
|
/**
|
|
* Remove all values for the given key without returning them. This is a minor performance
|
|
* optimization if you do not need the previous values.
|
|
*/
|
|
@ExperimentalApi("https://github.com/grpc/grpc-java/issues/4691")
|
|
public <T> void discardAll(Key<T> key) {
|
|
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;
|
|
}
|
|
|
|
/**
|
|
* Serialize all the metadata entries.
|
|
*
|
|
* <p>It produces serialized names and values interleaved. result[i*2] are names, while
|
|
* result[i*2+1] are values.
|
|
*
|
|
* <p>Names are ASCII string bytes that contains only the characters listed in the class comment
|
|
* of {@link Key}. If the name ends with {@code "-bin"}, the value can be raw binary. Otherwise,
|
|
* the value must contain only characters listed in the class comments of {@link AsciiMarshaller}
|
|
*
|
|
* <p>The returned individual byte arrays <em>must not</em> be modified. However, the top level
|
|
* array may be modified.
|
|
*
|
|
* <p>This method is intended for transport use only.
|
|
*/
|
|
@Nullable
|
|
byte[][] serialize() {
|
|
byte[][] serialized = new byte[len()][];
|
|
if (namesAndValues instanceof byte[][]) {
|
|
System.arraycopy(namesAndValues, 0, serialized, 0, len());
|
|
} else {
|
|
for (int i = 0; i < size; i++) {
|
|
serialized[i * 2] = name(i);
|
|
serialized[i * 2 + 1] = valueAsBytes(i);
|
|
}
|
|
}
|
|
return serialized;
|
|
}
|
|
|
|
/**
|
|
* Serializes all metadata entries, leaving some values as {@link InputStream}s.
|
|
*
|
|
* <p>Produces serialized names and values interleaved. result[i*2] are names, while
|
|
* result[i*2+1] are values.
|
|
*
|
|
* <p>Names are byte arrays as described according to the {@link #serialize}
|
|
* method. Values are either byte arrays or {@link InputStream}s.
|
|
*
|
|
* <p>This method is intended for transport use only.
|
|
*/
|
|
@Nullable
|
|
Object[] serializePartial() {
|
|
Object[] serialized = new Object[len()];
|
|
for (int i = 0; i < size; i++) {
|
|
serialized[i * 2] = name(i);
|
|
serialized[i * 2 + 1] = valueAsBytesOrStream(i);
|
|
}
|
|
return serialized;
|
|
}
|
|
|
|
/**
|
|
* Perform a simple merge of two sets of metadata.
|
|
*
|
|
* <p>This is a purely additive operation, because a single key can be associated with multiple
|
|
* values.
|
|
*/
|
|
public void merge(Metadata other) {
|
|
if (other.isEmpty()) {
|
|
return;
|
|
}
|
|
int remaining = cap() - len();
|
|
if (isEmpty() || remaining < other.len()) {
|
|
expand(len() + other.len());
|
|
}
|
|
System.arraycopy(other.namesAndValues, 0, namesAndValues, len(), other.len());
|
|
size += other.size;
|
|
}
|
|
|
|
/**
|
|
* Merge values from the given set of keys into this set of metadata. If a key is present in keys,
|
|
* then all of the associated values will be copied over.
|
|
*
|
|
* @param other The source of the new key values.
|
|
* @param keys The subset of matching key we want to copy, if they exist in the source.
|
|
*/
|
|
public void merge(Metadata other, Set<Key<?>> keys) {
|
|
Preconditions.checkNotNull(other, "other");
|
|
// Use ByteBuffer for equals and hashCode.
|
|
Map<ByteBuffer, Key<?>> asciiKeys = new HashMap<>(keys.size());
|
|
for (Key<?> key : keys) {
|
|
asciiKeys.put(ByteBuffer.wrap(key.asciiName()), key);
|
|
}
|
|
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() {
|
|
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(BASE64_ENCODING_OMIT_PADDING.encode(valueAsBytes(i)));
|
|
} else {
|
|
String headerValue = new String(valueAsBytes(i), US_ASCII);
|
|
sb.append(headerValue);
|
|
}
|
|
}
|
|
return sb.append(')').toString();
|
|
}
|
|
|
|
private boolean bytesEqual(byte[] left, byte[] right) {
|
|
return Arrays.equals(left, right);
|
|
}
|
|
|
|
/** 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
|
|
*/
|
|
byte[] toBytes(T value);
|
|
|
|
/**
|
|
* Parse a serialized metadata value from bytes.
|
|
*
|
|
* @param serialized value of metadata to parse
|
|
* @return a parsed instance of type T
|
|
*/
|
|
T parseBytes(byte[] serialized);
|
|
}
|
|
|
|
/**
|
|
* Marshaller for metadata values that are serialized into ASCII strings. The strings 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>ASCII visible characters ({@code 0x21-0x7E}).
|
|
* </ul>
|
|
*
|
|
* <p>Note this has to be the subset of valid characters in {@code field-content} from RFC 7230
|
|
* Section 3.2.
|
|
*/
|
|
public interface AsciiMarshaller<T> {
|
|
/**
|
|
* Serialize a metadata value to a ASCII string that contains only the characters listed in the
|
|
* class comment of {@link AsciiMarshaller}. Otherwise the output may be considered invalid and
|
|
* discarded by the transport, or the call may fail.
|
|
*
|
|
* @param value to serialize
|
|
* @return serialized version of value, or null if value cannot be transmitted.
|
|
*/
|
|
String toAsciiString(T value);
|
|
|
|
/**
|
|
* Parse a serialized metadata value from an ASCII string.
|
|
*
|
|
* @param serialized value of metadata to parse
|
|
* @return a parsed instance of type T
|
|
*/
|
|
T parseAsciiString(String serialized);
|
|
}
|
|
|
|
/** Marshaller for metadata values that are serialized to an InputStream. */
|
|
@ExperimentalApi("https://github.com/grpc/grpc-java/issues/6575")
|
|
public interface BinaryStreamMarshaller<T> {
|
|
/**
|
|
* Serializes a metadata value to an {@link InputStream}.
|
|
*
|
|
* @param value to serialize
|
|
* @return serialized version of value
|
|
*/
|
|
InputStream toStream(T value);
|
|
|
|
/**
|
|
* Parses a serialized metadata value from an {@link InputStream}.
|
|
*
|
|
* @param stream of metadata to parse
|
|
* @return a parsed instance of type T
|
|
*/
|
|
T parseStream(InputStream stream);
|
|
}
|
|
|
|
/**
|
|
* Key for metadata entries. Allows for parsing and serialization of 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>uppercase letters: {@code A-Z} (normalized to lower)
|
|
* <li>lowercase letters: {@code a-z}
|
|
* <li>special characters: {@code -_.}
|
|
* </ul>
|
|
*
|
|
* <p>This is 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>Note that a key is immutable but it may not be deeply immutable, because the key depends on
|
|
* its marshaller, and the marshaller can be mutable though not recommended.
|
|
*
|
|
* @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>
|
|
* @see <a href="https://tools.ietf.org/html/rfc5234#appendix-B.1">RFC5234</a>
|
|
*/
|
|
@Immutable
|
|
public abstract static class Key<T> {
|
|
|
|
/** Valid characters for field names as defined in RFC7230 and RFC5234. */
|
|
private static final BitSet VALID_T_CHARS = generateValidTChars();
|
|
|
|
/**
|
|
* Creates a key for a binary header.
|
|
*
|
|
* @param name Must contain only the valid key characters as defined in the class comment. Must
|
|
* end with {@link #BINARY_HEADER_SUFFIX}.
|
|
*/
|
|
public static <T> Key<T> of(String name, BinaryMarshaller<T> marshaller) {
|
|
return new BinaryKey<>(name, marshaller);
|
|
}
|
|
|
|
/**
|
|
* Creates a key for a binary header, serializing to input streams.
|
|
*
|
|
* @param name Must contain only the valid key characters as defined in the class comment. Must
|
|
* end with {@link #BINARY_HEADER_SUFFIX}.
|
|
*/
|
|
@ExperimentalApi("https://github.com/grpc/grpc-java/issues/6575")
|
|
public static <T> Key<T> of(String name, BinaryStreamMarshaller<T> marshaller) {
|
|
return new LazyStreamBinaryKey<>(name, marshaller);
|
|
}
|
|
|
|
/**
|
|
* Creates a key for an ASCII header.
|
|
*
|
|
* @param name Must contain only the valid key characters as defined in the class comment. Must
|
|
* <b>not</b> end with {@link #BINARY_HEADER_SUFFIX}
|
|
*/
|
|
public static <T> Key<T> of(String name, AsciiMarshaller<T> marshaller) {
|
|
return of(name, false, marshaller);
|
|
}
|
|
|
|
static <T> Key<T> of(String name, boolean pseudo, AsciiMarshaller<T> marshaller) {
|
|
return new AsciiKey<>(name, pseudo, marshaller);
|
|
}
|
|
|
|
static <T> Key<T> of(String name, boolean pseudo, TrustedAsciiMarshaller<T> marshaller) {
|
|
return new TrustedAsciiKey<>(name, pseudo, marshaller);
|
|
}
|
|
|
|
private final String originalName;
|
|
|
|
private final String name;
|
|
private final byte[] nameBytes;
|
|
private final Object marshaller;
|
|
|
|
private static BitSet generateValidTChars() {
|
|
BitSet valid = new BitSet(0x7f);
|
|
valid.set('-');
|
|
valid.set('_');
|
|
valid.set('.');
|
|
for (char c = '0'; c <= '9'; c++) {
|
|
valid.set(c);
|
|
}
|
|
// Only validates after normalization, so we exclude uppercase.
|
|
for (char c = 'a'; c <= 'z'; c++) {
|
|
valid.set(c);
|
|
}
|
|
return valid;
|
|
}
|
|
|
|
private static String validateName(String n, boolean pseudo) {
|
|
checkNotNull(n, "name");
|
|
checkArgument(!n.isEmpty(), "token must have at least 1 tchar");
|
|
if (n.equals("connection")) {
|
|
logger.log(
|
|
Level.WARNING,
|
|
"Metadata key is 'Connection', which should not be used. That is used by HTTP/1 for "
|
|
+ "connection-specific headers which are not to be forwarded. There is probably an "
|
|
+ "HTTP/1 conversion bug. Simply removing the Connection header is not enough; you "
|
|
+ "should remove all headers it references as well. See RFC 7230 section 6.1",
|
|
new RuntimeException("exception to show backtrace"));
|
|
}
|
|
for (int i = 0; i < n.length(); i++) {
|
|
char tChar = n.charAt(i);
|
|
if (pseudo && tChar == ':' && i == 0) {
|
|
continue;
|
|
}
|
|
|
|
checkArgument(
|
|
VALID_T_CHARS.get(tChar), "Invalid character '%s' in key name '%s'", tChar, n);
|
|
}
|
|
return n;
|
|
}
|
|
|
|
private Key(String name, boolean pseudo, Object marshaller) {
|
|
this.originalName = checkNotNull(name, "name");
|
|
this.name = validateName(this.originalName.toLowerCase(Locale.ROOT), pseudo);
|
|
this.nameBytes = this.name.getBytes(US_ASCII);
|
|
this.marshaller = marshaller;
|
|
}
|
|
|
|
/**
|
|
* Returns the original name used to create this key.
|
|
*/
|
|
public final String originalName() {
|
|
return originalName;
|
|
}
|
|
|
|
/**
|
|
* Returns the normalized name for this key.
|
|
*/
|
|
public final String name() {
|
|
return name;
|
|
}
|
|
|
|
/**
|
|
* Get the name as bytes using ASCII-encoding.
|
|
*
|
|
* <p>The returned byte arrays <em>must not</em> be modified.
|
|
*
|
|
* <p>This method is intended for transport use only.
|
|
*/
|
|
// TODO (louiscryan): Migrate to ByteString
|
|
@VisibleForTesting
|
|
byte[] asciiName() {
|
|
return nameBytes;
|
|
}
|
|
|
|
/**
|
|
* Returns true if the two objects are both Keys, and their names match (case insensitive).
|
|
*/
|
|
@SuppressWarnings("EqualsGetClass")
|
|
@Override
|
|
public final boolean equals(Object o) {
|
|
if (this == o) {
|
|
return true;
|
|
}
|
|
if (o == null || getClass() != o.getClass()) {
|
|
return false;
|
|
}
|
|
Key<?> key = (Key<?>) o;
|
|
return name.equals(key.name);
|
|
}
|
|
|
|
@Override
|
|
public final int hashCode() {
|
|
return name.hashCode();
|
|
}
|
|
|
|
@Override
|
|
public String toString() {
|
|
return "Key{name='" + name + "'}";
|
|
}
|
|
|
|
/**
|
|
* Serialize a metadata value to bytes.
|
|
*
|
|
* @param value to serialize
|
|
* @return serialized version of value
|
|
*/
|
|
abstract byte[] toBytes(T value);
|
|
|
|
/**
|
|
* Parse a serialized metadata value from bytes.
|
|
*
|
|
* @param serialized value of metadata to parse
|
|
* @return a parsed instance of type T
|
|
*/
|
|
abstract T parseBytes(byte[] serialized);
|
|
|
|
/**
|
|
* Returns whether this key will be serialized to bytes lazily.
|
|
*/
|
|
boolean serializesToStreams() {
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
* Gets this keys (implementation-specific) marshaller, or null if the
|
|
* marshaller is not of the given type.
|
|
*
|
|
* @param marshallerClass The type we expect the marshaller to be.
|
|
* @return the marshaller object for this key, or null.
|
|
*/
|
|
@Nullable
|
|
final <M> M getMarshaller(Class<M> marshallerClass) {
|
|
if (marshallerClass.isInstance(marshaller)) {
|
|
return marshallerClass.cast(marshaller);
|
|
}
|
|
return null;
|
|
}
|
|
}
|
|
|
|
private static class BinaryKey<T> extends Key<T> {
|
|
private final BinaryMarshaller<T> marshaller;
|
|
|
|
/** Keys have a name and a binary marshaller used for serialization. */
|
|
private BinaryKey(String name, BinaryMarshaller<T> marshaller) {
|
|
super(name, false /* not pseudo */, marshaller);
|
|
checkArgument(
|
|
name.endsWith(BINARY_HEADER_SUFFIX),
|
|
"Binary header is named %s. It must end with %s",
|
|
name,
|
|
BINARY_HEADER_SUFFIX);
|
|
checkArgument(name.length() > BINARY_HEADER_SUFFIX.length(), "empty key name");
|
|
this.marshaller = checkNotNull(marshaller, "marshaller is null");
|
|
}
|
|
|
|
@Override
|
|
byte[] toBytes(T value) {
|
|
return Preconditions.checkNotNull(marshaller.toBytes(value), "null marshaller.toBytes()");
|
|
}
|
|
|
|
@Override
|
|
T parseBytes(byte[] serialized) {
|
|
return marshaller.parseBytes(serialized);
|
|
}
|
|
}
|
|
|
|
/** A binary key for values which should be serialized lazily to {@link InputStream}s. */
|
|
private static class LazyStreamBinaryKey<T> extends Key<T> {
|
|
|
|
private final BinaryStreamMarshaller<T> marshaller;
|
|
|
|
/** Keys have a name and a stream marshaller used for serialization. */
|
|
private LazyStreamBinaryKey(String name, BinaryStreamMarshaller<T> marshaller) {
|
|
super(name, false /* not pseudo */, marshaller);
|
|
checkArgument(
|
|
name.endsWith(BINARY_HEADER_SUFFIX),
|
|
"Binary header is named %s. It must end with %s",
|
|
name,
|
|
BINARY_HEADER_SUFFIX);
|
|
checkArgument(name.length() > BINARY_HEADER_SUFFIX.length(), "empty key name");
|
|
this.marshaller = checkNotNull(marshaller, "marshaller is null");
|
|
}
|
|
|
|
@Override
|
|
byte[] toBytes(T value) {
|
|
return streamToBytes(checkNotNull(marshaller.toStream(value), "null marshaller.toStream()"));
|
|
}
|
|
|
|
@Override
|
|
T parseBytes(byte[] serialized) {
|
|
return marshaller.parseStream(new ByteArrayInputStream(serialized));
|
|
}
|
|
|
|
@Override
|
|
boolean serializesToStreams() {
|
|
return true;
|
|
}
|
|
}
|
|
|
|
/** Internal holder for values which are serialized/de-serialized lazily. */
|
|
static final class LazyValue<T> {
|
|
private final BinaryStreamMarshaller<T> marshaller;
|
|
private final T value;
|
|
private volatile byte[] serialized;
|
|
|
|
static <T> LazyValue<T> create(Key<T> key, T value) {
|
|
return new LazyValue<>(checkNotNull(getBinaryStreamMarshaller(key)), value);
|
|
}
|
|
|
|
/** A value set by the application. */
|
|
LazyValue(BinaryStreamMarshaller<T> marshaller, T value) {
|
|
this.marshaller = marshaller;
|
|
this.value = value;
|
|
}
|
|
|
|
InputStream toStream() {
|
|
return checkNotNull(marshaller.toStream(value), "null marshaller.toStream()");
|
|
}
|
|
|
|
byte[] toBytes() {
|
|
if (serialized == null) {
|
|
synchronized (this) {
|
|
if (serialized == null) {
|
|
serialized = streamToBytes(toStream());
|
|
}
|
|
}
|
|
}
|
|
return serialized;
|
|
}
|
|
|
|
<T2> T2 toObject(Key<T2> key) {
|
|
if (key.serializesToStreams()) {
|
|
BinaryStreamMarshaller<T2> marshaller = getBinaryStreamMarshaller(key);
|
|
if (marshaller != null) {
|
|
return marshaller.parseStream(toStream());
|
|
}
|
|
}
|
|
return key.parseBytes(toBytes());
|
|
}
|
|
|
|
@Nullable
|
|
@SuppressWarnings("unchecked")
|
|
private static <T> BinaryStreamMarshaller<T> getBinaryStreamMarshaller(Key<T> key) {
|
|
return (BinaryStreamMarshaller<T>) key.getMarshaller(BinaryStreamMarshaller.class);
|
|
}
|
|
}
|
|
|
|
private static class AsciiKey<T> extends Key<T> {
|
|
private final AsciiMarshaller<T> marshaller;
|
|
|
|
/** Keys have a name and an ASCII marshaller used for serialization. */
|
|
private AsciiKey(String name, boolean pseudo, AsciiMarshaller<T> marshaller) {
|
|
super(name, pseudo, marshaller);
|
|
Preconditions.checkArgument(
|
|
!name.endsWith(BINARY_HEADER_SUFFIX),
|
|
"ASCII header is named %s. Only binary headers may end with %s",
|
|
name,
|
|
BINARY_HEADER_SUFFIX);
|
|
this.marshaller = Preconditions.checkNotNull(marshaller, "marshaller");
|
|
}
|
|
|
|
@Override
|
|
byte[] toBytes(T value) {
|
|
String encoded = Preconditions.checkNotNull(
|
|
marshaller.toAsciiString(value), "null marshaller.toAsciiString()");
|
|
return encoded.getBytes(US_ASCII);
|
|
}
|
|
|
|
@Override
|
|
T parseBytes(byte[] serialized) {
|
|
return marshaller.parseAsciiString(new String(serialized, US_ASCII));
|
|
}
|
|
}
|
|
|
|
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. */
|
|
private TrustedAsciiKey(String name, boolean pseudo, TrustedAsciiMarshaller<T> marshaller) {
|
|
super(name, pseudo, marshaller);
|
|
Preconditions.checkArgument(
|
|
!name.endsWith(BINARY_HEADER_SUFFIX),
|
|
"ASCII header is named %s. Only binary headers may end with %s",
|
|
name,
|
|
BINARY_HEADER_SUFFIX);
|
|
this.marshaller = Preconditions.checkNotNull(marshaller, "marshaller");
|
|
}
|
|
|
|
@Override
|
|
byte[] toBytes(T value) {
|
|
return Preconditions.checkNotNull(
|
|
marshaller.toAsciiString(value), "null marshaller.toAsciiString()");
|
|
}
|
|
|
|
@Override
|
|
T parseBytes(byte[] serialized) {
|
|
return marshaller.parseAsciiString(serialized);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* A specialized plain ASCII marshaller. Both input and output are assumed to be valid header
|
|
* ASCII.
|
|
*/
|
|
@Immutable
|
|
interface TrustedAsciiMarshaller<T> {
|
|
/**
|
|
* Serialize a metadata value to a ASCII string that contains only the characters listed in the
|
|
* class comment of {@link io.grpc.Metadata.AsciiMarshaller}. Otherwise the output may be
|
|
* considered invalid and discarded by the transport, or the call may fail.
|
|
*
|
|
* @param value to serialize
|
|
* @return serialized version of value, or null if value cannot be transmitted.
|
|
*/
|
|
byte[] toAsciiString(T value);
|
|
|
|
/**
|
|
* Parse a serialized metadata value from an ASCII string.
|
|
*
|
|
* @param serialized value of metadata to parse
|
|
* @return a parsed instance of type T
|
|
*/
|
|
T parseAsciiString(byte[] serialized);
|
|
}
|
|
|
|
private static byte[] streamToBytes(InputStream stream) {
|
|
try {
|
|
return ByteStreams.toByteArray(stream);
|
|
} catch (IOException ioe) {
|
|
throw new RuntimeException("failure reading serialized stream", ioe);
|
|
}
|
|
}
|
|
}
|