类Metadata

提供对读取和写入元数据数值的访问,元数据数值在调用期间交换。

key 容许关联到多个值。

这个类不是线程安全,实现应该保证 header 的读取和写入不在多个线程中并发发生。

类定义

  1. package io.grpc;
  2. @NotThreadSafe
  3. public final class Metadata{}

类属性

  1. // 所有二进制 header 在他们的名字中应该有这个后缀。相反也是。
  2. // 它的值是"-bin"。ASCII header 的名字一定不能以这个结尾。
  3. public static final String BINARY_HEADER_SUFFIX = "-bin";
  4. // 保存的数据
  5. private byte[][] namesAndValues;
  6. // 当前未缩放的 header 数量
  7. private int size;

构造函数

  1. // 构造函数,当应用层想发送元数据时调用
  2. public Metadata() {}
  3. // 构造函数, 当传输层接收到二进制元数据时调用。
  4. Metadata(byte[]... binaryValues) {
  5. // 注意这里长度除以 2 了
  6. this(binaryValues.length / 2, binaryValues);
  7. }
  8. Metadata(int usedNames, byte[]... binaryValues) {
  9. size = usedNames;
  10. namesAndValues = binaryValues;
  11. }

和size相关的几个私有方法:

  1. len()

    1. private int len() {
    2. return size * 2
    3. }
  2. headerCount(): 返回在这个元数据中的键值 header 的总数量,包含重复

    1. public int headerCount() {
    2. return size;
    3. }

存取数据的方法

  1. containsKey(): 如果给定的key有值则返回true,注意这里没有检查value是否可能为空列表

    1. public boolean containsKey(Key<?> key) {
    2. for (int i = 0; i < size; i++) {
    3. if (bytesEqual(key.asciiName(), name(i))) {
    4. return true;
    5. }
    6. }
    7. return false;
    8. }

    注意这里是线性搜索,因此如果后面跟有 get() 或者 getAll() 方法,最好直接调用他们然后检查返回值是否为null。

  2. get(): 返回最后一个用给定name添加的元数据项,作为T解析,如果没有则返回null

    1. public < T > T get(Key < T > key) {
    2. for (int i = size - 1; i >= 0; i--) {
    3. if (bytesEqual(key.asciiName(), name(i))) {
    4. return key.parseBytes(value(i));
    5. }
    6. }
    7. return null;
    8. }
  3. getAll(): 返回所有用给定name添加的元数据项,作为T解析,如果没有则返回null。顺序和接收到的一致。

    1. public < T> Iterable< T> getAll(final Key< T> key) {
    2. for (int i = 0; i < size; i++) {
    3. if (bytesEqual(key.asciiName(), name(i))) {
    4. return new IterableAt<T>(key, i);
    5. }
    6. }
    7. return null;
    8. }

    这里一旦返现有 key 匹配,就直接new 一个 IterableAt 对象。后面对所有 同样 name 的游离是通过 IterableAt 来实现。

  4. keys(): 返回存储中的所有key的不可变集合

    1. @SuppressWarnings("deprecation") // The String ctor is deprecated, but fast.
    2. public Set<String> keys() {
    3. if (isEmpty()) {
    4. return Collections.emptySet();
    5. }
    6. Set<String> ks = new HashSet<String>(size);
    7. for (int i = 0; i < size; i++) {
    8. ks.add(new String(name(i), 0 /* hibyte */));
    9. }
    10. // immutable in case we decide to change the implementation later.
    11. return Collections.unmodifiableSet(ks);
    12. }

    实现逻辑很简单,但是有意思的是对 String 构造方法的使用。

  5. put(): 添加键值对。如果 key 已经有值,值被添加到最后。同一个key的重复的值是容许的。

    1. public < T > void put(Key< T> key, T value) {
    2. Preconditions.checkNotNull(key, "key");
    3. Preconditions.checkNotNull(value, "value");
    4. maybeExpand();
    5. name(size, key.asciiName());
    6. value(size, key.toBytes(value));
    7. size++;
    8. }
  6. remove(): 删除key的第一个出现的value,如果删除成功返回true,如果没有找到返回false

    1. public < T> boolean remove(Key< T> key, T value) {
    2. Preconditions.checkNotNull(key, "key");
    3. Preconditions.checkNotNull(value, "value");
    4. for (int i = 0; i < size; i++) {
    5. // 从头开始游历
    6. if (!bytesEqual(key.asciiName(), name(i))) {
    7. // 找 匹配的name
    8. continue;
    9. }
    10. @SuppressWarnings("unchecked")
    11. T stored = key.parseBytes(value(i));
    12. if (!value.equals(stored)) {
    13. // 如果 value 不匹配,跳过
    14. continue;
    15. }
    16. // name 和 value 都匹配了,准备做删除
    17. int writeIdx = i * 2;
    18. int readIdx = (i + 1) * 2;
    19. int readLen = len() - readIdx;
    20. // 将后面的数据向前复制
    21. System.arraycopy(namesAndValues, readIdx, namesAndValues, writeIdx, readLen);
    22. size -= 1;
    23. // 将最后一个位置的数据设置为null
    24. name(size, null);
    25. value(size, null);
    26. return true;
    27. }
    28. return false;
    29. }
  7. removeAll(): 删除给定key的所有值并返回这些被删除的值。如果没有找到值,则返回null

    1. public <T> Iterable<T> removeAll(final Key<T> key) {
    2. if (isEmpty()) {
    3. return null;
    4. }
    5. int writeIdx = 0;
    6. int readIdx = 0;
    7. List<T> ret = null;
    8. for (; readIdx < size; readIdx++) {
    9. if (bytesEqual(key.asciiName(), name(readIdx))) {
    10. ret = ret != null ? ret : new LinkedList<T>();
    11. ret.add(key.parseBytes(value(readIdx)));
    12. continue;
    13. }
    14. name(writeIdx, name(readIdx));
    15. value(writeIdx, value(readIdx));
    16. writeIdx++;
    17. }
    18. int newSize = writeIdx;
    19. // Multiply by two since namesAndValues is interleaved.
    20. Arrays.fill(namesAndValues, writeIdx * 2, len(), null);
    21. size = newSize;
    22. return ret;
    23. }
  8. discardAll(): 删除给定key的所有值但是不返回这些被删除的值。相比removeAll()方法有细微的性能提升,如果不需要使用之前的值。

内部定义的类

Marshaller

定义了两个 Marshaller 的interface,分别用于处理二进制和ASCII字符的值:

  1. BinaryMarshaller: 用于序列化为原始二进制的元数据值的装配器
  2. AsciiMarshaller: 用于序列化为 ASCII 字符的元数据值的装配器,值只包含下列字符:

    • 空格:0x20,但是不能在值的开头或者末尾。开始或者末尾的空白字符可能被去除。
    • ASCII 可见字符 (0x21-0x7E)

然后实现了这两个接口的内部匿名类:

  1. public static final BinaryMarshaller<byte[]> BINARY_BYTE_MARSHALLER = new BinaryMarshaller<byte[]>() {};
  2. public static final AsciiMarshaller<String> ASCII_STRING_MARSHALLER = new AsciiMarshaller<String>() {};
  3. static final AsciiMarshaller<Integer> INTEGER_MARSHALLER = new AsciiMarshaller<Integer>() {};

内部类 Metadata.Key

元数据项的key。考虑到元数据的解析和序列化。

key名称中的有效字符

在key的名字中仅容许下列ASCII字符:

  • 数字: 0-9
  • 大写字符: A-Z(标准化到小写)
  • 小写字符: a-z
  • 特殊字符: -_.

这是 HTTP 字段名规则的一个严格子集。应用不应该发送或者接收带有非法key名称的元数据。当然,gRPC 类库可能加工任何接收到的元数据,即使它不遵守上面的限制。此外,如果元数据包含不遵守的字段名,他们也将被发送。在这种方式下,未知元数据字段被解析,序列化和加工,但是从不中断。他们类似protobuf的未知字段。

注意,有一个有效的 HTTP/2 token字符的子集,被定义在 RFC7230 3.2.6 节和 RFC5234 B.1节。

  1. // 字段名的有效字符被定义在 RFC7230 和 RFC5234
  2. private static final BitSet VALID_T_CHARS = generateValidTChars();
  3. //在这里看到,仅有三个特殊字符,数字和小写字母是有效的
  4. private static BitSet generateValidTChars() {
  5. BitSet valid = new BitSet(0x7f);
  6. valid.set('-');
  7. valid.set('_');
  8. valid.set('.');
  9. for (char c = '0'; c <= '9'; c++) {
  10. valid.set(c);
  11. }
  12. // 仅在标准化之后验证,因此排除大写字母
  13. for (char c = 'a'; c <= 'z'; c++) {
  14. valid.set(c);
  15. }
  16. return valid;
  17. }

validateName()方法用于验证输入的字符串(会先转为小写)是否有效:

  1. private static String validateName(String n) {
  2. checkNotNull(n);
  3. checkArgument(n.length() != 0, "token must have at least 1 tchar");
  4. for (int i = 0; i < n.length(); i++) {
  5. char tChar = n.charAt(i);
  6. // TODO(notcarl): remove this hack once pseudo headers are properly handled
  7. if (tChar == ':' && i == 0) {
  8. continue;
  9. }
  10. checkArgument(VALID_T_CHARS.get(tChar),
  11. "Invalid character '%s' in key name '%s'", tChar, n);
  12. }
  13. return n;
  14. }

Key的类定义

  1. public abstract static class Key<T> {}

这是一个abstract类,后面将看到它的几个子类实现。

Key的属性和构造函数

  1. private final String originalName;
  2. private final String name;
  3. private final byte[] nameBytes;
  4. private Key(String name) {
  5. // 原始名字是构造函数中传递过来的原始输入
  6. this.originalName = checkNotNull(name);
  7. // Intern the result for faster string identity checking.
  8. // 有效名字是经过处理并验证有效的名字:这里的处理就是简单的改为小写
  9. this.name = validateName(this.originalName.toLowerCase(Locale.ROOT)).intern();
  10. // nameBytes是有效名字的字节数组
  11. this.nameBytes = this.name.getBytes(US_ASCII);
  12. }

Key的抽象方法

定义了两个方法用来做元数据和字节数组之间的转换和解析:

  1. // 将元数据序列化到byte数组
  2. abstract byte[] toBytes(T value);
  3. // 从byte数组解析被序列化的元数据值
  4. abstract T parseBytes(byte[] serialized);

Key的子类实现

  1. BinaryKey

    1. private static class BinaryKey<T> extends Key<T> {
    2. private final BinaryMarshaller<T> marshaller;
    3. // 构造函数传入一个BinaryMarshaller
    4. private BinaryKey(String name, BinaryMarshaller<T> marshaller) {
    5. ......
    6. this.marshaller = checkNotNull(marshaller, "marshaller is null");
    7. }
    8. @Override
    9. byte[] toBytes(T value) {
    10. // 使用这个BinaryMarshaller来转换 Key
    11. return marshaller.toBytes(value);
    12. }
    13. @Override
    14. T parseBytes(byte[] serialized) {
    15. // 使用这个BinaryMarshaller来解析 Key
    16. return marshaller.parseBytes(serialized);
    17. }
    18. }
  2. AsciiKey: 类似,这次使用的是 AsciiMarshaller