header_test.go 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675
  1. package h2mux
  2. import (
  3. "fmt"
  4. "math/rand"
  5. "net/http"
  6. "net/url"
  7. "reflect"
  8. "regexp"
  9. "sort"
  10. "strings"
  11. "testing"
  12. "testing/quick"
  13. "github.com/stretchr/testify/assert"
  14. "github.com/stretchr/testify/require"
  15. )
  16. type ByName []Header
  17. func (a ByName) Len() int { return len(a) }
  18. func (a ByName) Swap(i, j int) { a[i], a[j] = a[j], a[i] }
  19. func (a ByName) Less(i, j int) bool {
  20. if a[i].Name == a[j].Name {
  21. return a[i].Value < a[j].Value
  22. }
  23. return a[i].Name < a[j].Name
  24. }
  25. func TestH2RequestHeadersToH1Request_RegularHeaders(t *testing.T) {
  26. request, err := http.NewRequest(http.MethodGet, "http://example.com", nil)
  27. assert.NoError(t, err)
  28. mockHeaders := http.Header{
  29. "Mock header 1": {"Mock value 1"},
  30. "Mock header 2": {"Mock value 2"},
  31. }
  32. headersConversionErr := H2RequestHeadersToH1Request(createSerializedHeaders(RequestUserHeadersField, mockHeaders), request)
  33. assert.True(t, reflect.DeepEqual(mockHeaders, request.Header))
  34. assert.NoError(t, headersConversionErr)
  35. }
  36. func createSerializedHeaders(headersField string, headers http.Header) []Header {
  37. return []Header{{
  38. headersField,
  39. SerializeHeaders(headers),
  40. }}
  41. }
  42. func TestH2RequestHeadersToH1Request_NoHeaders(t *testing.T) {
  43. request, err := http.NewRequest(http.MethodGet, "http://example.com", nil)
  44. assert.NoError(t, err)
  45. headersConversionErr := H2RequestHeadersToH1Request(
  46. []Header{{
  47. RequestUserHeadersField,
  48. SerializeHeaders(http.Header{}),
  49. }},
  50. request,
  51. )
  52. assert.True(t, reflect.DeepEqual(http.Header{}, request.Header))
  53. assert.NoError(t, headersConversionErr)
  54. }
  55. func TestH2RequestHeadersToH1Request_InvalidHostPath(t *testing.T) {
  56. request, err := http.NewRequest(http.MethodGet, "http://example.com", nil)
  57. assert.NoError(t, err)
  58. mockRequestHeaders := []Header{
  59. {Name: ":path", Value: "//bad_path/"},
  60. {Name: RequestUserHeadersField, Value: SerializeHeaders(http.Header{"Mock header": {"Mock value"}})},
  61. }
  62. headersConversionErr := H2RequestHeadersToH1Request(mockRequestHeaders, request)
  63. assert.Equal(t, http.Header{
  64. "Mock header": []string{"Mock value"},
  65. }, request.Header)
  66. assert.Equal(t, "http://example.com//bad_path/", request.URL.String())
  67. assert.NoError(t, headersConversionErr)
  68. }
  69. func TestH2RequestHeadersToH1Request_HostPathWithQuery(t *testing.T) {
  70. request, err := http.NewRequest(http.MethodGet, "http://example.com/", nil)
  71. assert.NoError(t, err)
  72. mockRequestHeaders := []Header{
  73. {Name: ":path", Value: "/?query=mock%20value"},
  74. {Name: RequestUserHeadersField, Value: SerializeHeaders(http.Header{"Mock header": {"Mock value"}})},
  75. }
  76. headersConversionErr := H2RequestHeadersToH1Request(mockRequestHeaders, request)
  77. assert.Equal(t, http.Header{
  78. "Mock header": []string{"Mock value"},
  79. }, request.Header)
  80. assert.Equal(t, "http://example.com/?query=mock%20value", request.URL.String())
  81. assert.NoError(t, headersConversionErr)
  82. }
  83. func TestH2RequestHeadersToH1Request_HostPathWithURLEncoding(t *testing.T) {
  84. request, err := http.NewRequest(http.MethodGet, "http://example.com/", nil)
  85. assert.NoError(t, err)
  86. mockRequestHeaders := []Header{
  87. {Name: ":path", Value: "/mock%20path"},
  88. {Name: RequestUserHeadersField, Value: SerializeHeaders(http.Header{"Mock header": {"Mock value"}})},
  89. }
  90. headersConversionErr := H2RequestHeadersToH1Request(mockRequestHeaders, request)
  91. assert.Equal(t, http.Header{
  92. "Mock header": []string{"Mock value"},
  93. }, request.Header)
  94. assert.Equal(t, "http://example.com/mock%20path", request.URL.String())
  95. assert.NoError(t, headersConversionErr)
  96. }
  97. func TestH2RequestHeadersToH1Request_WeirdURLs(t *testing.T) {
  98. type testCase struct {
  99. path string
  100. want string
  101. }
  102. testCases := []testCase{
  103. {
  104. path: "",
  105. want: "",
  106. },
  107. {
  108. path: "/",
  109. want: "/",
  110. },
  111. {
  112. path: "//",
  113. want: "//",
  114. },
  115. {
  116. path: "/test",
  117. want: "/test",
  118. },
  119. {
  120. path: "//test",
  121. want: "//test",
  122. },
  123. {
  124. // https://github.com/cloudflare/cloudflared/issues/81
  125. path: "//test/",
  126. want: "//test/",
  127. },
  128. {
  129. path: "/%2Ftest",
  130. want: "/%2Ftest",
  131. },
  132. {
  133. path: "//%20test",
  134. want: "//%20test",
  135. },
  136. {
  137. // https://github.com/cloudflare/cloudflared/issues/124
  138. path: "/test?get=somthing%20a",
  139. want: "/test?get=somthing%20a",
  140. },
  141. {
  142. path: "/%20",
  143. want: "/%20",
  144. },
  145. {
  146. // stdlib's EscapedPath() will always percent-encode ' '
  147. path: "/ ",
  148. want: "/%20",
  149. },
  150. {
  151. path: "/ a ",
  152. want: "/%20a%20",
  153. },
  154. {
  155. path: "/a%20b",
  156. want: "/a%20b",
  157. },
  158. {
  159. path: "/foo/bar;param?query#frag",
  160. want: "/foo/bar;param?query#frag",
  161. },
  162. {
  163. // stdlib's EscapedPath() will always percent-encode non-ASCII chars
  164. path: "/a␠b",
  165. want: "/a%E2%90%A0b",
  166. },
  167. {
  168. path: "/a-umlaut-ä",
  169. want: "/a-umlaut-%C3%A4",
  170. },
  171. {
  172. path: "/a-umlaut-%C3%A4",
  173. want: "/a-umlaut-%C3%A4",
  174. },
  175. {
  176. path: "/a-umlaut-%c3%a4",
  177. want: "/a-umlaut-%c3%a4",
  178. },
  179. {
  180. // here the second '#' is treated as part of the fragment
  181. path: "/a#b#c",
  182. want: "/a#b%23c",
  183. },
  184. {
  185. path: "/a#b␠c",
  186. want: "/a#b%E2%90%A0c",
  187. },
  188. {
  189. path: "/a#b%20c",
  190. want: "/a#b%20c",
  191. },
  192. {
  193. path: "/a#b c",
  194. want: "/a#b%20c",
  195. },
  196. {
  197. // stdlib's EscapedPath() will always percent-encode '\'
  198. path: "/\\",
  199. want: "/%5C",
  200. },
  201. {
  202. path: "/a\\",
  203. want: "/a%5C",
  204. },
  205. {
  206. path: "/a,b.c.",
  207. want: "/a,b.c.",
  208. },
  209. {
  210. path: "/.",
  211. want: "/.",
  212. },
  213. {
  214. // stdlib's EscapedPath() will always percent-encode '`'
  215. path: "/a`",
  216. want: "/a%60",
  217. },
  218. {
  219. path: "/a[0]",
  220. want: "/a[0]",
  221. },
  222. {
  223. path: "/?a[0]=5 &b[]=",
  224. want: "/?a[0]=5 &b[]=",
  225. },
  226. {
  227. path: "/?a=%22b%20%22",
  228. want: "/?a=%22b%20%22",
  229. },
  230. }
  231. for index, testCase := range testCases {
  232. requestURL := "https://example.com"
  233. request, err := http.NewRequest(http.MethodGet, requestURL, nil)
  234. assert.NoError(t, err)
  235. mockRequestHeaders := []Header{
  236. {Name: ":path", Value: testCase.path},
  237. {Name: RequestUserHeadersField, Value: SerializeHeaders(http.Header{"Mock header": {"Mock value"}})},
  238. }
  239. headersConversionErr := H2RequestHeadersToH1Request(mockRequestHeaders, request)
  240. assert.NoError(t, headersConversionErr)
  241. assert.Equal(t,
  242. http.Header{
  243. "Mock header": []string{"Mock value"},
  244. },
  245. request.Header)
  246. assert.Equal(t,
  247. "https://example.com"+testCase.want,
  248. request.URL.String(),
  249. "Failed URL index: %v %#v", index, testCase)
  250. }
  251. }
  252. func TestH2RequestHeadersToH1Request_QuickCheck(t *testing.T) {
  253. config := &quick.Config{
  254. Values: func(args []reflect.Value, rand *rand.Rand) {
  255. args[0] = reflect.ValueOf(randomHTTP2Path(t, rand))
  256. },
  257. }
  258. type testOrigin struct {
  259. url string
  260. expectedScheme string
  261. expectedBasePath string
  262. }
  263. testOrigins := []testOrigin{
  264. {
  265. url: "http://origin.hostname.example.com:8080",
  266. expectedScheme: "http",
  267. expectedBasePath: "http://origin.hostname.example.com:8080",
  268. },
  269. {
  270. url: "http://origin.hostname.example.com:8080/",
  271. expectedScheme: "http",
  272. expectedBasePath: "http://origin.hostname.example.com:8080",
  273. },
  274. {
  275. url: "http://origin.hostname.example.com:8080/api",
  276. expectedScheme: "http",
  277. expectedBasePath: "http://origin.hostname.example.com:8080/api",
  278. },
  279. {
  280. url: "http://origin.hostname.example.com:8080/api/",
  281. expectedScheme: "http",
  282. expectedBasePath: "http://origin.hostname.example.com:8080/api",
  283. },
  284. {
  285. url: "https://origin.hostname.example.com:8080/api",
  286. expectedScheme: "https",
  287. expectedBasePath: "https://origin.hostname.example.com:8080/api",
  288. },
  289. }
  290. // use multiple schemes to demonstrate that the URL is based on the
  291. // origin's scheme, not the :scheme header
  292. for _, testScheme := range []string{"http", "https"} {
  293. for _, testOrigin := range testOrigins {
  294. assertion := func(testPath string) bool {
  295. const expectedMethod = "POST"
  296. const expectedHostname = "request.hostname.example.com"
  297. h2 := []Header{
  298. {Name: ":method", Value: expectedMethod},
  299. {Name: ":scheme", Value: testScheme},
  300. {Name: ":authority", Value: expectedHostname},
  301. {Name: ":path", Value: testPath},
  302. {Name: RequestUserHeadersField, Value: ""},
  303. }
  304. h1, err := http.NewRequest("GET", testOrigin.url, nil)
  305. require.NoError(t, err)
  306. err = H2RequestHeadersToH1Request(h2, h1)
  307. return assert.NoError(t, err) &&
  308. assert.Equal(t, expectedMethod, h1.Method) &&
  309. assert.Equal(t, expectedHostname, h1.Host) &&
  310. assert.Equal(t, testOrigin.expectedScheme, h1.URL.Scheme) &&
  311. assert.Equal(t, testOrigin.expectedBasePath+testPath, h1.URL.String())
  312. }
  313. err := quick.Check(assertion, config)
  314. assert.NoError(t, err)
  315. }
  316. }
  317. }
  318. func randomASCIIPrintableChar(rand *rand.Rand) int {
  319. // smallest printable ASCII char is 32, largest is 126
  320. const startPrintable = 32
  321. const endPrintable = 127
  322. return startPrintable + rand.Intn(endPrintable-startPrintable)
  323. }
  324. // randomASCIIText generates an ASCII string, some of whose characters may be
  325. // percent-encoded. Its "logical length" (ignoring percent-encoding) is
  326. // between 1 and `maxLength`.
  327. func randomASCIIText(rand *rand.Rand, minLength int, maxLength int) string {
  328. length := minLength + rand.Intn(maxLength)
  329. var result strings.Builder
  330. for i := 0; i < length; i++ {
  331. c := randomASCIIPrintableChar(rand)
  332. // 1/4 chance of using percent encoding when not necessary
  333. if c == '%' || rand.Intn(4) == 0 {
  334. result.WriteString(fmt.Sprintf("%%%02X", c))
  335. } else {
  336. result.WriteByte(byte(c))
  337. }
  338. }
  339. return result.String()
  340. }
  341. // Calls `randomASCIIText` and ensures the result is a valid URL path,
  342. // i.e. one that can pass unchanged through url.URL.String()
  343. func randomHTTP1Path(t *testing.T, rand *rand.Rand, minLength int, maxLength int) string {
  344. text := randomASCIIText(rand, minLength, maxLength)
  345. re, err := regexp.Compile("[^/;,]*")
  346. require.NoError(t, err)
  347. return "/" + re.ReplaceAllStringFunc(text, url.PathEscape)
  348. }
  349. // Calls `randomASCIIText` and ensures the result is a valid URL query,
  350. // i.e. one that can pass unchanged through url.URL.String()
  351. func randomHTTP1Query(rand *rand.Rand, minLength int, maxLength int) string {
  352. text := randomASCIIText(rand, minLength, maxLength)
  353. return "?" + strings.ReplaceAll(text, "#", "%23")
  354. }
  355. // Calls `randomASCIIText` and ensures the result is a valid URL fragment,
  356. // i.e. one that can pass unchanged through url.URL.String()
  357. func randomHTTP1Fragment(t *testing.T, rand *rand.Rand, minLength int, maxLength int) string {
  358. text := randomASCIIText(rand, minLength, maxLength)
  359. u, err := url.Parse("#" + text)
  360. require.NoError(t, err)
  361. return u.String()
  362. }
  363. // Assemble a random :path pseudoheader that is legal by Go stdlib standards
  364. // (i.e. all characters will satisfy "net/url".shouldEscape for their respective locations)
  365. func randomHTTP2Path(t *testing.T, rand *rand.Rand) string {
  366. result := randomHTTP1Path(t, rand, 1, 64)
  367. if rand.Intn(2) == 1 {
  368. result += randomHTTP1Query(rand, 1, 32)
  369. }
  370. if rand.Intn(2) == 1 {
  371. result += randomHTTP1Fragment(t, rand, 1, 16)
  372. }
  373. return result
  374. }
  375. func stdlibHeaderToH2muxHeader(headers http.Header) (h2muxHeaders []Header) {
  376. for name, values := range headers {
  377. for _, value := range values {
  378. h2muxHeaders = append(h2muxHeaders, Header{name, value})
  379. }
  380. }
  381. return h2muxHeaders
  382. }
  383. func TestSerializeHeaders(t *testing.T) {
  384. request, err := http.NewRequest(http.MethodGet, "http://example.com", nil)
  385. assert.NoError(t, err)
  386. mockHeaders := http.Header{
  387. "Mock-Header-One": {"Mock header one value", "three"},
  388. "Mock-Header-Two-Long": {"Mock header two value\nlong"},
  389. ":;": {":;", ";:"},
  390. ":": {":"},
  391. ";": {";"},
  392. ";;": {";;"},
  393. "Empty values": {"", ""},
  394. "": {"Empty key"},
  395. "control\tcharacter\b\n": {"value\n\b\t"},
  396. ";\v:": {":\v;"},
  397. }
  398. for header, values := range mockHeaders {
  399. for _, value := range values {
  400. // Note that Golang's http library is opinionated;
  401. // at this point every header name will be title-cased in order to comply with the HTTP RFC
  402. // This means our proxy is not completely transparent when it comes to proxying headers
  403. request.Header.Add(header, value)
  404. }
  405. }
  406. serializedHeaders := SerializeHeaders(request.Header)
  407. // Sanity check: the headers serialized to something that's not an empty string
  408. assert.NotEqual(t, "", serializedHeaders)
  409. // Deserialize back, and ensure we get the same set of headers
  410. deserializedHeaders, err := DeserializeHeaders(serializedHeaders)
  411. assert.NoError(t, err)
  412. assert.Equal(t, 13, len(deserializedHeaders))
  413. h2muxExpectedHeaders := stdlibHeaderToH2muxHeader(mockHeaders)
  414. sort.Sort(ByName(deserializedHeaders))
  415. sort.Sort(ByName(h2muxExpectedHeaders))
  416. assert.True(
  417. t,
  418. reflect.DeepEqual(h2muxExpectedHeaders, deserializedHeaders),
  419. fmt.Sprintf("got = %#v, want = %#v\n", deserializedHeaders, h2muxExpectedHeaders),
  420. )
  421. }
  422. func TestSerializeNoHeaders(t *testing.T) {
  423. request, err := http.NewRequest(http.MethodGet, "http://example.com", nil)
  424. assert.NoError(t, err)
  425. serializedHeaders := SerializeHeaders(request.Header)
  426. deserializedHeaders, err := DeserializeHeaders(serializedHeaders)
  427. assert.NoError(t, err)
  428. assert.Equal(t, 0, len(deserializedHeaders))
  429. }
  430. func TestDeserializeMalformed(t *testing.T) {
  431. var err error
  432. malformedData := []string{
  433. "malformed data",
  434. "bW9jawo=", // "mock"
  435. "bW9jawo=:ZGF0YQo=:bW9jawo=", // "mock:data:mock"
  436. "::",
  437. }
  438. for _, malformedValue := range malformedData {
  439. _, err = DeserializeHeaders(malformedValue)
  440. assert.Error(t, err)
  441. }
  442. }
  443. func TestParseHeaders(t *testing.T) {
  444. mockUserHeadersToSerialize := http.Header{
  445. "Mock-Header-One": {"1", "1.5"},
  446. "Mock-Header-Two": {"2"},
  447. "Mock-Header-Three": {"3"},
  448. }
  449. mockHeaders := []Header{
  450. {Name: "One", Value: "1"}, // will be dropped
  451. {Name: "Cf-Two", Value: "cf-value-1"},
  452. {Name: "Cf-Two", Value: "cf-value-2"},
  453. {Name: RequestUserHeadersField, Value: SerializeHeaders(mockUserHeadersToSerialize)},
  454. }
  455. expectedHeaders := []Header{
  456. {Name: "Cf-Two", Value: "cf-value-1"},
  457. {Name: "Cf-Two", Value: "cf-value-2"},
  458. {Name: "Mock-Header-One", Value: "1"},
  459. {Name: "Mock-Header-One", Value: "1.5"},
  460. {Name: "Mock-Header-Two", Value: "2"},
  461. {Name: "Mock-Header-Three", Value: "3"},
  462. }
  463. h1 := &http.Request{
  464. Header: make(http.Header),
  465. }
  466. err := H2RequestHeadersToH1Request(mockHeaders, h1)
  467. assert.NoError(t, err)
  468. assert.ElementsMatch(t, expectedHeaders, stdlibHeaderToH2muxHeader(h1.Header))
  469. }
  470. func TestIsControlHeader(t *testing.T) {
  471. controlHeaders := []string{
  472. // Anything that begins with cf-
  473. "cf-sample-header",
  474. // Any http2 pseudoheader
  475. ":sample-pseudo-header",
  476. // content-length is a special case, it has to be there
  477. // for some requests to work (per the HTTP2 spec)
  478. "content-length",
  479. }
  480. for _, header := range controlHeaders {
  481. assert.True(t, IsControlHeader(header))
  482. }
  483. }
  484. func TestIsNotControlHeader(t *testing.T) {
  485. notControlHeaders := []string{
  486. "mock-header",
  487. "another-sample-header",
  488. }
  489. for _, header := range notControlHeaders {
  490. assert.False(t, IsControlHeader(header))
  491. }
  492. }
  493. func TestH1ResponseToH2ResponseHeaders(t *testing.T) {
  494. mockHeaders := http.Header{
  495. "User-header-one": {""},
  496. "User-header-two": {"1", "2"},
  497. "cf-header": {"cf-value"},
  498. "Content-Length": {"123"},
  499. }
  500. mockResponse := http.Response{
  501. StatusCode: 200,
  502. Header: mockHeaders,
  503. }
  504. headers := H1ResponseToH2ResponseHeaders(mockResponse.StatusCode, mockResponse.Header)
  505. serializedHeadersIndex := -1
  506. for i, header := range headers {
  507. if header.Name == ResponseUserHeadersField {
  508. serializedHeadersIndex = i
  509. break
  510. }
  511. }
  512. assert.NotEqual(t, -1, serializedHeadersIndex)
  513. actualControlHeaders := append(
  514. headers[:serializedHeadersIndex],
  515. headers[serializedHeadersIndex+1:]...,
  516. )
  517. expectedControlHeaders := []Header{
  518. {Name: ":status", Value: "200"},
  519. {Name: "content-length", Value: "123"},
  520. }
  521. assert.ElementsMatch(t, expectedControlHeaders, actualControlHeaders)
  522. actualUserHeaders, err := DeserializeHeaders(headers[serializedHeadersIndex].Value)
  523. expectedUserHeaders := []Header{
  524. {Name: "User-header-one", Value: ""},
  525. {Name: "User-header-two", Value: "1"},
  526. {Name: "User-header-two", Value: "2"},
  527. }
  528. assert.NoError(t, err)
  529. assert.ElementsMatch(t, expectedUserHeaders, actualUserHeaders)
  530. }
  531. // The purpose of this test is to check that our code and the http.Header
  532. // implementation don't throw validation errors about header size
  533. func TestHeaderSize(t *testing.T) {
  534. largeValue := randSeq(5 * 1024 * 1024) // 5Mb
  535. largeHeaders := http.Header{
  536. "User-header": {largeValue},
  537. }
  538. mockResponse := http.Response{
  539. StatusCode: 200,
  540. Header: largeHeaders,
  541. }
  542. serializedHeaders := H1ResponseToH2ResponseHeaders(mockResponse.StatusCode, mockResponse.Header)
  543. request, err := http.NewRequest(http.MethodGet, "https://example.com/", nil)
  544. assert.NoError(t, err)
  545. for _, header := range serializedHeaders {
  546. request.Header.Set(header.Name, header.Value)
  547. }
  548. for _, header := range serializedHeaders {
  549. if header.Name != ResponseUserHeadersField {
  550. continue
  551. }
  552. deserializedHeaders, err := DeserializeHeaders(header.Value)
  553. assert.NoError(t, err)
  554. assert.Equal(t, largeValue, deserializedHeaders[0].Value)
  555. }
  556. }
  557. func randSeq(n int) string {
  558. randomizer := rand.New(rand.NewSource(17))
  559. var letters = []rune(":;,+/=abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ")
  560. b := make([]rune, n)
  561. for i := range b {
  562. b[i] = letters[randomizer.Intn(len(letters))]
  563. }
  564. return string(b)
  565. }
  566. func BenchmarkH1ResponseToH2ResponseHeaders(b *testing.B) {
  567. ser := "eC1mb3J3YXJkZWQtcHJvdG8:aHR0cHM;dXBncmFkZS1pbnNlY3VyZS1yZXF1ZXN0cw:MQ;YWNjZXB0LWxhbmd1YWdl:ZW4tVVMsZW47cT0wLjkscnU7cT0wLjg;YWNjZXB0LWVuY29kaW5n:Z3ppcA;eC1mb3J3YXJkZWQtZm9y:MTczLjI0NS42MC42;dXNlci1hZ2VudA:TW96aWxsYS81LjAgKE1hY2ludG9zaDsgSW50ZWwgTWFjIE9TIFggMTBfMTRfNikgQXBwbGVXZWJLaXQvNTM3LjM2IChLSFRNTCwgbGlrZSBHZWNrbykgQ2hyb21lLzg0LjAuNDE0Ny44OSBTYWZhcmkvNTM3LjM2;c2VjLWZldGNoLW1vZGU:bmF2aWdhdGU;Y2RuLWxvb3A:Y2xvdWRmbGFyZQ;c2VjLWZldGNoLWRlc3Q:ZG9jdW1lbnQ;c2VjLWZldGNoLXVzZXI:PzE;c2VjLWZldGNoLXNpdGU:bm9uZQ;Y29va2ll:X19jZmR1aWQ9ZGNkOWZjOGNjNWMxMzE0NTMyYTFkMjhlZDEyOWRhOTYwMTU2OTk1MTYzNDsgX19jZl9ibT1mYzY2MzMzYzAzZmM0MWFiZTZmOWEyYzI2ZDUwOTA0YzIxYzZhMTQ2LTE1OTU2MjIzNDEtMTgwMC1BZTVzS2pIU2NiWGVFM05mMUhrTlNQMG1tMHBLc2pQWkloVnM1Z2g1SkNHQkFhS1UxVDB2b003alBGN3FjMHVSR2NjZGcrWHdhL1EzbTJhQzdDVU4xZ2M9;YWNjZXB0:dGV4dC9odG1sLGFwcGxpY2F0aW9uL3hodG1sK3htbCxhcHBsaWNhdGlvbi94bWw7cT0wLjksaW1hZ2Uvd2VicCxpbWFnZS9hcG5nLCovKjtxPTAuOCxhcHBsaWNhdGlvbi9zaWduZWQtZXhjaGFuZ2U7dj1iMztxPTAuOQ"
  568. h2, _ := DeserializeHeaders(ser)
  569. h1 := make(http.Header)
  570. for _, header := range h2 {
  571. h1.Add(header.Name, header.Value)
  572. }
  573. h1.Add("Content-Length", "200")
  574. h1.Add("Cf-Something", "Else")
  575. h1.Add("Upgrade", "websocket")
  576. h1resp := &http.Response{
  577. StatusCode: 200,
  578. Header: h1,
  579. }
  580. b.ReportAllocs()
  581. b.ResetTimer()
  582. for i := 0; i < b.N; i++ {
  583. _ = H1ResponseToH2ResponseHeaders(h1resp.StatusCode, h1resp.Header)
  584. }
  585. }