header_util.go 6.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179
  1. package service
  2. import (
  3. "net/http"
  4. "strings"
  5. )
  6. // headerWireCasing 定义每个白名单 header 在真实 Claude CLI 抓包中的准确大小写。
  7. // Go 的 HTTP server 解析请求时会将所有 header key 转为 Canonical 形式(如 x-app → X-App),
  8. // 此 map 用于在转发时恢复到真实的 wire format。
  9. //
  10. // 来源:对真实 Claude CLI (claude-cli/2.1.81) 到 api.anthropic.com 的 HTTPS 流量抓包。
  11. var headerWireCasing = map[string]string{
  12. // Title case
  13. "accept": "Accept",
  14. "user-agent": "User-Agent",
  15. // X-Stainless-* 保持 SDK 原始大小写
  16. "x-stainless-retry-count": "X-Stainless-Retry-Count",
  17. "x-stainless-timeout": "X-Stainless-Timeout",
  18. "x-stainless-lang": "X-Stainless-Lang",
  19. "x-stainless-package-version": "X-Stainless-Package-Version",
  20. "x-stainless-os": "X-Stainless-OS",
  21. "x-stainless-arch": "X-Stainless-Arch",
  22. "x-stainless-runtime": "X-Stainless-Runtime",
  23. "x-stainless-runtime-version": "X-Stainless-Runtime-Version",
  24. "x-stainless-helper-method": "x-stainless-helper-method",
  25. // Anthropic SDK 自身设置的 header,全小写
  26. "anthropic-dangerous-direct-browser-access": "anthropic-dangerous-direct-browser-access",
  27. "anthropic-version": "anthropic-version",
  28. "anthropic-beta": "anthropic-beta",
  29. "x-app": "x-app",
  30. "content-type": "content-type",
  31. "accept-language": "accept-language",
  32. "sec-fetch-mode": "sec-fetch-mode",
  33. "accept-encoding": "accept-encoding",
  34. "authorization": "authorization",
  35. // Claude Code 2.1.87+ 新增 header
  36. "x-claude-code-session-id": "X-Claude-Code-Session-Id",
  37. "x-client-request-id": "x-client-request-id",
  38. "content-length": "content-length",
  39. }
  40. // headerWireOrder 定义真实 Claude CLI 发送 header 的顺序(基于抓包)。
  41. // 用于 debug log 按此顺序输出,便于与抓包结果直接对比。
  42. var headerWireOrder = []string{
  43. "Accept",
  44. "X-Stainless-Retry-Count",
  45. "X-Stainless-Timeout",
  46. "X-Stainless-Lang",
  47. "X-Stainless-Package-Version",
  48. "X-Stainless-OS",
  49. "X-Stainless-Arch",
  50. "X-Stainless-Runtime",
  51. "X-Stainless-Runtime-Version",
  52. "anthropic-dangerous-direct-browser-access",
  53. "anthropic-version",
  54. "authorization",
  55. "x-app",
  56. "User-Agent",
  57. "X-Claude-Code-Session-Id",
  58. "content-type",
  59. "anthropic-beta",
  60. "x-client-request-id",
  61. "accept-language",
  62. "sec-fetch-mode",
  63. "accept-encoding",
  64. "content-length",
  65. "x-stainless-helper-method",
  66. }
  67. // headerWireOrderSet 用于快速判断某个 key 是否在 headerWireOrder 中(按 lowercase 匹配)。
  68. var headerWireOrderSet map[string]struct{}
  69. func init() {
  70. headerWireOrderSet = make(map[string]struct{}, len(headerWireOrder))
  71. for _, k := range headerWireOrder {
  72. headerWireOrderSet[strings.ToLower(k)] = struct{}{}
  73. }
  74. }
  75. // resolveWireCasing 将 Go canonical key(如 X-Stainless-Os)映射为真实 wire casing(如 X-Stainless-OS)。
  76. // 如果 map 中没有对应条目,返回原始 key 不变。
  77. func resolveWireCasing(key string) string {
  78. if wk, ok := headerWireCasing[strings.ToLower(key)]; ok {
  79. return wk
  80. }
  81. return key
  82. }
  83. // setHeaderRaw sets a header bypassing Go's canonical-case normalization.
  84. // The key is stored exactly as provided, preserving original casing.
  85. //
  86. // It first removes any existing value under the canonical key, the wire casing key,
  87. // and the exact raw key, preventing duplicates from any source.
  88. func setHeaderRaw(h http.Header, key, value string) {
  89. h.Del(key) // remove canonical form (e.g. "Anthropic-Beta")
  90. if wk := resolveWireCasing(key); wk != key {
  91. delete(h, wk) // remove wire casing form if different
  92. }
  93. delete(h, key) // remove exact raw key if it differs from canonical
  94. h[key] = []string{value}
  95. }
  96. // addHeaderRaw appends a header value bypassing Go's canonical-case normalization.
  97. func addHeaderRaw(h http.Header, key, value string) {
  98. h[key] = append(h[key], value)
  99. }
  100. // deleteHeaderAllForms removes a header in all common key forms (raw, wire casing,
  101. // canonical) so subsequent setHeaderRaw will not coexist with a passthrough value
  102. // written under a different casing.
  103. func deleteHeaderAllForms(h http.Header, key string) {
  104. if h == nil || key == "" {
  105. return
  106. }
  107. h.Del(key) // canonical
  108. delete(h, key)
  109. if wk := resolveWireCasing(key); wk != key {
  110. delete(h, wk)
  111. }
  112. }
  113. // getHeaderRaw reads a header value, trying multiple key forms to handle the mismatch
  114. // between Go canonical keys, wire casing keys, and raw keys:
  115. // 1. exact key as provided
  116. // 2. wire casing form (from headerWireCasing)
  117. // 3. Go canonical form (via http.Header.Get)
  118. func getHeaderRaw(h http.Header, key string) string {
  119. // 1. exact key
  120. if vals := h[key]; len(vals) > 0 {
  121. return vals[0]
  122. }
  123. // 2. wire casing (e.g. looking up "Anthropic-Dangerous-Direct-Browser-Access" finds "anthropic-dangerous-direct-browser-access")
  124. if wk := resolveWireCasing(key); wk != key {
  125. if vals := h[wk]; len(vals) > 0 {
  126. return vals[0]
  127. }
  128. }
  129. // 3. canonical fallback
  130. return h.Get(key)
  131. }
  132. // sortHeadersByWireOrder 按照真实 Claude CLI 的 header 顺序返回排序后的 key 列表。
  133. // 在 headerWireOrder 中定义的 key 按其顺序排列,未定义的 key 追加到末尾。
  134. func sortHeadersByWireOrder(h http.Header) []string {
  135. // 构建 lowercase -> actual map key 的映射
  136. present := make(map[string]string, len(h))
  137. for k := range h {
  138. present[strings.ToLower(k)] = k
  139. }
  140. result := make([]string, 0, len(h))
  141. seen := make(map[string]struct{}, len(h))
  142. // 先按 wire order 输出
  143. for _, wk := range headerWireOrder {
  144. lk := strings.ToLower(wk)
  145. if actual, ok := present[lk]; ok {
  146. if _, dup := seen[lk]; !dup {
  147. result = append(result, actual)
  148. seen[lk] = struct{}{}
  149. }
  150. }
  151. }
  152. // 再追加不在 wire order 中的 header
  153. for k := range h {
  154. lk := strings.ToLower(k)
  155. if _, ok := seen[lk]; !ok {
  156. result = append(result, k)
  157. seen[lk] = struct{}{}
  158. }
  159. }
  160. return result
  161. }