• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

grpc / grpc-java / #20245

17 Apr 2026 02:24PM UTC coverage: 88.788% (-0.02%) from 88.811%
#20245

push

github

web-flow
core: Reduce per-stream idle memory by 20%

Metadata was accidentally being retained after the start of the call.
That can be an overwhelming percentage of memory for an idle RPC; don't
do that. The other changes are considerably smaller, but I happened to
notice them and the changes are straight-forward without magic numbers
(e.g., there's many arrays that could be tuned).

The regular interop server uses 4600 bytes per full duplex stream while
idle, but much of that is Census recorded events hanging around. Keeping
the Census integration but removing the Census impl (so a noop is used)
drops that to 3000 bytes. This change brings that down to ~2450 bytes
(which is still including stuff from TestServiceImpl). But there's very
little Metadata in the interop tests, so absolute real-life savings
would be much higher (but relative real-life savings may be lower,
because the application will often have more state).

The measurements were captured using a modified
timeout_on_sleeping_server client that had 100,000 concurrent full
duplex calls on one connection.

36015 of 40563 relevant lines covered (88.79%)

0.89 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

91.86
/../api/src/main/java/io/grpc/Metadata.java
1
/*
2
 * Copyright 2014 The gRPC Authors
3
 *
4
 * Licensed under the Apache License, Version 2.0 (the "License");
5
 * you may not use this file except in compliance with the License.
6
 * You may obtain a copy of the License at
7
 *
8
 *     http://www.apache.org/licenses/LICENSE-2.0
9
 *
10
 * Unless required by applicable law or agreed to in writing, software
11
 * distributed under the License is distributed on an "AS IS" BASIS,
12
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
 * See the License for the specific language governing permissions and
14
 * limitations under the License.
15
 */
16

17
package io.grpc;
18

19
import static com.google.common.base.Preconditions.checkArgument;
20
import static com.google.common.base.Preconditions.checkNotNull;
21
import static java.nio.charset.StandardCharsets.US_ASCII;
22

23
import com.google.common.annotations.VisibleForTesting;
24
import com.google.common.base.Preconditions;
25
import com.google.common.collect.Maps;
26
import com.google.common.collect.Sets;
27
import com.google.common.io.BaseEncoding;
28
import com.google.common.io.ByteStreams;
29
import java.io.ByteArrayInputStream;
30
import java.io.IOException;
31
import java.io.InputStream;
32
import java.nio.ByteBuffer;
33
import java.util.ArrayList;
34
import java.util.Arrays;
35
import java.util.BitSet;
36
import java.util.Collections;
37
import java.util.Iterator;
38
import java.util.List;
39
import java.util.Locale;
40
import java.util.Map;
41
import java.util.NoSuchElementException;
42
import java.util.Set;
43
import java.util.logging.Level;
44
import java.util.logging.Logger;
45
import javax.annotation.Nullable;
46
import javax.annotation.concurrent.Immutable;
47
import javax.annotation.concurrent.NotThreadSafe;
48

49
/**
50
 * Provides access to read and write metadata values to be exchanged during a call.
51
 *
52
 * <p>Keys are allowed to be associated with more than one value.
53
 *
54
 * <p>This class is not thread safe, implementations should ensure that header reads and writes do
55
 * not occur in multiple threads concurrently.
56
 */
57
@NotThreadSafe
58
public final class Metadata {
59
  private static final Logger logger = Logger.getLogger(Metadata.class.getName());
1✔
60

61
  /**
62
   * All binary headers should have this suffix in their names. Vice versa.
63
   *
64
   * <p>Its value is {@code "-bin"}. An ASCII header's name must not end with this.
65
   */
66
  public static final String BINARY_HEADER_SUFFIX = "-bin";
67

68
  /**
69
   * Simple metadata marshaller that encodes bytes as is.
70
   *
71
   * <p>This should be used when raw bytes are favored over un-serialized version of object. Can be
72
   * helpful in situations where more processing to bytes is needed on application side, avoids
73
   * double encoding/decoding.
74
   *
75
   * <p>Both {@link BinaryMarshaller#toBytes} and {@link BinaryMarshaller#parseBytes} methods do not
76
   * return a copy of the byte array. Do _not_ modify the byte arrays of either the arguments or
77
   * return values.
78
   */
79
  public static final BinaryMarshaller<byte[]> BINARY_BYTE_MARSHALLER =
1✔
80
      new BinaryMarshaller<byte[]>() {
1✔
81

82
        @Override
83
        public byte[] toBytes(byte[] value) {
84
          return value;
1✔
85
        }
86

87
        @Override
88
        public byte[] parseBytes(byte[] serialized) {
89
          return serialized;
1✔
90
        }
91
      };
92

93
  /**
94
   * Simple metadata marshaller that encodes strings as is.
95
   *
96
   * <p>This should be used with ASCII strings that only contain the characters listed in the class
97
   * comment of {@link AsciiMarshaller}. Otherwise the output may be considered invalid and
98
   * discarded by the transport, or the call may fail.
99
   */
100
  public static final AsciiMarshaller<String> ASCII_STRING_MARSHALLER =
1✔
101
      new AsciiMarshaller<String>() {
1✔
102

103
        @Override
104
        public String toAsciiString(String value) {
105
          return value;
1✔
106
        }
107

108
        @Override
109
        public String parseAsciiString(String serialized) {
110
          return serialized;
1✔
111
        }
112
      };
113

114
  static final BaseEncoding BASE64_ENCODING_OMIT_PADDING = BaseEncoding.base64().omitPadding();
1✔
115

116
  /**
117
   * Constructor called by the transport layer when it receives binary metadata. Metadata will
118
   * mutate the passed in array.
119
   */
120
  Metadata(byte[]... binaryValues) {
121
    this(binaryValues.length / 2, binaryValues);
1✔
122
  }
1✔
123

124
  /**
125
   * Constructor called by the transport layer when it receives binary metadata. Metadata will
126
   * mutate the passed in array.
127
   *
128
   * @param usedNames the number of names
129
   */
130
  Metadata(int usedNames, byte[]... binaryValues) {
131
    this(usedNames, (Object[]) binaryValues);
1✔
132
  }
1✔
133

134
  /**
135
   * Constructor called by the transport layer when it receives partially-parsed metadata.
136
   * Metadata will mutate the passed in array.
137
   *
138
   * @param usedNames the number of names
139
   * @param namesAndValues an array of interleaved names and values, with each name
140
   *     (at even indices) represented by a byte array, and values (at odd indices) as
141
   *     described by {@link InternalMetadata#newMetadataWithParsedValues}.
142
   */
143
  Metadata(int usedNames, Object[] namesAndValues) {
1✔
144
    assert (namesAndValues.length & 1) == 0
1✔
145
        : "Odd number of key-value pairs " + namesAndValues.length;
146
    size = usedNames;
1✔
147
    this.namesAndValues = namesAndValues;
1✔
148
  }
1✔
149

150
  private Object[] namesAndValues;
151
  // The unscaled number of headers present.
152
  private int size;
153

154
  private byte[] name(int i) {
155
    return (byte[]) namesAndValues[i * 2];
1✔
156
  }
157

158
  private void name(int i, byte[] name) {
159
    namesAndValues[i * 2] = name;
1✔
160
  }
1✔
161

162
  private Object value(int i) {
163
    return namesAndValues[i * 2 + 1];
1✔
164
  }
165

166
  private void value(int i, byte[] value) {
167
    namesAndValues[i * 2 + 1] = value;
1✔
168
  }
1✔
169

170
  private void value(int i, Object value) {
171
    if (namesAndValues instanceof byte[][]) {
1✔
172
      // Reallocate an array of Object.
173
      expand(cap());
1✔
174
    }
175
    namesAndValues[i * 2 + 1] = value;
1✔
176
  }
1✔
177

178
  private byte[] valueAsBytes(int i) {
179
    Object value = value(i);
1✔
180
    if (value instanceof byte[]) {
1✔
181
      return (byte[]) value;
1✔
182
    } else {
183
      return ((LazyValue<?>) value).toBytes();
1✔
184
    }
185
  }
186

187
  private Object valueAsBytesOrStream(int i) {
188
    Object value = value(i);
1✔
189
    if (value instanceof byte[]) {
1✔
190
      return value;
1✔
191
    } else {
192
      return ((LazyValue<?>) value).toStream();
1✔
193
    }
194
  }
195

196
  private <T> T valueAsT(int i, Key<T> key) {
197
    Object value = value(i);
1✔
198
    if (value instanceof byte[]) {
1✔
199
      return key.parseBytes((byte[]) value);
1✔
200
    } else {
201
      return ((LazyValue<?>) value).toObject(key);
1✔
202
    }
203
  }
204

205
  private int cap() {
206
    return namesAndValues != null ? namesAndValues.length : 0;
1✔
207
  }
208

209
  // The scaled version of size.
210
  private int len() {
211
    return size * 2;
1✔
212
  }
213

214
  /** checks when {@link #namesAndValues} is null or has no elements. */
215
  private boolean isEmpty() {
216
    return size == 0;
1✔
217
  }
218

219
  /** Constructor called by the application layer when it wants to send metadata. */
220
  public Metadata() {}
1✔
221

222
  /** Returns the total number of key-value headers in this metadata, including duplicates. */
223
  int headerCount() {
224
    return size;
1✔
225
  }
226

227
  /**
228
   * Returns true if a value is defined for the given key.
229
   *
230
   * <p>This is done by linear search, so if it is followed by {@link #get} or {@link #getAll},
231
   * prefer calling them directly and checking the return value against {@code null}.
232
   */
233
  public boolean containsKey(Key<?> key) {
234
    for (int i = 0; i < size; i++) {
1✔
235
      if (bytesEqual(key.asciiName(), name(i))) {
1✔
236
        return true;
1✔
237
      }
238
    }
239
    return false;
1✔
240
  }
241

242
  /**
243
   * Returns the last metadata entry added with the name 'name' parsed as T.
244
   *
245
   * @return the parsed metadata entry or null if there are none.
246
   */
247
  @Nullable
248
  public <T> T get(Key<T> key) {
249
    for (int i = size - 1; i >= 0; i--) {
1✔
250
      if (bytesEqual(key.asciiName(), name(i))) {
1✔
251
        return valueAsT(i, key);
1✔
252
      }
253
    }
254
    return null;
1✔
255
  }
256

257
  private final class IterableAt<T> implements Iterable<T> {
258
    private final Key<T> key;
259
    private int startIdx;
260

261
    private IterableAt(Key<T> key, int startIdx) {
1✔
262
      this.key = key;
1✔
263
      this.startIdx = startIdx;
1✔
264
    }
1✔
265

266
    @Override
267
    public Iterator<T> iterator() {
268
      return new Iterator<T>() {
1✔
269
        private boolean hasNext = true;
1✔
270
        private int idx = startIdx;
1✔
271

272
        @Override
273
        public boolean hasNext() {
274
          if (hasNext) {
1✔
275
            return true;
1✔
276
          }
277
          for (; idx < size; idx++) {
1✔
278
            if (bytesEqual(key.asciiName(), name(idx))) {
1✔
279
              hasNext = true;
1✔
280
              return hasNext;
1✔
281
            }
282
          }
283
          return false;
1✔
284
        }
285

286
        @Override
287
        public T next() {
288
          if (hasNext()) {
1✔
289
            hasNext = false;
1✔
290
            return valueAsT(idx++, key);
1✔
291
          }
292
          throw new NoSuchElementException();
×
293
        }
294

295
        @Override
296
        public void remove() {
297
          throw new UnsupportedOperationException();
1✔
298
        }
299
      };
300
    }
301
  }
302

303
  /**
304
   * Returns all the metadata entries named 'name', in the order they were received, parsed as T, or
305
   * null if there are none. The iterator is not guaranteed to be "live." It may or may not be
306
   * accurate if Metadata is mutated.
307
   */
308
  @Nullable
309
  public <T> Iterable<T> getAll(final Key<T> key) {
310
    for (int i = 0; i < size; i++) {
1✔
311
      if (bytesEqual(key.asciiName(), name(i))) {
1✔
312
        return new IterableAt<>(key, i);
1✔
313
      }
314
    }
315
    return null;
1✔
316
  }
317

318
  /**
319
   * Returns set of all keys in store.
320
   *
321
   * @return unmodifiable Set of keys
322
   */
323
  @SuppressWarnings("deprecation") // The String ctor is deprecated, but fast.
324
  public Set<String> keys() {
325
    if (isEmpty()) {
1✔
326
      return Collections.emptySet();
1✔
327
    }
328
    Set<String> ks = Sets.newHashSetWithExpectedSize(size);
1✔
329
    for (int i = 0; i < size; i++) {
1✔
330
      ks.add(new String(name(i), 0 /* hibyte */));
1✔
331
    }
332
    // immutable in case we decide to change the implementation later.
333
    return Collections.unmodifiableSet(ks);
1✔
334
  }
335

336
  /**
337
   * Adds the {@code key, value} pair. If {@code key} already has values, {@code value} is added to
338
   * the end. Duplicate values for the same key are permitted.
339
   *
340
   * @throws NullPointerException if key or value is null
341
   */
342
  public <T> void put(Key<T> key, T value) {
343
    Preconditions.checkNotNull(key, "key");
1✔
344
    Preconditions.checkNotNull(value, "value");
1✔
345
    maybeExpand();
1✔
346
    name(size, key.asciiName());
1✔
347
    if (key.serializesToStreams()) {
1✔
348
      value(size, LazyValue.create(key, value));
1✔
349
    } else {
350
      value(size, key.toBytes(value));
1✔
351
    }
352
    size++;
1✔
353
  }
1✔
354

355
  private void maybeExpand() {
356
    if (len() == 0 || len() == cap()) {
1✔
357
      expand(Math.max(len() * 2, 8));
1✔
358
    }
359
  }
1✔
360

361
  // Expands to exactly the desired capacity.
362
  private void expand(int newCapacity) {
363
    Object[] newNamesAndValues = new Object[newCapacity];
1✔
364
    if (!isEmpty()) {
1✔
365
      System.arraycopy(namesAndValues, 0, newNamesAndValues, 0, len());
1✔
366
    }
367
    namesAndValues = newNamesAndValues;
1✔
368
  }
1✔
369

370
  /**
371
   * Removes the first occurrence of {@code value} for {@code key}.
372
   *
373
   * @param key key for value
374
   * @param value value
375
   * @return {@code true} if {@code value} removed; {@code false} if {@code value} was not present
376
   * @throws NullPointerException if {@code key} or {@code value} is null
377
   */
378
  public <T> boolean remove(Key<T> key, T value) {
379
    Preconditions.checkNotNull(key, "key");
1✔
380
    Preconditions.checkNotNull(value, "value");
1✔
381
    for (int i = 0; i < size; i++) {
1✔
382
      if (!bytesEqual(key.asciiName(), name(i))) {
1✔
383
        continue;
×
384
      }
385
      T stored = valueAsT(i, key);
1✔
386
      if (!value.equals(stored)) {
1✔
387
        continue;
1✔
388
      }
389
      int writeIdx = i * 2;
1✔
390
      int readIdx = (i + 1) * 2;
1✔
391
      int readLen = len() - readIdx;
1✔
392
      System.arraycopy(namesAndValues, readIdx, namesAndValues, writeIdx, readLen);
1✔
393
      size -= 1;
1✔
394
      name(size, null);
1✔
395
      value(size, (byte[]) null);
1✔
396
      return true;
1✔
397
    }
398
    return false;
1✔
399
  }
400

401
  /** Remove all values for the given key. If there were no values, {@code null} is returned. */
402
  public <T> Iterable<T> removeAll(Key<T> key) {
403
    if (isEmpty()) {
1✔
404
      return null;
1✔
405
    }
406
    int writeIdx = 0;
1✔
407
    int readIdx = 0;
1✔
408
    List<T> ret = null;
1✔
409
    for (; readIdx < size; readIdx++) {
1✔
410
      if (bytesEqual(key.asciiName(), name(readIdx))) {
1✔
411
        ret = ret != null ? ret : new ArrayList<T>();
1✔
412
        ret.add(valueAsT(readIdx, key));
1✔
413
        continue;
1✔
414
      }
415
      name(writeIdx, name(readIdx));
×
416
      value(writeIdx, value(readIdx));
×
417
      writeIdx++;
×
418
    }
419
    int newSize = writeIdx;
1✔
420
    // Multiply by two since namesAndValues is interleaved.
421
    Arrays.fill(namesAndValues, writeIdx * 2, len(), null);
1✔
422
    size = newSize;
1✔
423
    return ret;
1✔
424
  }
425

426
  /**
427
   * Remove all values for the given key without returning them. This is a minor performance
428
   * optimization if you do not need the previous values.
429
   */
430
  @ExperimentalApi("https://github.com/grpc/grpc-java/issues/4691")
431
  public <T> void discardAll(Key<T> key) {
432
    if (isEmpty()) {
1✔
433
      return;
1✔
434
    }
435
    int writeIdx = 0;
1✔
436
    int readIdx = 0;
1✔
437
    for (; readIdx < size; readIdx++) {
1✔
438
      if (bytesEqual(key.asciiName(), name(readIdx))) {
1✔
439
        continue;
1✔
440
      }
441
      name(writeIdx, name(readIdx));
1✔
442
      value(writeIdx, value(readIdx));
1✔
443
      writeIdx++;
1✔
444
    }
445
    int newSize = writeIdx;
1✔
446
    // Multiply by two since namesAndValues is interleaved.
447
    Arrays.fill(namesAndValues, writeIdx * 2, len(), null);
1✔
448
    size = newSize;
1✔
449
  }
1✔
450

451
  /**
452
   * Serialize all the metadata entries.
453
   *
454
   * <p>It produces serialized names and values interleaved. result[i*2] are names, while
455
   * result[i*2+1] are values.
456
   *
457
   * <p>Names are ASCII string bytes that contains only the characters listed in the class comment
458
   * of {@link Key}. If the name ends with {@code "-bin"}, the value can be raw binary. Otherwise,
459
   * the value must contain only characters listed in the class comments of {@link AsciiMarshaller}
460
   *
461
   * <p>The returned individual byte arrays <em>must not</em> be modified. However, the top level
462
   * array may be modified.
463
   *
464
   * <p>This method is intended for transport use only.
465
   */
466
  @Nullable
467
  byte[][] serialize() {
468
    byte[][] serialized = new byte[len()][];
1✔
469
    if (namesAndValues instanceof byte[][]) {
1✔
470
      System.arraycopy(namesAndValues, 0, serialized, 0, len());
1✔
471
    } else {
472
      for (int i = 0; i < size; i++) {
1✔
473
        serialized[i * 2] = name(i);
1✔
474
        serialized[i * 2 + 1] = valueAsBytes(i);
1✔
475
      }
476
    }
477
    return serialized;
1✔
478
  }
479

480
  /**
481
   * Serializes all metadata entries, leaving some values as {@link InputStream}s.
482
   *
483
   * <p>Produces serialized names and values interleaved. result[i*2] are names, while
484
   * result[i*2+1] are values.
485
   *
486
   * <p>Names are byte arrays as described according to the {@link #serialize}
487
   * method. Values are either byte arrays or {@link InputStream}s.
488
   *
489
   * <p>This method is intended for transport use only.
490
   */
491
  @Nullable
492
  Object[] serializePartial() {
493
    Object[] serialized = new Object[len()];
1✔
494
    for (int i = 0; i < size; i++) {
1✔
495
      serialized[i * 2] = name(i);
1✔
496
      serialized[i * 2 + 1] = valueAsBytesOrStream(i);
1✔
497
    }
498
    return serialized;
1✔
499
  }
500

501
  /**
502
   * Perform a simple merge of two sets of metadata.
503
   *
504
   * <p>This is a purely additive operation, because a single key can be associated with multiple
505
   * values.
506
   */
507
  public void merge(Metadata other) {
508
    if (other.isEmpty()) {
1✔
509
      return;
1✔
510
    }
511
    int remaining = cap() - len();
1✔
512
    if (isEmpty() || remaining < other.len()) {
1✔
513
      expand(len() + other.len());
1✔
514
    }
515
    System.arraycopy(other.namesAndValues, 0, namesAndValues, len(), other.len());
1✔
516
    size += other.size;
1✔
517
  }
1✔
518

519
  /**
520
   * Merge values from the given set of keys into this set of metadata. If a key is present in keys,
521
   * then all of the associated values will be copied over.
522
   *
523
   * @param other The source of the new key values.
524
   * @param keys The subset of matching key we want to copy, if they exist in the source.
525
   */
526
  public void merge(Metadata other, Set<Key<?>> keys) {
527
    Preconditions.checkNotNull(other, "other");
×
528
    // Use ByteBuffer for equals and hashCode.
529
    Map<ByteBuffer, Key<?>> asciiKeys = Maps.newHashMapWithExpectedSize(keys.size());
×
530
    for (Key<?> key : keys) {
×
531
      asciiKeys.put(ByteBuffer.wrap(key.asciiName()), key);
×
532
    }
×
533
    for (int i = 0; i < other.size; i++) {
×
534
      ByteBuffer wrappedNamed = ByteBuffer.wrap(other.name(i));
×
535
      if (asciiKeys.containsKey(wrappedNamed)) {
×
536
        maybeExpand();
×
537
        name(size, other.name(i));
×
538
        value(size, other.value(i));
×
539
        size++;
×
540
      }
541
    }
542
  }
×
543

544
  @Override
545
  public String toString() {
546
    StringBuilder sb = new StringBuilder("Metadata(");
1✔
547
    for (int i = 0; i < size; i++) {
1✔
548
      if (i != 0) {
1✔
549
        sb.append(',');
1✔
550
      }
551
      String headerName = new String(name(i), US_ASCII);
1✔
552
      sb.append(headerName).append('=');
1✔
553
      if (headerName.endsWith(BINARY_HEADER_SUFFIX)) {
1✔
554
        sb.append(BASE64_ENCODING_OMIT_PADDING.encode(valueAsBytes(i)));
1✔
555
      } else {
556
        String headerValue = new String(valueAsBytes(i), US_ASCII);
1✔
557
        sb.append(headerValue);
1✔
558
      }
559
    }
560
    return sb.append(')').toString();
1✔
561
  }
562

563
  private boolean bytesEqual(byte[] left, byte[] right) {
564
    return Arrays.equals(left, right);
1✔
565
  }
566

567
  /** Marshaller for metadata values that are serialized into raw binary. */
568
  public interface BinaryMarshaller<T> {
569
    /**
570
     * Serialize a metadata value to bytes.
571
     *
572
     * @param value to serialize
573
     * @return serialized version of value
574
     */
575
    byte[] toBytes(T value);
576

577
    /**
578
     * Parse a serialized metadata value from bytes.
579
     *
580
     * @param serialized value of metadata to parse
581
     * @return a parsed instance of type T
582
     */
583
    T parseBytes(byte[] serialized);
584
  }
585

586
  /**
587
   * Marshaller for metadata values that are serialized into ASCII strings. The strings contain only
588
   * following characters:
589
   *
590
   * <ul>
591
   * <li>Space: {@code 0x20}, but must not be at the beginning or at the end of the value. Leading
592
   *     or trailing whitespace may not be preserved.
593
   * <li>ASCII visible characters ({@code 0x21-0x7E}).
594
   * </ul>
595
   *
596
   * <p>Note this has to be the subset of valid characters in {@code field-content} from RFC 7230
597
   * Section 3.2.
598
   */
599
  public interface AsciiMarshaller<T> {
600
    /**
601
     * Serialize a metadata value to a ASCII string that contains only the characters listed in the
602
     * class comment of {@link AsciiMarshaller}. Otherwise the output may be considered invalid and
603
     * discarded by the transport, or the call may fail.
604
     *
605
     * @param value to serialize
606
     * @return serialized version of value, or null if value cannot be transmitted.
607
     */
608
    String toAsciiString(T value);
609

610
    /**
611
     * Parse a serialized metadata value from an ASCII string.
612
     *
613
     * @param serialized value of metadata to parse
614
     * @return a parsed instance of type T
615
     */
616
    T parseAsciiString(String serialized);
617
  }
618

619
  /** Marshaller for metadata values that are serialized to an InputStream. */
620
  @ExperimentalApi("https://github.com/grpc/grpc-java/issues/6575")
621
  public interface BinaryStreamMarshaller<T> {
622
    /**
623
     * Serializes a metadata value to an {@link InputStream}.
624
     *
625
     * @param value to serialize
626
     * @return serialized version of value
627
     */
628
    InputStream toStream(T value);
629

630
    /**
631
     * Parses a serialized metadata value from an {@link InputStream}.
632
     *
633
     * @param stream of metadata to parse
634
     * @return a parsed instance of type T
635
     */
636
    T parseStream(InputStream stream);
637
  }
638

639
  /**
640
   * Key for metadata entries. Allows for parsing and serialization of metadata.
641
   *
642
   * <h3>Valid characters in key names</h3>
643
   *
644
   * <p>Only the following ASCII characters are allowed in the names of keys:
645
   *
646
   * <ul>
647
   * <li>digits: {@code 0-9}
648
   * <li>uppercase letters: {@code A-Z} (normalized to lower)
649
   * <li>lowercase letters: {@code a-z}
650
   * <li>special characters: {@code -_.}
651
   * </ul>
652
   *
653
   * <p>This is a strict subset of the HTTP field-name rules. Applications may not send or receive
654
   * metadata with invalid key names. However, the gRPC library may preserve any metadata received
655
   * even if it does not conform to the above limitations. Additionally, if metadata contains non
656
   * conforming field names, they will still be sent. In this way, unknown metadata fields are
657
   * parsed, serialized and preserved, but never interpreted. They are similar to protobuf unknown
658
   * fields.
659
   *
660
   * <p>Note this has to be the subset of valid HTTP/2 token characters as defined in RFC7230
661
   * Section 3.2.6 and RFC5234 Section B.1
662
   *
663
   * <p>Note that a key is immutable but it may not be deeply immutable, because the key depends on
664
   * its marshaller, and the marshaller can be mutable though not recommended.
665
   *
666
   * @see <a href="https://github.com/grpc/grpc/blob/master/doc/PROTOCOL-HTTP2.md">Wire Spec</a>
667
   * @see <a href="https://tools.ietf.org/html/rfc7230#section-3.2.6">RFC7230</a>
668
   * @see <a href="https://tools.ietf.org/html/rfc5234#appendix-B.1">RFC5234</a>
669
   */
670
  @Immutable
671
  public abstract static class Key<T> {
672

673
    /** Valid characters for field names as defined in RFC7230 and RFC5234. */
674
    private static final BitSet VALID_T_CHARS = generateValidTChars();
1✔
675

676
    /**
677
     * Creates a key for a binary header.
678
     *
679
     * @param name Must contain only the valid key characters as defined in the class comment. Must
680
     *     end with {@link #BINARY_HEADER_SUFFIX}.
681
     */
682
    public static <T> Key<T> of(String name, BinaryMarshaller<T> marshaller) {
683
      return new BinaryKey<>(name, marshaller);
1✔
684
    }
685

686
    /**
687
     * Creates a key for a binary header, serializing to input streams.
688
     *
689
     * @param name Must contain only the valid key characters as defined in the class comment. Must
690
     *     end with {@link #BINARY_HEADER_SUFFIX}.
691
     */
692
    @ExperimentalApi("https://github.com/grpc/grpc-java/issues/6575")
693
    public static <T> Key<T> of(String name, BinaryStreamMarshaller<T> marshaller) {
694
      return new LazyStreamBinaryKey<>(name, marshaller);
1✔
695
    }
696

697
    /**
698
     * Creates a key for an ASCII header.
699
     *
700
     * @param name Must contain only the valid key characters as defined in the class comment. Must
701
     *     <b>not</b> end with {@link #BINARY_HEADER_SUFFIX}
702
     */
703
    public static <T> Key<T> of(String name, AsciiMarshaller<T> marshaller) {
704
      return of(name, false, marshaller);
1✔
705
    }
706

707
    static <T> Key<T> of(String name, boolean pseudo, AsciiMarshaller<T> marshaller) {
708
      return new AsciiKey<>(name, pseudo, marshaller);
1✔
709
    }
710

711
    static <T> Key<T> of(String name, boolean pseudo, TrustedAsciiMarshaller<T> marshaller) {
712
      return new TrustedAsciiKey<>(name, pseudo, marshaller);
1✔
713
    }
714

715
    private final String originalName;
716

717
    private final String name;
718
    private final byte[] nameBytes;
719
    private final Object marshaller;
720

721
    private static BitSet generateValidTChars() {
722
      BitSet valid = new BitSet(0x7f);
1✔
723
      valid.set('-');
1✔
724
      valid.set('_');
1✔
725
      valid.set('.');
1✔
726
      for (char c = '0'; c <= '9'; c++) {
1✔
727
        valid.set(c);
1✔
728
      }
729
      // Only validates after normalization, so we exclude uppercase.
730
      for (char c = 'a'; c <= 'z'; c++) {
1✔
731
        valid.set(c);
1✔
732
      }
733
      return valid;
1✔
734
    }
735

736
    private static String validateName(String n, boolean pseudo) {
737
      checkNotNull(n, "name");
1✔
738
      checkArgument(!n.isEmpty(), "token must have at least 1 tchar");
1✔
739
      if (n.equals("connection")) {
1✔
740
        logger.log(
×
741
            Level.WARNING,
742
            "Metadata key is 'Connection', which should not be used. That is used by HTTP/1 for "
743
            + "connection-specific headers which are not to be forwarded. There is probably an "
744
            + "HTTP/1 conversion bug. Simply removing the Connection header is not enough; you "
745
            + "should remove all headers it references as well. See RFC 7230 section 6.1",
746
            new RuntimeException("exception to show backtrace"));
747
      }
748
      for (int i = 0; i < n.length(); i++) {
1✔
749
        char tChar = n.charAt(i);
1✔
750
        if (pseudo && tChar == ':' && i == 0) {
1✔
751
          continue;
1✔
752
        }
753

754
        checkArgument(
1✔
755
            VALID_T_CHARS.get(tChar), "Invalid character '%s' in key name '%s'", tChar, n);
1✔
756
      }
757
      return n;
1✔
758
    }
759

760
    private Key(String name, boolean pseudo, Object marshaller) {
1✔
761
      this.originalName = checkNotNull(name, "name");
1✔
762
      this.name = validateName(this.originalName.toLowerCase(Locale.ROOT), pseudo);
1✔
763
      this.nameBytes = this.name.getBytes(US_ASCII);
1✔
764
      this.marshaller = marshaller;
1✔
765
    }
1✔
766

767
    /**
768
     * Returns the original name used to create this key.
769
     */
770
    public final String originalName() {
771
      return originalName;
1✔
772
    }
773

774
    /**
775
     * Returns the normalized name for this key.
776
     */
777
    public final String name() {
778
      return name;
1✔
779
    }
780

781
    /**
782
     * Get the name as bytes using ASCII-encoding.
783
     *
784
     * <p>The returned byte arrays <em>must not</em> be modified.
785
     *
786
     * <p>This method is intended for transport use only.
787
     */
788
    // TODO (louiscryan): Migrate to ByteString
789
    @VisibleForTesting
790
    byte[] asciiName() {
791
      return nameBytes;
1✔
792
    }
793

794
    /**
795
     * Returns true if the two objects are both Keys, and their names match (case insensitive).
796
     */
797
    @SuppressWarnings("EqualsGetClass")
798
    @Override
799
    public final boolean equals(Object o) {
800
      if (this == o) {
1✔
801
        return true;
1✔
802
      }
803
      if (o == null || getClass() != o.getClass()) {
1✔
804
        return false;
1✔
805
      }
806
      Key<?> key = (Key<?>) o;
1✔
807
      return name.equals(key.name);
1✔
808
    }
809

810
    @Override
811
    public final int hashCode() {
812
      return name.hashCode();
1✔
813
    }
814

815
    @Override
816
    public String toString() {
817
      return "Key{name='" + name + "'}";
1✔
818
    }
819

820
    /**
821
     * Serialize a metadata value to bytes.
822
     *
823
     * @param value to serialize
824
     * @return serialized version of value
825
     */
826
    abstract byte[] toBytes(T value);
827

828
    /**
829
     * Parse a serialized metadata value from bytes.
830
     *
831
     * @param serialized value of metadata to parse
832
     * @return a parsed instance of type T
833
     */
834
    abstract T parseBytes(byte[] serialized);
835

836
    /**
837
     * Returns whether this key will be serialized to bytes lazily.
838
     */
839
    boolean serializesToStreams() {
840
      return false;
1✔
841
    }
842

843
    /**
844
     * Gets this keys (implementation-specific) marshaller, or null if the
845
     * marshaller is not of the given type.
846
     *
847
     * @param marshallerClass The type we expect the marshaller to be.
848
     * @return the marshaller object for this key, or null.
849
     */
850
    @Nullable
851
    final <M> M getMarshaller(Class<M> marshallerClass) {
852
      if (marshallerClass.isInstance(marshaller)) {
1✔
853
        return marshallerClass.cast(marshaller);
1✔
854
      }
855
      return null;
×
856
    }
857
  }
858

859
  private static class BinaryKey<T> extends Key<T> {
860
    private final BinaryMarshaller<T> marshaller;
861

862
    /** Keys have a name and a binary marshaller used for serialization. */
863
    private BinaryKey(String name, BinaryMarshaller<T> marshaller) {
864
      super(name, false /* not pseudo */, marshaller);
1✔
865
      checkArgument(
1✔
866
          name.endsWith(BINARY_HEADER_SUFFIX),
1✔
867
          "Binary header is named %s. It must end with %s",
868
          name,
869
          BINARY_HEADER_SUFFIX);
870
      checkArgument(name.length() > BINARY_HEADER_SUFFIX.length(), "empty key name");
1✔
871
      this.marshaller = checkNotNull(marshaller, "marshaller is null");
1✔
872
    }
1✔
873

874
    @Override
875
    byte[] toBytes(T value) {
876
      return Preconditions.checkNotNull(marshaller.toBytes(value), "null marshaller.toBytes()");
1✔
877
    }
878

879
    @Override
880
    T parseBytes(byte[] serialized) {
881
      return marshaller.parseBytes(serialized);
1✔
882
    }
883
  }
884

885
  /** A binary key for values which should be serialized lazily to {@link InputStream}s. */
886
  private static class LazyStreamBinaryKey<T> extends Key<T> {
887

888
    private final BinaryStreamMarshaller<T> marshaller;
889

890
    /** Keys have a name and a stream marshaller used for serialization. */
891
    private LazyStreamBinaryKey(String name, BinaryStreamMarshaller<T> marshaller) {
892
      super(name, false /* not pseudo */, marshaller);
1✔
893
      checkArgument(
1✔
894
          name.endsWith(BINARY_HEADER_SUFFIX),
1✔
895
          "Binary header is named %s. It must end with %s",
896
          name,
897
          BINARY_HEADER_SUFFIX);
898
      checkArgument(name.length() > BINARY_HEADER_SUFFIX.length(), "empty key name");
1✔
899
      this.marshaller = checkNotNull(marshaller, "marshaller is null");
1✔
900
    }
1✔
901

902
    @Override
903
    byte[] toBytes(T value) {
904
      return streamToBytes(checkNotNull(marshaller.toStream(value), "null marshaller.toStream()"));
×
905
    }
906

907
    @Override
908
    T parseBytes(byte[] serialized) {
909
      return marshaller.parseStream(new ByteArrayInputStream(serialized));
1✔
910
    }
911

912
    @Override
913
    boolean serializesToStreams() {
914
      return true;
1✔
915
    }
916
  }
917

918
  /** Internal holder for values which are serialized/de-serialized lazily. */
919
  static final class LazyValue<T> {
920
    private final BinaryStreamMarshaller<T> marshaller;
921
    private final T value;
922
    private volatile byte[] serialized;
923

924
    static <T> LazyValue<T> create(Key<T> key, T value) {
925
      return new LazyValue<>(checkNotNull(getBinaryStreamMarshaller(key)), value);
1✔
926
    }
927

928
    /** A value set by the application. */
929
    LazyValue(BinaryStreamMarshaller<T> marshaller, T value) {
1✔
930
      this.marshaller = marshaller;
1✔
931
      this.value = value;
1✔
932
    }
1✔
933

934
    InputStream toStream() {
935
      return checkNotNull(marshaller.toStream(value), "null marshaller.toStream()");
1✔
936
    }
937

938
    byte[] toBytes() {
939
      if (serialized == null) {
1✔
940
        synchronized (this) {
1✔
941
          if (serialized == null) {
1✔
942
            serialized = streamToBytes(toStream());
1✔
943
          }
944
        }
1✔
945
      }
946
      return serialized;
1✔
947
    }
948

949
    <T2> T2 toObject(Key<T2> key) {
950
      if (key.serializesToStreams()) {
1✔
951
        BinaryStreamMarshaller<T2> marshaller = getBinaryStreamMarshaller(key);
1✔
952
        if (marshaller != null) {
1✔
953
          return marshaller.parseStream(toStream());
1✔
954
        }
955
      }
956
      return key.parseBytes(toBytes());
×
957
    }
958

959
    @Nullable
960
    @SuppressWarnings("unchecked")
961
    private static <T> BinaryStreamMarshaller<T> getBinaryStreamMarshaller(Key<T> key) {
962
      return (BinaryStreamMarshaller<T>) key.getMarshaller(BinaryStreamMarshaller.class);
1✔
963
    }
964
  }
965

966
  private static class AsciiKey<T> extends Key<T> {
967
    private final AsciiMarshaller<T> marshaller;
968

969
    /** Keys have a name and an ASCII marshaller used for serialization. */
970
    private AsciiKey(String name, boolean pseudo, AsciiMarshaller<T> marshaller) {
971
      super(name, pseudo, marshaller);
1✔
972
      Preconditions.checkArgument(
1✔
973
          !name.endsWith(BINARY_HEADER_SUFFIX),
1✔
974
          "ASCII header is named %s.  Only binary headers may end with %s",
975
          name,
976
          BINARY_HEADER_SUFFIX);
977
      this.marshaller = Preconditions.checkNotNull(marshaller, "marshaller");
1✔
978
    }
1✔
979

980
    @Override
981
    byte[] toBytes(T value) {
982
      String encoded = Preconditions.checkNotNull(
1✔
983
          marshaller.toAsciiString(value), "null marshaller.toAsciiString()");
1✔
984
      return encoded.getBytes(US_ASCII);
1✔
985
    }
986

987
    @Override
988
    T parseBytes(byte[] serialized) {
989
      return marshaller.parseAsciiString(new String(serialized, US_ASCII));
1✔
990
    }
991
  }
992

993
  private static final class TrustedAsciiKey<T> extends Key<T> {
994
    private final TrustedAsciiMarshaller<T> marshaller;
995

996
    /** Keys have a name and an ASCII marshaller used for serialization. */
997
    private TrustedAsciiKey(String name, boolean pseudo, TrustedAsciiMarshaller<T> marshaller) {
998
      super(name, pseudo, marshaller);
1✔
999
      Preconditions.checkArgument(
1✔
1000
          !name.endsWith(BINARY_HEADER_SUFFIX),
1✔
1001
          "ASCII header is named %s.  Only binary headers may end with %s",
1002
          name,
1003
          BINARY_HEADER_SUFFIX);
1004
      this.marshaller = Preconditions.checkNotNull(marshaller, "marshaller");
1✔
1005
    }
1✔
1006

1007
    @Override
1008
    byte[] toBytes(T value) {
1009
      return Preconditions.checkNotNull(
1✔
1010
          marshaller.toAsciiString(value), "null marshaller.toAsciiString()");
1✔
1011
    }
1012

1013
    @Override
1014
    T parseBytes(byte[] serialized) {
1015
      return marshaller.parseAsciiString(serialized);
1✔
1016
    }
1017
  }
1018

1019
  /**
1020
   * A specialized plain ASCII marshaller. Both input and output are assumed to be valid header
1021
   * ASCII.
1022
   */
1023
  @Immutable
1024
  interface TrustedAsciiMarshaller<T> {
1025
    /**
1026
     * Serialize a metadata value to a ASCII string that contains only the characters listed in the
1027
     * class comment of {@link io.grpc.Metadata.AsciiMarshaller}. Otherwise the output may be
1028
     * considered invalid and discarded by the transport, or the call may fail.
1029
     *
1030
     * @param value to serialize
1031
     * @return serialized version of value, or null if value cannot be transmitted.
1032
     */
1033
    byte[] toAsciiString(T value);
1034

1035
    /**
1036
     * Parse a serialized metadata value from an ASCII string.
1037
     *
1038
     * @param serialized value of metadata to parse
1039
     * @return a parsed instance of type T
1040
     */
1041
    T parseAsciiString(byte[] serialized);
1042
  }
1043

1044
  private static byte[] streamToBytes(InputStream stream) {
1045
    try {
1046
      return ByteStreams.toByteArray(stream);
1✔
1047
    } catch (IOException ioe) {
×
1048
      throw new RuntimeException("failure reading serialized stream", ioe);
×
1049
    }
1050
  }
1051
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc