• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 #region Copyright notice and license
2 // Copyright 2015 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 #endregion
16 
17 using System;
18 using System.Collections;
19 using System.Collections.Generic;
20 using System.Runtime.InteropServices;
21 using System.Text;
22 using Grpc.Core.Api.Utils;
23 
24 using Grpc.Core.Utils;
25 
26 namespace Grpc.Core
27 {
28     /// <summary>
29     /// A collection of metadata entries that can be exchanged during a call.
30     /// gRPC supports these types of metadata:
31     /// <list type="bullet">
32     /// <item><term>Request headers</term><description>are sent by the client at the beginning of a remote call before any request messages are sent.</description></item>
33     /// <item><term>Response headers</term><description>are sent by the server at the beginning of a remote call handler before any response messages are sent.</description></item>
34     /// <item><term>Response trailers</term><description>are sent by the server at the end of a remote call along with resulting call status.</description></item>
35     /// </list>
36     /// </summary>
37     public sealed class Metadata : IList<Metadata.Entry>
38     {
39         /// <summary>
40         /// All binary headers should have this suffix.
41         /// </summary>
42         public const string BinaryHeaderSuffix = "-bin";
43 
44         /// <summary>
45         /// An read-only instance of metadata containing no entries.
46         /// </summary>
47         public static readonly Metadata Empty = new Metadata().Freeze();
48 
49         /// <summary>
50         /// To be used in initial metadata to request specific compression algorithm
51         /// for given call. Direct selection of compression algorithms is an internal
52         /// feature and is not part of public API.
53         /// </summary>
54         internal const string CompressionRequestAlgorithmMetadataKey = "grpc-internal-encoding-request";
55         static readonly Encoding EncodingASCII = System.Text.Encoding.ASCII;
56 
57         readonly List<Entry> entries;
58         bool readOnly;
59 
60         /// <summary>
61         /// Initializes a new instance of <c>Metadata</c>.
62         /// </summary>
Metadata()63         public Metadata()
64         {
65             this.entries = new List<Entry>();
66         }
67 
68         /// <summary>
69         /// Makes this object read-only.
70         /// </summary>
71         /// <returns>this object</returns>
Freeze()72         internal Metadata Freeze()
73         {
74             this.readOnly = true;
75             return this;
76         }
77 
78         /// <summary>
79         /// Gets the last metadata entry with the specified key.
80         /// If there are no matching entries then <c>null</c> is returned.
81         /// </summary>
Get(string key)82         public Entry Get(string key)
83         {
84             for (int i = entries.Count - 1; i >= 0; i--)
85             {
86                 if (entries[i].Key == key)
87                 {
88                     return entries[i];
89                 }
90             }
91 
92             return null;
93         }
94 
95         /// <summary>
96         /// Gets the string value of the last metadata entry with the specified key.
97         /// If the metadata entry is binary then an exception is thrown.
98         /// If there are no matching entries then <c>null</c> is returned.
99         /// </summary>
GetValue(string key)100         public string GetValue(string key)
101         {
102             return Get(key)?.Value;
103         }
104 
105         /// <summary>
106         /// Gets the bytes value of the last metadata entry with the specified key.
107         /// If the metadata entry is not binary the string value will be returned as ASCII encoded bytes.
108         /// If there are no matching entries then <c>null</c> is returned.
109         /// </summary>
GetValueBytes(string key)110         public byte[] GetValueBytes(string key)
111         {
112             return Get(key)?.ValueBytes;
113         }
114 
115         /// <summary>
116         /// Gets all metadata entries with the specified key.
117         /// </summary>
GetAll(string key)118         public IEnumerable<Entry> GetAll(string key)
119         {
120             for (int i = 0; i < entries.Count; i++)
121             {
122                 if (entries[i].Key == key)
123                 {
124                     yield return entries[i];
125                 }
126             }
127         }
128 
129         /// <summary>
130         /// Adds a new ASCII-valued metadata entry. See <c>Metadata.Entry</c> constructor for params.
131         /// </summary>
Add(string key, string value)132         public void Add(string key, string value)
133         {
134             Add(new Entry(key, value));
135         }
136 
137         /// <summary>
138         /// Adds a new binary-valued metadata entry. See <c>Metadata.Entry</c> constructor for params.
139         /// </summary>
Add(string key, byte[] valueBytes)140         public void Add(string key, byte[] valueBytes)
141         {
142             Add(new Entry(key, valueBytes));
143         }
144 
145         #region IList members
146 
147 
148         /// <summary>
149         /// <see cref="T:IList`1"/>
150         /// </summary>
IndexOf(Metadata.Entry item)151         public int IndexOf(Metadata.Entry item)
152         {
153             return entries.IndexOf(item);
154         }
155 
156         /// <summary>
157         /// <see cref="T:IList`1"/>
158         /// </summary>
Insert(int index, Metadata.Entry item)159         public void Insert(int index, Metadata.Entry item)
160         {
161             GrpcPreconditions.CheckNotNull(item);
162             CheckWriteable();
163             entries.Insert(index, item);
164         }
165 
166         /// <summary>
167         /// <see cref="T:IList`1"/>
168         /// </summary>
RemoveAt(int index)169         public void RemoveAt(int index)
170         {
171             CheckWriteable();
172             entries.RemoveAt(index);
173         }
174 
175         /// <summary>
176         /// <see cref="T:IList`1"/>
177         /// </summary>
178         public Metadata.Entry this[int index]
179         {
180             get
181             {
182                 return entries[index];
183             }
184 
185             set
186             {
187                 GrpcPreconditions.CheckNotNull(value);
188                 CheckWriteable();
189                 entries[index] = value;
190             }
191         }
192 
193         /// <summary>
194         /// <see cref="T:IList`1"/>
195         /// </summary>
Add(Metadata.Entry item)196         public void Add(Metadata.Entry item)
197         {
198             GrpcPreconditions.CheckNotNull(item);
199             CheckWriteable();
200             entries.Add(item);
201         }
202 
203         /// <summary>
204         /// <see cref="T:IList`1"/>
205         /// </summary>
Clear()206         public void Clear()
207         {
208             CheckWriteable();
209             entries.Clear();
210         }
211 
212         /// <summary>
213         /// <see cref="T:IList`1"/>
214         /// </summary>
Contains(Metadata.Entry item)215         public bool Contains(Metadata.Entry item)
216         {
217             return entries.Contains(item);
218         }
219 
220         /// <summary>
221         /// <see cref="T:IList`1"/>
222         /// </summary>
CopyTo(Metadata.Entry[] array, int arrayIndex)223         public void CopyTo(Metadata.Entry[] array, int arrayIndex)
224         {
225             entries.CopyTo(array, arrayIndex);
226         }
227 
228         /// <summary>
229         /// <see cref="T:IList`1"/>
230         /// </summary>
231         public int Count
232         {
233             get { return entries.Count; }
234         }
235 
236         /// <summary>
237         /// <see cref="T:IList`1"/>
238         /// </summary>
239         public bool IsReadOnly
240         {
241             get { return readOnly; }
242         }
243 
244         /// <summary>
245         /// <see cref="T:IList`1"/>
246         /// </summary>
Remove(Metadata.Entry item)247         public bool Remove(Metadata.Entry item)
248         {
249             CheckWriteable();
250             return entries.Remove(item);
251         }
252 
253         /// <summary>
254         /// <see cref="T:IList`1"/>
255         /// </summary>
GetEnumerator()256         public IEnumerator<Metadata.Entry> GetEnumerator()
257         {
258             return entries.GetEnumerator();
259         }
260 
System.Collections.IEnumerable.GetEnumerator()261         IEnumerator System.Collections.IEnumerable.GetEnumerator()
262         {
263             return entries.GetEnumerator();
264         }
265 
CheckWriteable()266         private void CheckWriteable()
267         {
268             GrpcPreconditions.CheckState(!readOnly, "Object is read only");
269         }
270 
271         #endregion
272 
273         /// <summary>
274         /// Metadata entry
275         /// </summary>
276         public class Entry
277         {
278             readonly string key;
279             readonly string value;
280             readonly byte[] valueBytes;
281 
Entry(string key, string value, byte[] valueBytes)282             private Entry(string key, string value, byte[] valueBytes)
283             {
284                 this.key = key;
285                 this.value = value;
286                 this.valueBytes = valueBytes;
287             }
288 
289             /// <summary>
290             /// Initializes a new instance of the <see cref="Grpc.Core.Metadata.Entry"/> struct with a binary value.
291             /// </summary>
292             /// <param name="key">Metadata key. Gets converted to lowercase. Needs to have suffix indicating a binary valued metadata entry. Can only contain lowercase alphanumeric characters, underscores, hyphens and dots.</param>
293             /// <param name="valueBytes">Value bytes.</param>
Entry(string key, byte[] valueBytes)294             public Entry(string key, byte[] valueBytes)
295             {
296                 this.key = NormalizeKey(key);
297                 GrpcPreconditions.CheckArgument(HasBinaryHeaderSuffix(this.key),
298                     "Key for binary valued metadata entry needs to have suffix indicating binary value.");
299                 this.value = null;
300                 GrpcPreconditions.CheckNotNull(valueBytes, "valueBytes");
301                 this.valueBytes = new byte[valueBytes.Length];
302                 Buffer.BlockCopy(valueBytes, 0, this.valueBytes, 0, valueBytes.Length);  // defensive copy to guarantee immutability
303             }
304 
305             /// <summary>
306             /// Initializes a new instance of the <see cref="Grpc.Core.Metadata.Entry"/> struct with an ASCII value.
307             /// </summary>
308             /// <param name="key">Metadata key. Gets converted to lowercase. Must not use suffix indicating a binary valued metadata entry. Can only contain lowercase alphanumeric characters, underscores, hyphens and dots.</param>
309             /// <param name="value">Value string. Only ASCII characters are allowed.</param>
Entry(string key, string value)310             public Entry(string key, string value)
311             {
312                 this.key = NormalizeKey(key);
313                 GrpcPreconditions.CheckArgument(!HasBinaryHeaderSuffix(this.key),
314                     "Key for ASCII valued metadata entry cannot have suffix indicating binary value.");
315                 this.value = GrpcPreconditions.CheckNotNull(value, "value");
316                 this.valueBytes = null;
317             }
318 
319             /// <summary>
320             /// Gets the metadata entry key.
321             /// </summary>
322             public string Key
323             {
324                 get
325                 {
326                     return this.key;
327                 }
328             }
329 
330             /// <summary>
331             /// Gets the binary value of this metadata entry.
332             /// If the metadata entry is not binary the string value will be returned as ASCII encoded bytes.
333             /// </summary>
334             public byte[] ValueBytes
335             {
336                 get
337                 {
338                     if (valueBytes == null)
339                     {
340                         return EncodingASCII.GetBytes(value);
341                     }
342 
343                     // defensive copy to guarantee immutability
344                     var bytes = new byte[valueBytes.Length];
345                     Buffer.BlockCopy(valueBytes, 0, bytes, 0, valueBytes.Length);
346                     return bytes;
347                 }
348             }
349 
350             /// <summary>
351             /// Gets the string value of this metadata entry.
352             /// If the metadata entry is binary then an exception is thrown.
353             /// </summary>
354             public string Value
355             {
356                 get
357                 {
358                     GrpcPreconditions.CheckState(!IsBinary, "Cannot access string value of a binary metadata entry");
359                     return value;
360                 }
361             }
362 
363             /// <summary>
364             /// Returns <c>true</c> if this entry is a binary-value entry.
365             /// </summary>
366             public bool IsBinary
367             {
368                 get
369                 {
370                     return value == null;
371                 }
372             }
373 
374             /// <summary>
375             /// Returns a <see cref="System.String"/> that represents the current <see cref="Grpc.Core.Metadata.Entry"/>.
376             /// </summary>
ToString()377             public override string ToString()
378             {
379                 if (IsBinary)
380                 {
381                     return string.Format("[Entry: key={0}, valueBytes={1}]", key, valueBytes);
382                 }
383 
384                 return string.Format("[Entry: key={0}, value={1}]", key, value);
385             }
386 
387             /// <summary>
388             /// Gets the serialized value for this entry. For binary metadata entries, this leaks
389             /// the internal <c>valueBytes</c> byte array and caller must not change contents of it.
390             /// </summary>
GetSerializedValueUnsafe()391             internal byte[] GetSerializedValueUnsafe()
392             {
393                 return valueBytes ?? EncodingASCII.GetBytes(value);
394             }
395 
396             /// <summary>
397             /// Creates a binary value or ascii value metadata entry from data received from the native layer.
398             /// We trust C core to give us well-formed data, so we don't perform any checks or defensive copying.
399             /// </summary>
CreateUnsafe(string key, IntPtr source, int length)400             internal static Entry CreateUnsafe(string key, IntPtr source, int length)
401             {
402                 if (HasBinaryHeaderSuffix(key))
403                 {
404                     byte[] arr;
405                     if (length == 0)
406                     {
407                         arr = EmptyByteArray;
408                     }
409                     else
410                     {   // create a local copy in a fresh array
411                         arr = new byte[length];
412                         Marshal.Copy(source, arr, 0, length);
413                     }
414                     return new Entry(key, null, arr);
415                 }
416                 else
417                 {
418                     string s = EncodingASCII.GetString(source, length);
419                     return new Entry(key, s, null);
420                 }
421             }
422 
423             static readonly byte[] EmptyByteArray = new byte[0];
424 
NormalizeKey(string key)425             private static string NormalizeKey(string key)
426             {
427                 GrpcPreconditions.CheckNotNull(key, "key");
428 
429                 GrpcPreconditions.CheckArgument(IsValidKey(key, out bool isLowercase),
430                     "Metadata entry key not valid. Keys can only contain lowercase alphanumeric characters, underscores, hyphens and dots.");
431                 if (isLowercase)
432                 {
433                     // save allocation of a new string if already lowercase
434                     return key;
435                 }
436 
437                 return key.ToLowerInvariant();
438             }
439 
IsValidKey(string input, out bool isLowercase)440             private static bool IsValidKey(string input, out bool isLowercase)
441             {
442                 isLowercase = true;
443                 for (int i = 0; i < input.Length; i++)
444                 {
445                     char c = input[i];
446                     if ('a' <= c && c <= 'z' ||
447                         '0' <= c && c <= '9' ||
448                         c == '.' ||
449                         c == '_' ||
450                         c == '-' )
451                         continue;
452 
453                     if ('A' <= c && c <= 'Z')
454                     {
455                         isLowercase = false;
456                         continue;
457                     }
458 
459                     return false;
460                 }
461 
462                 return true;
463             }
464 
465             /// <summary>
466             /// Returns <c>true</c> if the key has "-bin" binary header suffix.
467             /// </summary>
HasBinaryHeaderSuffix(string key)468             private static bool HasBinaryHeaderSuffix(string key)
469             {
470                 // We don't use just string.EndsWith because its implementation is extremely slow
471                 // on CoreCLR and we've seen significant differences in gRPC benchmarks caused by it.
472                 // See https://github.com/dotnet/coreclr/issues/5612
473 
474                 int len = key.Length;
475                 if (len >= 4 &&
476                     key[len - 4] == '-' &&
477                     key[len - 3] == 'b' &&
478                     key[len - 2] == 'i' &&
479                     key[len - 1] == 'n')
480                 {
481                     return true;
482                 }
483                 return false;
484             }
485         }
486     }
487 }
488