You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 

345 lines
8.8 KiB

  1. // Copyright 2018 Google LLC
  2. //
  3. // Licensed under the Apache License, Version 2.0 (the "License");
  4. // you may not use this file except in compliance with the License.
  5. // You may obtain a copy of the License at
  6. //
  7. // http://www.apache.org/licenses/LICENSE-2.0
  8. //
  9. // Unless required by applicable law or agreed to in writing, software
  10. // distributed under the License is distributed on an "AS IS" BASIS,
  11. // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  12. // See the License for the specific language governing permissions and
  13. // limitations under the License.
  14. // +build go1.8
  15. package proxy
  16. import (
  17. "bytes"
  18. "encoding/json"
  19. "errors"
  20. "fmt"
  21. "io"
  22. "io/ioutil"
  23. "log"
  24. "mime"
  25. "mime/multipart"
  26. "net/http"
  27. "reflect"
  28. "strconv"
  29. "strings"
  30. "github.com/google/martian/har"
  31. "github.com/google/martian/martianlog"
  32. )
  33. // ForReplaying returns a Proxy configured to replay.
  34. func ForReplaying(filename string, port int) (*Proxy, error) {
  35. p, err := newProxy(filename)
  36. if err != nil {
  37. return nil, err
  38. }
  39. calls, initial, err := readLog(filename)
  40. if err != nil {
  41. return nil, err
  42. }
  43. p.mproxy.SetRoundTripper(replayRoundTripper{calls: calls})
  44. p.Initial = initial
  45. // Debug logging.
  46. // TODO(jba): factor out from here and ForRecording.
  47. logger := martianlog.NewLogger()
  48. logger.SetDecode(true)
  49. p.mproxy.SetRequestModifier(logger)
  50. p.mproxy.SetResponseModifier(logger)
  51. if err := p.start(port); err != nil {
  52. return nil, err
  53. }
  54. return p, nil
  55. }
  56. // A call is an HTTP request and its matching response.
  57. type call struct {
  58. req *har.Request
  59. reqBody *requestBody // parsed request body
  60. res *har.Response
  61. }
  62. func readLog(filename string) ([]*call, []byte, error) {
  63. bytes, err := ioutil.ReadFile(filename)
  64. if err != nil {
  65. return nil, nil, err
  66. }
  67. var f httprFile
  68. if err := json.Unmarshal(bytes, &f); err != nil {
  69. return nil, nil, fmt.Errorf("%s: %v", filename, err)
  70. }
  71. ignoreIDs := map[string]bool{} // IDs of requests to ignore
  72. callsByID := map[string]*call{}
  73. var calls []*call
  74. for _, e := range f.HAR.Log.Entries {
  75. if ignoreIDs[e.ID] {
  76. continue
  77. }
  78. c, ok := callsByID[e.ID]
  79. switch {
  80. case !ok:
  81. if e.Request == nil {
  82. return nil, nil, fmt.Errorf("first entry for ID %s does not have a request", e.ID)
  83. }
  84. if e.Request.Method == "CONNECT" {
  85. // Ignore CONNECT methods.
  86. ignoreIDs[e.ID] = true
  87. } else {
  88. reqBody, err := newRequestBodyFromHAR(e.Request)
  89. if err != nil {
  90. return nil, nil, err
  91. }
  92. c := &call{e.Request, reqBody, e.Response}
  93. calls = append(calls, c)
  94. callsByID[e.ID] = c
  95. }
  96. case e.Request != nil:
  97. if e.Response != nil {
  98. return nil, nil, errors.New("HAR entry has both request and response")
  99. }
  100. c.req = e.Request
  101. case e.Response != nil:
  102. c.res = e.Response
  103. default:
  104. return nil, nil, errors.New("HAR entry has neither request nor response")
  105. }
  106. }
  107. for _, c := range calls {
  108. if c.req == nil || c.res == nil {
  109. return nil, nil, fmt.Errorf("missing request or response: %+v", c)
  110. }
  111. }
  112. return calls, f.Initial, nil
  113. }
  114. type replayRoundTripper struct {
  115. calls []*call
  116. }
  117. func (r replayRoundTripper) RoundTrip(req *http.Request) (*http.Response, error) {
  118. reqBody, err := newRequestBodyFromHTTP(req)
  119. if err != nil {
  120. return nil, err
  121. }
  122. for i, call := range r.calls {
  123. if call == nil {
  124. continue
  125. }
  126. if requestsMatch(req, reqBody, call.req, call.reqBody) {
  127. r.calls[i] = nil // nil out this call so we don't reuse it
  128. return harResponseToHTTPResponse(call.res, req), nil
  129. }
  130. }
  131. return nil, fmt.Errorf("no matching request for %+v", req)
  132. }
  133. // Headers that shouldn't be compared, becuase they may differ on different executions
  134. // of the same code, or may not be present during record or replay.
  135. var ignoreHeaders = map[string]bool{}
  136. func init() {
  137. // Sensitive headers are redacted in the log, so they won't be equal to incoming values.
  138. for _, h := range sensitiveHeaders {
  139. ignoreHeaders[h] = true
  140. }
  141. for _, h := range []string{
  142. "Content-Type", // handled by requestBody
  143. "Date",
  144. "Host",
  145. "Transfer-Encoding",
  146. "Via",
  147. "X-Forwarded-For",
  148. "X-Forwarded-Host",
  149. "X-Forwarded-Proto",
  150. "X-Forwarded-Url",
  151. "X-Cloud-Trace-Context", // OpenCensus traces have a random ID
  152. } {
  153. ignoreHeaders[h] = true
  154. }
  155. }
  156. // Report whether the incoming request in matches the candidate request cand.
  157. func requestsMatch(in *http.Request, inBody *requestBody, cand *har.Request, candBody *requestBody) bool {
  158. if in.Method != cand.Method {
  159. return false
  160. }
  161. if in.URL.String() != cand.URL {
  162. return false
  163. }
  164. if !inBody.equal(candBody) {
  165. return false
  166. }
  167. // Check headers last. See DebugHeaders.
  168. return headersMatch(in.Header, harHeadersToHTTP(cand.Headers), ignoreHeaders)
  169. }
  170. func harHeadersToHTTP(hhs []har.Header) http.Header {
  171. res := http.Header{}
  172. for _, hh := range hhs {
  173. res[hh.Name] = append(res[hh.Name], hh.Value)
  174. }
  175. return res
  176. }
  177. // Convert a HAR response to a Go http.Response.
  178. // HAR (Http ARchive) is a standard for storing HTTP interactions.
  179. // See http://www.softwareishard.com/blog/har-12-spec.
  180. func harResponseToHTTPResponse(hr *har.Response, req *http.Request) *http.Response {
  181. res := &http.Response{
  182. StatusCode: hr.Status,
  183. Status: hr.StatusText,
  184. Proto: hr.HTTPVersion,
  185. Header: harHeadersToHTTP(hr.Headers),
  186. Body: ioutil.NopCloser(bytes.NewReader(hr.Content.Text)),
  187. ContentLength: int64(len(hr.Content.Text)),
  188. }
  189. res.Request = req
  190. // For HEAD, set ContentLength to the value of the Content-Length header, or -1
  191. // if there isn't one.
  192. if req.Method == "HEAD" {
  193. res.ContentLength = -1
  194. if c := res.Header["Content-Length"]; len(c) == 1 {
  195. if c64, err := strconv.ParseInt(c[0], 10, 64); err == nil {
  196. res.ContentLength = c64
  197. }
  198. }
  199. }
  200. return res
  201. }
  202. // A requestBody represents the body of a request. If the content type is multipart, the
  203. // body is split into parts.
  204. //
  205. // The replaying proxy needs to understand multipart bodies because the boundaries are
  206. // generated randomly, so we can't just compare the entire bodies for equality.
  207. type requestBody struct {
  208. mediaType string // the media type part of the Content-Type header
  209. parts [][]byte // the parts of the body, or just a single []byte if not multipart
  210. }
  211. func newRequestBodyFromHTTP(req *http.Request) (*requestBody, error) {
  212. defer req.Body.Close()
  213. return newRequestBody(req.Header.Get("Content-Type"), req.Body)
  214. }
  215. func newRequestBodyFromHAR(req *har.Request) (*requestBody, error) {
  216. if req.PostData == nil {
  217. return nil, nil
  218. }
  219. var cth string
  220. for _, h := range req.Headers {
  221. if h.Name == "Content-Type" {
  222. cth = h.Value
  223. break
  224. }
  225. }
  226. return newRequestBody(cth, strings.NewReader(req.PostData.Text))
  227. }
  228. // newRequestBody parses the Content-Type header, reads the body, and splits it into
  229. // parts if necessary.
  230. func newRequestBody(contentType string, body io.Reader) (*requestBody, error) {
  231. if contentType == "" {
  232. // No content-type header. There should not be a body.
  233. if _, err := body.Read(make([]byte, 1)); err != io.EOF {
  234. return nil, errors.New("no Content-Type, but body")
  235. }
  236. return nil, nil
  237. }
  238. mediaType, params, err := mime.ParseMediaType(contentType)
  239. if err != nil {
  240. return nil, err
  241. }
  242. rb := &requestBody{mediaType: mediaType}
  243. if strings.HasPrefix(mediaType, "multipart/") {
  244. mr := multipart.NewReader(body, params["boundary"])
  245. for {
  246. p, err := mr.NextPart()
  247. if err == io.EOF {
  248. break
  249. }
  250. if err != nil {
  251. return nil, err
  252. }
  253. part, err := ioutil.ReadAll(p)
  254. if err != nil {
  255. return nil, err
  256. }
  257. // TODO(jba): care about part headers?
  258. rb.parts = append(rb.parts, part)
  259. }
  260. } else {
  261. bytes, err := ioutil.ReadAll(body)
  262. if err != nil {
  263. return nil, err
  264. }
  265. rb.parts = [][]byte{bytes}
  266. }
  267. return rb, nil
  268. }
  269. func (r1 *requestBody) equal(r2 *requestBody) bool {
  270. if r1 == nil || r2 == nil {
  271. return r1 == r2
  272. }
  273. if r1.mediaType != r2.mediaType {
  274. return false
  275. }
  276. if len(r1.parts) != len(r2.parts) {
  277. return false
  278. }
  279. for i, p1 := range r1.parts {
  280. if !bytes.Equal(p1, r2.parts[i]) {
  281. return false
  282. }
  283. }
  284. return true
  285. }
  286. // DebugHeaders helps to determine whether a header should be ignored.
  287. // When true, if requests have the same method, URL and body but differ
  288. // in a header, the first mismatched header is logged.
  289. var DebugHeaders = false
  290. func headersMatch(in, cand http.Header, ignores map[string]bool) bool {
  291. for k1, v1 := range in {
  292. if ignores[k1] {
  293. continue
  294. }
  295. v2 := cand[k1]
  296. if v2 == nil {
  297. if DebugHeaders {
  298. log.Printf("header %s: present in incoming request but not candidate", k1)
  299. }
  300. return false
  301. }
  302. if !reflect.DeepEqual(v1, v2) {
  303. if DebugHeaders {
  304. log.Printf("header %s: incoming %v, candidate %v", k1, v1, v2)
  305. }
  306. return false
  307. }
  308. }
  309. for k2 := range cand {
  310. if ignores[k2] {
  311. continue
  312. }
  313. if in[k2] == nil {
  314. if DebugHeaders {
  315. log.Printf("header %s: not in incoming request but present in candidate", k2)
  316. }
  317. return false
  318. }
  319. }
  320. return true
  321. }