No puede seleccionar más de 25 temas Los temas deben comenzar con una letra o número, pueden incluir guiones ('-') y pueden tener hasta 35 caracteres de largo.
 
 
 

291 líneas
7.1 KiB

  1. // Copyright 2015 Google Inc. All rights reserved.
  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. // Package messageview provides no-op snapshots for HTTP requests and
  15. // responses.
  16. package messageview
  17. import (
  18. "bytes"
  19. "compress/flate"
  20. "compress/gzip"
  21. "fmt"
  22. "io"
  23. "io/ioutil"
  24. "net/http"
  25. "net/http/httputil"
  26. "strings"
  27. )
  28. // MessageView is a static view of an HTTP request or response.
  29. type MessageView struct {
  30. message []byte
  31. cts []string
  32. chunked bool
  33. skipBody bool
  34. compress string
  35. bodyoffset int64
  36. traileroffset int64
  37. }
  38. type config struct {
  39. decode bool
  40. }
  41. // Option is a configuration option for a MessageView.
  42. type Option func(*config)
  43. // Decode sets an option to decode the message body for logging purposes.
  44. func Decode() Option {
  45. return func(c *config) {
  46. c.decode = true
  47. }
  48. }
  49. // New returns a new MessageView.
  50. func New() *MessageView {
  51. return &MessageView{}
  52. }
  53. // SkipBody will skip reading the body when the view is loaded with a request
  54. // or response.
  55. func (mv *MessageView) SkipBody(skipBody bool) {
  56. mv.skipBody = skipBody
  57. }
  58. // SkipBodyUnlessContentType will skip reading the body unless the
  59. // Content-Type matches one in cts.
  60. func (mv *MessageView) SkipBodyUnlessContentType(cts ...string) {
  61. mv.skipBody = true
  62. mv.cts = cts
  63. }
  64. // SnapshotRequest reads the request into the MessageView. If mv.skipBody is false
  65. // it will also read the body into memory and replace the existing body with
  66. // the in-memory copy. This method is semantically a no-op.
  67. func (mv *MessageView) SnapshotRequest(req *http.Request) error {
  68. buf := new(bytes.Buffer)
  69. fmt.Fprintf(buf, "%s %s HTTP/%d.%d\r\n", req.Method,
  70. req.URL, req.ProtoMajor, req.ProtoMinor)
  71. if req.Host != "" {
  72. fmt.Fprintf(buf, "Host: %s\r\n", req.Host)
  73. }
  74. if tec := len(req.TransferEncoding); tec > 0 {
  75. mv.chunked = req.TransferEncoding[tec-1] == "chunked"
  76. fmt.Fprintf(buf, "Transfer-Encoding: %s\r\n", strings.Join(req.TransferEncoding, ", "))
  77. }
  78. if !mv.chunked && req.ContentLength >= 0 {
  79. fmt.Fprintf(buf, "Content-Length: %d\r\n", req.ContentLength)
  80. }
  81. mv.compress = req.Header.Get("Content-Encoding")
  82. req.Header.WriteSubset(buf, map[string]bool{
  83. "Host": true,
  84. "Content-Length": true,
  85. "Transfer-Encoding": true,
  86. })
  87. fmt.Fprint(buf, "\r\n")
  88. mv.bodyoffset = int64(buf.Len())
  89. mv.traileroffset = int64(buf.Len())
  90. ct := req.Header.Get("Content-Type")
  91. if mv.skipBody && !mv.matchContentType(ct) || req.Body == nil {
  92. mv.message = buf.Bytes()
  93. return nil
  94. }
  95. data, err := ioutil.ReadAll(req.Body)
  96. if err != nil {
  97. return err
  98. }
  99. req.Body.Close()
  100. if mv.chunked {
  101. cw := httputil.NewChunkedWriter(buf)
  102. cw.Write(data)
  103. cw.Close()
  104. } else {
  105. buf.Write(data)
  106. }
  107. mv.traileroffset = int64(buf.Len())
  108. req.Body = ioutil.NopCloser(bytes.NewReader(data))
  109. if req.Trailer != nil {
  110. req.Trailer.Write(buf)
  111. } else if mv.chunked {
  112. fmt.Fprint(buf, "\r\n")
  113. }
  114. mv.message = buf.Bytes()
  115. return nil
  116. }
  117. // SnapshotResponse reads the response into the MessageView. If mv.headersOnly
  118. // is false it will also read the body into memory and replace the existing
  119. // body with the in-memory copy. This method is semantically a no-op.
  120. func (mv *MessageView) SnapshotResponse(res *http.Response) error {
  121. buf := new(bytes.Buffer)
  122. fmt.Fprintf(buf, "HTTP/%d.%d %s\r\n", res.ProtoMajor, res.ProtoMinor, res.Status)
  123. if tec := len(res.TransferEncoding); tec > 0 {
  124. mv.chunked = res.TransferEncoding[tec-1] == "chunked"
  125. fmt.Fprintf(buf, "Transfer-Encoding: %s\r\n", strings.Join(res.TransferEncoding, ", "))
  126. }
  127. if !mv.chunked && res.ContentLength >= 0 {
  128. fmt.Fprintf(buf, "Content-Length: %d\r\n", res.ContentLength)
  129. }
  130. mv.compress = res.Header.Get("Content-Encoding")
  131. // Do not uncompress if we have don't have the full contents.
  132. if res.StatusCode == http.StatusNoContent || res.StatusCode == http.StatusPartialContent {
  133. mv.compress = ""
  134. }
  135. res.Header.WriteSubset(buf, map[string]bool{
  136. "Content-Length": true,
  137. "Transfer-Encoding": true,
  138. })
  139. fmt.Fprint(buf, "\r\n")
  140. mv.bodyoffset = int64(buf.Len())
  141. mv.traileroffset = int64(buf.Len())
  142. ct := res.Header.Get("Content-Type")
  143. if mv.skipBody && !mv.matchContentType(ct) || res.Body == nil {
  144. mv.message = buf.Bytes()
  145. return nil
  146. }
  147. data, err := ioutil.ReadAll(res.Body)
  148. if err != nil {
  149. return err
  150. }
  151. res.Body.Close()
  152. if mv.chunked {
  153. cw := httputil.NewChunkedWriter(buf)
  154. cw.Write(data)
  155. cw.Close()
  156. } else {
  157. buf.Write(data)
  158. }
  159. mv.traileroffset = int64(buf.Len())
  160. res.Body = ioutil.NopCloser(bytes.NewReader(data))
  161. if res.Trailer != nil {
  162. res.Trailer.Write(buf)
  163. } else if mv.chunked {
  164. fmt.Fprint(buf, "\r\n")
  165. }
  166. mv.message = buf.Bytes()
  167. return nil
  168. }
  169. // Reader returns the an io.ReadCloser that reads the full HTTP message.
  170. func (mv *MessageView) Reader(opts ...Option) (io.ReadCloser, error) {
  171. hr := mv.HeaderReader()
  172. br, err := mv.BodyReader(opts...)
  173. if err != nil {
  174. return nil, err
  175. }
  176. tr := mv.TrailerReader()
  177. return struct {
  178. io.Reader
  179. io.Closer
  180. }{
  181. Reader: io.MultiReader(hr, br, tr),
  182. Closer: br,
  183. }, nil
  184. }
  185. // HeaderReader returns an io.Reader that reads the HTTP Status-Line or
  186. // HTTP Request-Line and headers.
  187. func (mv *MessageView) HeaderReader() io.Reader {
  188. r := bytes.NewReader(mv.message)
  189. return io.NewSectionReader(r, 0, mv.bodyoffset)
  190. }
  191. // BodyReader returns an io.ReadCloser that reads the HTTP request or response
  192. // body. If mv.skipBody was set the reader will immediately return io.EOF.
  193. //
  194. // If the Decode option is passed the body will be unchunked if
  195. // Transfer-Encoding is set to "chunked", and will decode the following
  196. // Content-Encodings: gzip, deflate.
  197. func (mv *MessageView) BodyReader(opts ...Option) (io.ReadCloser, error) {
  198. var r io.Reader
  199. conf := &config{}
  200. for _, o := range opts {
  201. o(conf)
  202. }
  203. br := bytes.NewReader(mv.message)
  204. r = io.NewSectionReader(br, mv.bodyoffset, mv.traileroffset-mv.bodyoffset)
  205. if !conf.decode {
  206. return ioutil.NopCloser(r), nil
  207. }
  208. if mv.chunked {
  209. r = httputil.NewChunkedReader(r)
  210. }
  211. switch mv.compress {
  212. case "gzip":
  213. gr, err := gzip.NewReader(r)
  214. if err != nil {
  215. return nil, err
  216. }
  217. return gr, nil
  218. case "deflate":
  219. return flate.NewReader(r), nil
  220. default:
  221. return ioutil.NopCloser(r), nil
  222. }
  223. }
  224. // TrailerReader returns an io.Reader that reads the HTTP request or response
  225. // trailers, if present.
  226. func (mv *MessageView) TrailerReader() io.Reader {
  227. r := bytes.NewReader(mv.message)
  228. end := int64(len(mv.message)) - mv.traileroffset
  229. return io.NewSectionReader(r, mv.traileroffset, end)
  230. }
  231. func (mv *MessageView) matchContentType(mct string) bool {
  232. for _, ct := range mv.cts {
  233. if strings.HasPrefix(mct, ct) {
  234. return true
  235. }
  236. }
  237. return false
  238. }