Du kannst nicht mehr als 25 Themen auswählen Themen müssen entweder mit einem Buchstaben oder einer Ziffer beginnen. Sie können Bindestriche („-“) enthalten und bis zu 35 Zeichen lang sein.
 
 
 

248 Zeilen
6.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 marbl provides HTTP traffic logs streamed over websockets
  15. // that can be added to any point within a Martian modifier tree.
  16. // Marbl transmits HTTP logs that are serialized based on the following
  17. // schema:
  18. //
  19. // Frame Header
  20. // FrameType uint8
  21. // MessageType uint8
  22. // ID [8]byte
  23. // Payload HeaderFrame/DataFrame
  24. //
  25. // Header Frame
  26. // NameLen uint32
  27. // ValueLen uint32
  28. // Name variable
  29. // Value variable
  30. //
  31. // Data Frame
  32. // Index uint32
  33. // Terminal uint8
  34. // Len uint32
  35. // Data variable
  36. package marbl
  37. import (
  38. "io"
  39. "net/http"
  40. "strconv"
  41. "sync/atomic"
  42. "time"
  43. "github.com/google/martian"
  44. "github.com/google/martian/log"
  45. "github.com/google/martian/proxyutil"
  46. )
  47. // MessageType incicates whether the message represents an HTTP request or response.
  48. type MessageType uint8
  49. const (
  50. // Unknown type of Message.
  51. Unknown MessageType = 0x0
  52. // Request indicates a message that contains an HTTP request.
  53. Request MessageType = 0x1
  54. // Response indicates a message that contains an HTTP response.
  55. Response MessageType = 0x2
  56. )
  57. // FrameType indicates whether the frame contains a Header or Data.
  58. type FrameType uint8
  59. const (
  60. // UnknownFrame indicates an unknown type of Frame.
  61. UnknownFrame FrameType = 0x0
  62. // HeaderFrame indicates a frame that contains a header.
  63. HeaderFrame FrameType = 0x1
  64. // DataFrame indicates a frame that contains the payload, usually the body.
  65. DataFrame FrameType = 0x2
  66. )
  67. // Stream writes logs of requests and responses to a writer.
  68. type Stream struct {
  69. w io.Writer
  70. framec chan []byte
  71. closec chan struct{}
  72. }
  73. // NewStream initializes a Stream with an io.Writer to log requests and
  74. // responses to. Upon construction, a goroutine is started that listens for frames
  75. // and writes them to w.
  76. func NewStream(w io.Writer) *Stream {
  77. s := &Stream{
  78. w: w,
  79. framec: make(chan []byte),
  80. closec: make(chan struct{}),
  81. }
  82. go s.loop()
  83. return s
  84. }
  85. func (s *Stream) loop() {
  86. for {
  87. select {
  88. case f := <-s.framec:
  89. _, err := s.w.Write(f)
  90. if err != nil {
  91. log.Errorf("martian: Error while writing frame")
  92. }
  93. case <-s.closec:
  94. return
  95. }
  96. }
  97. }
  98. // Close signals Stream to stop listening for frames in the log loop and stop writing logs.
  99. func (s *Stream) Close() error {
  100. s.closec <- struct{}{}
  101. close(s.closec)
  102. return nil
  103. }
  104. func newFrame(id string, ft FrameType, mt MessageType, plen uint32) []byte {
  105. f := make([]byte, 0, 10+plen)
  106. f = append(f, byte(ft), byte(mt))
  107. f = append(f, id[:8]...)
  108. return f
  109. }
  110. func (s *Stream) sendHeader(id string, mt MessageType, key, value string) {
  111. kl := uint32(len(key))
  112. vl := uint32(len(value))
  113. f := newFrame(id, HeaderFrame, mt, 64+kl+vl)
  114. f = append(f, byte(kl>>24), byte(kl>>16), byte(kl>>8), byte(kl))
  115. f = append(f, byte(vl>>24), byte(vl>>16), byte(vl>>8), byte(vl))
  116. f = append(f, key[:kl]...)
  117. f = append(f, value[:vl]...)
  118. s.framec <- f
  119. }
  120. func (s *Stream) sendData(id string, mt MessageType, i uint32, terminal bool, b []byte, bl int) {
  121. var ti uint8
  122. if terminal {
  123. ti = 1
  124. }
  125. f := newFrame(id, DataFrame, mt, 72+uint32(bl))
  126. f = append(f, byte(i>>24), byte(i>>16), byte(i>>8), byte(i))
  127. f = append(f, byte(ti))
  128. f = append(f, byte(bl>>24), byte(bl>>16), byte(bl>>8), byte(bl))
  129. f = append(f, b[:bl]...)
  130. s.framec <- f
  131. }
  132. // LogRequest writes an http.Request to Stream with an id unique for the request / response pair.
  133. func (s *Stream) LogRequest(id string, req *http.Request) error {
  134. s.sendHeader(id, Request, ":method", req.Method)
  135. s.sendHeader(id, Request, ":scheme", req.URL.Scheme)
  136. s.sendHeader(id, Request, ":authority", req.URL.Host)
  137. s.sendHeader(id, Request, ":path", req.URL.EscapedPath())
  138. s.sendHeader(id, Request, ":query", req.URL.RawQuery)
  139. s.sendHeader(id, Request, ":proto", req.Proto)
  140. s.sendHeader(id, Request, ":remote", req.RemoteAddr)
  141. ts := strconv.FormatInt(time.Now().UnixNano()/1000/1000, 10)
  142. s.sendHeader(id, Request, ":timestamp", ts)
  143. ctx := martian.NewContext(req)
  144. if ctx.IsAPIRequest() {
  145. s.sendHeader(id, Request, ":api", "true")
  146. }
  147. h := proxyutil.RequestHeader(req)
  148. for k, vs := range h.Map() {
  149. for _, v := range vs {
  150. s.sendHeader(id, Request, k, v)
  151. }
  152. }
  153. req.Body = &bodyLogger{
  154. s: s,
  155. id: id,
  156. mt: Request,
  157. body: req.Body,
  158. }
  159. return nil
  160. }
  161. // LogResponse writes an http.Response to Stream with an id unique for the request / response pair.
  162. func (s *Stream) LogResponse(id string, res *http.Response) error {
  163. s.sendHeader(id, Response, ":proto", res.Proto)
  164. s.sendHeader(id, Response, ":status", strconv.Itoa(res.StatusCode))
  165. s.sendHeader(id, Response, ":reason", res.Status)
  166. ts := strconv.FormatInt(time.Now().UnixNano()/1000/1000, 10)
  167. s.sendHeader(id, Response, ":timestamp", ts)
  168. ctx := martian.NewContext(res.Request)
  169. if ctx.IsAPIRequest() {
  170. s.sendHeader(id, Response, ":api", "true")
  171. }
  172. h := proxyutil.ResponseHeader(res)
  173. for k, vs := range h.Map() {
  174. for _, v := range vs {
  175. s.sendHeader(id, Response, k, v)
  176. }
  177. }
  178. res.Body = &bodyLogger{
  179. s: s,
  180. id: id,
  181. mt: Response,
  182. body: res.Body,
  183. }
  184. return nil
  185. }
  186. type bodyLogger struct {
  187. index uint32 // atomic
  188. s *Stream
  189. id string
  190. mt MessageType
  191. body io.ReadCloser
  192. }
  193. // Read implements the standard Reader interface. Read reads the bytes of the body
  194. // and returns the number of bytes read and an error.
  195. func (bl *bodyLogger) Read(b []byte) (int, error) {
  196. var terminal bool
  197. n, err := bl.body.Read(b)
  198. if err == io.EOF {
  199. terminal = true
  200. }
  201. bl.s.sendData(bl.id, bl.mt, atomic.AddUint32(&bl.index, 1)-1, terminal, b, n)
  202. return n, err
  203. }
  204. // Close closes the bodyLogger.
  205. func (bl *bodyLogger) Close() error {
  206. return bl.body.Close()
  207. }