xref: /aosp_15_r20/external/grpc-grpc-java/api/src/test/java/io/grpc/MetadataTest.java (revision e07d83d3ffcef9ecfc9f7f475418ec639ff0e5fe)
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.Charsets.US_ASCII;
20 import static com.google.common.base.Charsets.UTF_8;
21 import static org.junit.Assert.assertArrayEquals;
22 import static org.junit.Assert.assertEquals;
23 import static org.junit.Assert.assertFalse;
24 import static org.junit.Assert.assertNotEquals;
25 import static org.junit.Assert.assertNotSame;
26 import static org.junit.Assert.assertNull;
27 import static org.junit.Assert.assertSame;
28 import static org.junit.Assert.assertTrue;
29 import static org.junit.Assert.fail;
30 
31 import com.google.common.collect.Lists;
32 import com.google.common.io.ByteStreams;
33 import io.grpc.internal.GrpcUtil;
34 import java.io.ByteArrayInputStream;
35 import java.io.IOException;
36 import java.io.InputStream;
37 import java.util.Arrays;
38 import java.util.Iterator;
39 import java.util.Locale;
40 import org.junit.Rule;
41 import org.junit.Test;
42 import org.junit.rules.ExpectedException;
43 import org.junit.runner.RunWith;
44 import org.junit.runners.JUnit4;
45 
46 /**
47  * Tests for {@link Metadata}.
48  */
49 @RunWith(JUnit4.class)
50 public class MetadataTest {
51 
52   @SuppressWarnings("deprecation") // https://github.com/grpc/grpc-java/issues/7467
53   @Rule public final ExpectedException thrown = ExpectedException.none();
54 
55   private static final Metadata.BinaryMarshaller<Fish> FISH_MARSHALLER =
56       new Metadata.BinaryMarshaller<Fish>() {
57     @Override
58     public byte[] toBytes(Fish fish) {
59       return fish.name.getBytes(UTF_8);
60     }
61 
62     @Override
63     public Fish parseBytes(byte[] serialized) {
64       return new Fish(new String(serialized, UTF_8));
65     }
66   };
67 
68   private static class FishStreamMarsaller implements Metadata.BinaryStreamMarshaller<Fish> {
69     @Override
toStream(Fish fish)70     public InputStream toStream(Fish fish) {
71       return new ByteArrayInputStream(FISH_MARSHALLER.toBytes(fish));
72     }
73 
74     @Override
parseStream(InputStream stream)75     public Fish parseStream(InputStream stream) {
76       try {
77         return FISH_MARSHALLER.parseBytes(ByteStreams.toByteArray(stream));
78       } catch (IOException ioe) {
79         throw new AssertionError();
80       }
81     }
82   }
83 
84   private static final Metadata.BinaryStreamMarshaller<Fish> FISH_STREAM_MARSHALLER =
85       new FishStreamMarsaller();
86 
87   /** A pattern commonly used to avoid unnecessary serialization of immutable objects. */
88   private static final class FakeFishStream extends InputStream {
89     final Fish fish;
90 
FakeFishStream(Fish fish)91     FakeFishStream(Fish fish) {
92       this.fish = fish;
93     }
94 
95     @Override
read()96     public int read() throws IOException {
97       throw new IOException("Not actually a stream");
98     }
99   }
100 
101   private static final Metadata.BinaryStreamMarshaller<Fish> IMMUTABLE_FISH_MARSHALLER =
102       new Metadata.BinaryStreamMarshaller<Fish>() {
103     @Override
104     public InputStream toStream(Fish fish) {
105       return new FakeFishStream(fish);
106     }
107 
108     @Override
109     public Fish parseStream(InputStream stream) {
110       return ((FakeFishStream) stream).fish;
111     }
112   };
113 
114   private static final String LANCE = "lance";
115   private static final byte[] LANCE_BYTES = LANCE.getBytes(US_ASCII);
116   private static final Metadata.Key<Fish> KEY = Metadata.Key.of("test-bin", FISH_MARSHALLER);
117   private static final Metadata.Key<Fish> KEY_STREAMED =
118       Metadata.Key.of("streamed-bin", FISH_STREAM_MARSHALLER);
119   private static final Metadata.Key<Fish> KEY_IMMUTABLE =
120       Metadata.Key.of("immutable-bin", IMMUTABLE_FISH_MARSHALLER);
121 
122   @Test
noPseudoHeaders()123   public void noPseudoHeaders() {
124     thrown.expect(IllegalArgumentException.class);
125     thrown.expectMessage("Invalid character");
126 
127     Metadata.Key.of(":test-bin", FISH_MARSHALLER);
128   }
129 
130   @Test
testMutations()131   public void testMutations() {
132     Fish lance = new Fish(LANCE);
133     Fish cat = new Fish("cat");
134     Metadata metadata = new Metadata();
135 
136     assertNull(metadata.get(KEY));
137     metadata.put(KEY, lance);
138     assertEquals(Arrays.asList(lance), Lists.newArrayList(metadata.getAll(KEY)));
139     assertEquals(lance, metadata.get(KEY));
140     metadata.put(KEY, lance);
141     assertEquals(Arrays.asList(lance, lance), Lists.newArrayList(metadata.getAll(KEY)));
142     assertTrue(metadata.remove(KEY, lance));
143     assertEquals(Arrays.asList(lance), Lists.newArrayList(metadata.getAll(KEY)));
144 
145     assertFalse(metadata.remove(KEY, cat));
146     metadata.put(KEY, cat);
147     assertEquals(cat, metadata.get(KEY));
148     metadata.put(KEY, lance);
149     assertEquals(Arrays.asList(lance, cat, lance), Lists.newArrayList(metadata.getAll(KEY)));
150     assertEquals(lance, metadata.get(KEY));
151     assertTrue(metadata.remove(KEY, lance));
152     assertEquals(Arrays.asList(cat, lance), Lists.newArrayList(metadata.getAll(KEY)));
153     metadata.put(KEY, lance);
154     assertTrue(metadata.remove(KEY, cat));
155     assertEquals(Arrays.asList(lance, lance), Lists.newArrayList(metadata.getAll(KEY)));
156 
157     assertEquals(Arrays.asList(lance, lance), Lists.newArrayList(metadata.removeAll(KEY)));
158     assertNull(metadata.getAll(KEY));
159     assertNull(metadata.get(KEY));
160   }
161 
162   @Test
discardAll()163   public void discardAll() {
164     Fish lance = new Fish(LANCE);
165     Metadata metadata = new Metadata();
166 
167     metadata.put(KEY, lance);
168     metadata.discardAll(KEY);
169     assertNull(metadata.getAll(KEY));
170     assertNull(metadata.get(KEY));
171   }
172 
173   @Test
discardAll_empty()174   public void discardAll_empty() {
175     Metadata metadata = new Metadata();
176     metadata.discardAll(KEY);
177     assertNull(metadata.getAll(KEY));
178     assertNull(metadata.get(KEY));
179   }
180 
181   @Test
testGetAllNoRemove()182   public void testGetAllNoRemove() {
183     Fish lance = new Fish(LANCE);
184     Metadata metadata = new Metadata();
185     metadata.put(KEY, lance);
186     Iterator<Fish> i = metadata.getAll(KEY).iterator();
187     assertEquals(lance, i.next());
188 
189     thrown.expect(UnsupportedOperationException.class);
190     i.remove();
191   }
192 
193   @Test
testWriteParsed()194   public void testWriteParsed() {
195     Fish lance = new Fish(LANCE);
196     Metadata metadata = new Metadata();
197     metadata.put(KEY, lance);
198     assertEquals(lance, metadata.get(KEY));
199     Iterator<Fish> fishes = metadata.getAll(KEY).iterator();
200     assertTrue(fishes.hasNext());
201     assertEquals(fishes.next(), lance);
202     assertFalse(fishes.hasNext());
203     byte[][] serialized = metadata.serialize();
204     assertEquals(2, serialized.length);
205     assertEquals("test-bin", new String(serialized[0], US_ASCII));
206     assertArrayEquals(LANCE_BYTES, serialized[1]);
207     assertEquals(lance, metadata.get(KEY));
208     assertEquals(serialized[0], metadata.serialize()[0]);
209     assertEquals(serialized[1], metadata.serialize()[1]);
210   }
211 
212   @Test
testWriteRaw()213   public void testWriteRaw() {
214     Metadata raw = new Metadata(KEY.asciiName(), LANCE_BYTES);
215     Fish lance = raw.get(KEY);
216     assertEquals(lance, new Fish(LANCE));
217     // Reading again should return the same parsed instance
218     assertEquals(lance, raw.get(KEY));
219   }
220 
221   @Test
testSerializeRaw()222   public void testSerializeRaw() {
223     Metadata raw = new Metadata(KEY.asciiName(), LANCE_BYTES);
224     byte[][] serialized = raw.serialize();
225     assertArrayEquals(serialized[0], KEY.asciiName());
226     assertArrayEquals(serialized[1], LANCE_BYTES);
227   }
228 
229   @Test
testMergeByteConstructed()230   public void testMergeByteConstructed() {
231     Metadata raw = new Metadata(KEY.asciiName(), LANCE_BYTES);
232     Metadata serializable = new Metadata();
233     serializable.merge(raw);
234 
235     byte[][] serialized = serializable.serialize();
236     assertArrayEquals(serialized[0], KEY.asciiName());
237     assertArrayEquals(serialized[1], LANCE_BYTES);
238     assertEquals(new Fish(LANCE), serializable.get(KEY));
239   }
240 
241   @Test
headerMergeShouldCopyValues()242   public void headerMergeShouldCopyValues() {
243     Fish lance = new Fish(LANCE);
244     Metadata h1 = new Metadata();
245 
246     Metadata h2 = new Metadata();
247     h2.put(KEY, lance);
248 
249     h1.merge(h2);
250 
251     Iterator<Fish> fishes = h1.getAll(KEY).iterator();
252     assertTrue(fishes.hasNext());
253     assertEquals(fishes.next(), lance);
254     assertFalse(fishes.hasNext());
255   }
256 
257   @Test
mergeExpands()258   public void mergeExpands() {
259     Fish lance = new Fish(LANCE);
260     Metadata h1 = new Metadata();
261     h1.put(KEY, lance);
262 
263     Metadata h2 = new Metadata();
264     h2.put(KEY, lance);
265     h2.put(KEY, lance);
266     h2.put(KEY, lance);
267     h2.put(KEY, lance);
268 
269     h1.merge(h2);
270   }
271 
272   @Test
shortBinaryKeyName()273   public void shortBinaryKeyName() {
274     thrown.expect(IllegalArgumentException.class);
275 
276     Metadata.Key.of("-bin", FISH_MARSHALLER);
277   }
278 
279   @Test
invalidSuffixBinaryKeyName()280   public void invalidSuffixBinaryKeyName() {
281     thrown.expect(IllegalArgumentException.class);
282     thrown.expectMessage("Binary header is named");
283 
284     Metadata.Key.of("nonbinary", FISH_MARSHALLER);
285   }
286 
287   @Test
verifyToString()288   public void verifyToString() {
289     Metadata h = new Metadata();
290     h.put(KEY, new Fish("binary"));
291     h.put(Metadata.Key.of("test", Metadata.ASCII_STRING_MARSHALLER), "ascii");
292     assertEquals("Metadata(test-bin=YmluYXJ5,test=ascii)", h.toString());
293 
294     Metadata t = new Metadata();
295     t.put(Metadata.Key.of("test", Metadata.ASCII_STRING_MARSHALLER), "ascii");
296     assertEquals("Metadata(test=ascii)", t.toString());
297 
298     t = new Metadata("test".getBytes(US_ASCII), "ascii".getBytes(US_ASCII),
299         "test-bin".getBytes(US_ASCII), "binary".getBytes(US_ASCII));
300     assertEquals("Metadata(test=ascii,test-bin=YmluYXJ5)", t.toString());
301   }
302 
303   @Test
verifyToString_usingBinary()304   public void verifyToString_usingBinary() {
305     Metadata h = new Metadata();
306     h.put(KEY, new Fish("binary"));
307     h.put(Metadata.Key.of("test", Metadata.ASCII_STRING_MARSHALLER), "ascii");
308     assertEquals("Metadata(test-bin=YmluYXJ5,test=ascii)", h.toString());
309 
310     Metadata t = new Metadata();
311     t.put(Metadata.Key.of("test", Metadata.ASCII_STRING_MARSHALLER), "ascii");
312     assertEquals("Metadata(test=ascii)", t.toString());
313   }
314 
315   @Test
testKeyCaseHandling()316   public void testKeyCaseHandling() {
317     Locale originalLocale = Locale.getDefault();
318     Locale.setDefault(new Locale("tr", "TR"));
319     try {
320       // In Turkish, both I and i (which are in ASCII) change into non-ASCII characters when their
321       // case is changed as ı and İ, respectively.
322       assertEquals("İ", "i".toUpperCase());
323       assertEquals("ı", "I".toLowerCase());
324 
325       Metadata.Key<String> keyTitleCase
326           = Metadata.Key.of("If-Modified-Since", Metadata.ASCII_STRING_MARSHALLER);
327       Metadata.Key<String> keyLowerCase
328           = Metadata.Key.of("if-modified-since", Metadata.ASCII_STRING_MARSHALLER);
329       Metadata.Key<String> keyUpperCase
330           = Metadata.Key.of("IF-MODIFIED-SINCE", Metadata.ASCII_STRING_MARSHALLER);
331 
332       Metadata metadata = new Metadata();
333       metadata.put(keyTitleCase, "plain string");
334       assertEquals("plain string", metadata.get(keyTitleCase));
335       assertEquals("plain string", metadata.get(keyLowerCase));
336       assertEquals("plain string", metadata.get(keyUpperCase));
337 
338       byte[][] bytes = metadata.serialize();
339       assertEquals(2, bytes.length);
340       assertArrayEquals("if-modified-since".getBytes(US_ASCII), bytes[0]);
341       assertArrayEquals("plain string".getBytes(US_ASCII), bytes[1]);
342     } finally {
343       Locale.setDefault(originalLocale);
344     }
345   }
346 
347   @Test
removeIgnoresMissingValue()348   public void removeIgnoresMissingValue() {
349     Metadata m = new Metadata();
350     // Any key will work.
351     Metadata.Key<String> key = GrpcUtil.USER_AGENT_KEY;
352 
353     boolean success = m.remove(key, "agent");
354     assertFalse(success);
355   }
356 
357   @Test
removeAllIgnoresMissingValue()358   public void removeAllIgnoresMissingValue() {
359     Metadata m = new Metadata();
360     // Any key will work.
361     Metadata.Key<String> key = GrpcUtil.USER_AGENT_KEY;
362 
363     Iterable<String> removed = m.removeAll(key);
364     assertNull(removed);
365   }
366 
367   @Test
keyEqualsHashNameWorks()368   public void keyEqualsHashNameWorks() {
369     Metadata.Key<?> k1 = Metadata.Key.of("case", Metadata.ASCII_STRING_MARSHALLER);
370 
371     Metadata.Key<?> k2 = Metadata.Key.of("CASE", Metadata.ASCII_STRING_MARSHALLER);
372     assertEquals(k1, k1);
373     assertNotEquals(k1, null);
374     assertNotEquals(k1, new Object(){});
375     assertEquals(k1, k2);
376 
377     assertEquals(k1.hashCode(), k2.hashCode());
378     // Check that the casing is preserved.
379     assertEquals("CASE", k2.originalName());
380     assertEquals("case", k2.name());
381   }
382 
383   @Test
invalidKeyName()384   public void invalidKeyName() {
385     try {
386       Metadata.Key.of("io.grpc/key1", Metadata.ASCII_STRING_MARSHALLER);
387       fail("Should have thrown");
388     } catch (IllegalArgumentException e) {
389       assertEquals("Invalid character '/' in key name 'io.grpc/key1'", e.getMessage());
390     }
391   }
392 
393   @Test
streamedValue()394   public void streamedValue() {
395     Fish salmon = new Fish("salmon");
396     Metadata h = new Metadata();
397     h.put(KEY_STREAMED, salmon);
398     assertEquals(salmon, h.get(KEY_STREAMED));
399   }
400 
401   @Test
streamedValueDifferentKey()402   public void streamedValueDifferentKey() {
403     Fish salmon = new Fish("salmon");
404     Metadata h = new Metadata();
405     h.put(KEY_STREAMED, salmon);
406 
407     // Get using a different key instance (but the same marshaller).
408     Fish fish = h.get(copyKey(KEY_STREAMED, FISH_STREAM_MARSHALLER));
409     assertEquals(salmon, fish);
410   }
411 
412   @Test
streamedValueDifferentMarshaller()413   public void streamedValueDifferentMarshaller() {
414     Fish salmon = new Fish("salmon");
415     Metadata h = new Metadata();
416     h.put(KEY_STREAMED, salmon);
417 
418     // Get using a different marshaller instance.
419     Fish fish = h.get(copyKey(KEY_STREAMED, new FishStreamMarsaller()));
420     assertEquals(salmon, fish);
421   }
422 
423   @Test
serializeParseMetadataWithStreams()424   public void serializeParseMetadataWithStreams() {
425     Metadata h = new Metadata();
426     Fish salmon = new Fish("salmon");
427     h.put(KEY_STREAMED, salmon);
428 
429     Metadata parsed = new Metadata(h.serialize());
430     assertEquals(salmon, parsed.get(KEY_STREAMED));
431   }
432 
433   @Test
immutableMarshaller()434   public void immutableMarshaller() {
435     Metadata h = new Metadata(KEY.asciiName(), LANCE_BYTES);
436     Fish salmon = new Fish("salmon");
437     h.put(KEY_IMMUTABLE, salmon);
438     assertSame(salmon, h.get(KEY_IMMUTABLE));
439     // Even though the key differs, the marshaller can chose to avoid serialization.
440     assertSame(salmon, h.get(copyKey(KEY_IMMUTABLE, IMMUTABLE_FISH_MARSHALLER)));
441   }
442 
443   @Test
partialSerialization()444   public void partialSerialization() {
445     Metadata h = new Metadata(KEY.asciiName(), LANCE_BYTES);
446     Fish salmon = new Fish("salmon");
447     h.put(KEY_STREAMED, salmon);
448     h.put(KEY_IMMUTABLE, salmon);
449 
450     Object[] serialized = InternalMetadata.serializePartial(h);
451     assertEquals(6, serialized.length);
452     assertEquals("test-bin", new String((byte[]) serialized[0], US_ASCII));
453     assertArrayEquals(LANCE_BYTES, (byte[]) serialized[1]);
454 
455     assertEquals("streamed-bin", new String((byte[]) serialized[2], US_ASCII));
456     assertEquals(salmon, FISH_STREAM_MARSHALLER.parseStream((InputStream) serialized[3]));
457     assertNotSame(salmon, FISH_STREAM_MARSHALLER.parseStream((InputStream) serialized[3]));
458 
459     assertEquals("immutable-bin", new String((byte[]) serialized[4], US_ASCII));
460     assertSame(salmon, IMMUTABLE_FISH_MARSHALLER.parseStream((InputStream) serialized[5]));
461   }
462 
463   @Test
createFromPartial()464   public void createFromPartial() {
465     Metadata h = new Metadata(KEY.asciiName(), LANCE_BYTES);
466     Fish salmon = new Fish("salmon");
467     h.put(KEY_STREAMED, salmon);
468     h.put(KEY_IMMUTABLE, salmon);
469 
470     Fish anotherSalmon = new Fish("salmon");
471 
472     Object[] partial = InternalMetadata.serializePartial(h);
473     partial[3] = InternalMetadata.parsedValue(FISH_STREAM_MARSHALLER, anotherSalmon);
474     partial[5] = InternalMetadata.parsedValue(IMMUTABLE_FISH_MARSHALLER, anotherSalmon);
475 
476     Metadata h2 = new Metadata(3, partial);
477     assertEquals(new Fish(LANCE), h2.get(KEY));
478     assertEquals(anotherSalmon, h2.get(KEY_STREAMED));
479     assertSame(anotherSalmon, h2.get(KEY_IMMUTABLE));
480   }
481 
482   private static final class Fish {
483     private String name;
484 
Fish(String name)485     private Fish(String name) {
486       this.name = name;
487     }
488 
489     @Override
equals(Object o)490     public boolean equals(Object o) {
491       if (this == o) {
492         return true;
493       }
494       if (o == null || getClass() != o.getClass()) {
495         return false;
496       }
497       Fish fish = (Fish) o;
498       if (name != null ? !name.equals(fish.name) : fish.name != null) {
499         return false;
500       }
501       return true;
502     }
503 
504     @Override
hashCode()505     public int hashCode() {
506       return name.hashCode();
507     }
508 
509     @Override
toString()510     public String toString() {
511       return "Fish(" + name + ")";
512     }
513   }
514 
copyKey( Metadata.Key<T> key, Metadata.BinaryStreamMarshaller<T> marshaller)515   private static <T> Metadata.Key<T> copyKey(
516       Metadata.Key<T> key, Metadata.BinaryStreamMarshaller<T> marshaller) {
517     return Metadata.Key.of(key.originalName(), marshaller);
518   }
519 }
520