HeaderTable.cs 7.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198
  1. #if (!UNITY_WEBGL || UNITY_EDITOR) && !BESTHTTP_DISABLE_ALTERNATE_SSL && !BESTHTTP_DISABLE_HTTP2
  2. using System;
  3. using System.Collections.Generic;
  4. namespace BestHTTP.Connections.HTTP2
  5. {
  6. sealed class HeaderTable
  7. {
  8. // https://http2.github.io/http2-spec/compression.html#static.table.definition
  9. // Valid indexes starts with 1, so there's an empty entry.
  10. static string[] StaticTableValues = new string[] { string.Empty, string.Empty, "GET", "POST", "/", "/index.html", "http", "https", "200", "204", "206", "304", "400", "404", "500", string.Empty, "gzip, deflate" };
  11. // https://http2.github.io/http2-spec/compression.html#static.table.definition
  12. // Valid indexes starts with 1, so there's an empty entry.
  13. static string[] StaticTable = new string[62]
  14. {
  15. string.Empty,
  16. ":authority",
  17. ":method", // GET
  18. ":method", // POST
  19. ":path", // /
  20. ":path", // index.html
  21. ":scheme", // http
  22. ":scheme", // https
  23. ":status", // 200
  24. ":status", // 204
  25. ":status", // 206
  26. ":status", // 304
  27. ":status", // 400
  28. ":status", // 404
  29. ":status", // 500
  30. "accept-charset",
  31. "accept-encoding", // gzip, deflate
  32. "accept-language",
  33. "accept-ranges",
  34. "accept",
  35. "access-control-allow-origin",
  36. "age",
  37. "allow",
  38. "authorization",
  39. "cache-control",
  40. "content-disposition",
  41. "content-encoding",
  42. "content-language",
  43. "content-length",
  44. "content-location",
  45. "content-range",
  46. "content-type",
  47. "cookie",
  48. "date",
  49. "etag",
  50. "expect",
  51. "expires",
  52. "from",
  53. "host",
  54. "if-match",
  55. "if-modified-since",
  56. "if-none-match",
  57. "if-range",
  58. "if-unmodified-since",
  59. "last-modified",
  60. "link",
  61. "location",
  62. "max-forwards",
  63. "proxy-authenticate",
  64. "proxy-authorization",
  65. "range",
  66. "referer",
  67. "refresh",
  68. "retry-after",
  69. "server",
  70. "set-cookie",
  71. "strict-transport-security",
  72. "transfer-encoding",
  73. "user-agent",
  74. "vary",
  75. "via",
  76. "www-authenticate",
  77. };
  78. public UInt32 DynamicTableSize { get; private set; }
  79. public UInt32 MaxDynamicTableSize {
  80. get { return this._maxDynamicTableSize; }
  81. set
  82. {
  83. this._maxDynamicTableSize = value;
  84. EvictEntries(0);
  85. }
  86. }
  87. private UInt32 _maxDynamicTableSize;
  88. private List<KeyValuePair<string, string>> DynamicTable = new List<KeyValuePair<string, string>>();
  89. private HTTP2SettingsRegistry settingsRegistry;
  90. public HeaderTable(HTTP2SettingsRegistry registry)
  91. {
  92. this.settingsRegistry = registry;
  93. this.MaxDynamicTableSize = this.settingsRegistry[HTTP2Settings.HEADER_TABLE_SIZE];
  94. }
  95. public KeyValuePair<UInt32, UInt32> GetIndex(string key, string value)
  96. {
  97. for (int i = 0; i < DynamicTable.Count; ++i)
  98. {
  99. var kvp = DynamicTable[i];
  100. // Exact match for both key and value
  101. if (kvp.Key.Equals(key, StringComparison.OrdinalIgnoreCase) && kvp.Value.Equals(value, StringComparison.OrdinalIgnoreCase))
  102. return new KeyValuePair<UInt32, UInt32>((UInt32)(StaticTable.Length + i), (UInt32)(StaticTable.Length + i));
  103. }
  104. KeyValuePair<UInt32, UInt32> bestMatch = new KeyValuePair<UInt32, UInt32>(0, 0);
  105. for (int i = 0; i < StaticTable.Length; ++i)
  106. {
  107. if (StaticTable[i].Equals(key, StringComparison.OrdinalIgnoreCase))
  108. {
  109. if (i < StaticTableValues.Length && !string.IsNullOrEmpty(StaticTableValues[i]) && StaticTableValues[i].Equals(value, StringComparison.OrdinalIgnoreCase))
  110. return new KeyValuePair<UInt32, UInt32>((UInt32)i, (UInt32)i);
  111. else
  112. bestMatch = new KeyValuePair<UInt32, UInt32>((UInt32)i, 0);
  113. }
  114. }
  115. return bestMatch;
  116. }
  117. public string GetKey(UInt32 index)
  118. {
  119. if (index < StaticTable.Length)
  120. return StaticTable[index];
  121. return this.DynamicTable[(int)(index - StaticTable.Length)].Key;
  122. }
  123. public KeyValuePair<string, string> GetHeader(UInt32 index)
  124. {
  125. if (index < StaticTable.Length)
  126. return new KeyValuePair<string, string>(StaticTable[index],
  127. index < StaticTableValues.Length ? StaticTableValues[index] : null);
  128. return this.DynamicTable[(int)(index - StaticTable.Length)];
  129. }
  130. public void Add(KeyValuePair<string, string> header)
  131. {
  132. // https://http2.github.io/http2-spec/compression.html#calculating.table.size
  133. // The size of an entry is the sum of its name's length in octets (as defined in Section 5.2),
  134. // its value's length in octets, and 32.
  135. UInt32 newHeaderSize = CalculateEntrySize(header);
  136. EvictEntries(newHeaderSize);
  137. // If the size of the new entry is less than or equal to the maximum size, that entry is added to the table.
  138. // It is not an error to attempt to add an entry that is larger than the maximum size;
  139. // an attempt to add an entry larger than the maximum size causes the table to be
  140. // emptied of all existing entries and results in an empty table.
  141. if (this.DynamicTableSize + newHeaderSize <= this.MaxDynamicTableSize)
  142. {
  143. this.DynamicTable.Insert(0, header);
  144. this.DynamicTableSize += (UInt32)newHeaderSize;
  145. }
  146. }
  147. private UInt32 CalculateEntrySize(KeyValuePair<string, string> entry)
  148. {
  149. return 32 + (UInt32)System.Text.Encoding.UTF8.GetByteCount(entry.Key) +
  150. (UInt32)System.Text.Encoding.UTF8.GetByteCount(entry.Value);
  151. }
  152. private void EvictEntries(uint newHeaderSize)
  153. {
  154. // https://http2.github.io/http2-spec/compression.html#entry.addition
  155. // Before a new entry is added to the dynamic table, entries are evicted from the end of the dynamic
  156. // table until the size of the dynamic table is less than or equal to (maximum size - new entry size) or until the table is empty.
  157. while (this.DynamicTableSize + newHeaderSize > this.MaxDynamicTableSize && this.DynamicTable.Count > 0)
  158. {
  159. KeyValuePair<string, string> entry = this.DynamicTable[this.DynamicTable.Count - 1];
  160. this.DynamicTable.RemoveAt(this.DynamicTable.Count - 1);
  161. this.DynamicTableSize -= CalculateEntrySize(entry);
  162. }
  163. }
  164. public override string ToString()
  165. {
  166. System.Text.StringBuilder sb = new System.Text.StringBuilder("[HeaderTable ");
  167. sb.AppendFormat("DynamicTable count: {0}, DynamicTableSize: {1}, MaxDynamicTableSize: {2}, ", this.DynamicTable.Count, this.DynamicTableSize, this.MaxDynamicTableSize);
  168. foreach(var kvp in this.DynamicTable)
  169. sb.AppendFormat("\"{0}\": \"{1}\", ", kvp.Key, kvp.Value);
  170. sb.Append("]");
  171. return sb.ToString();
  172. }
  173. }
  174. }
  175. #endif