Вы не можете выбрать более 25 тем Темы должны начинаться с буквы или цифры, могут содержать дефисы(-) и должны содержать не более 35 символов.
 
 
 

210 строки
5.6 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 body allows for the replacement of message body on responses.
  15. package body
  16. import (
  17. "bytes"
  18. "crypto/rand"
  19. "encoding/json"
  20. "fmt"
  21. "io"
  22. "io/ioutil"
  23. "mime/multipart"
  24. "net/http"
  25. "net/textproto"
  26. "strconv"
  27. "strings"
  28. "github.com/google/martian/log"
  29. "github.com/google/martian/parse"
  30. )
  31. func init() {
  32. parse.Register("body.Modifier", modifierFromJSON)
  33. }
  34. // Modifier substitutes the body on an HTTP response.
  35. type Modifier struct {
  36. contentType string
  37. body []byte
  38. boundary string
  39. }
  40. type modifierJSON struct {
  41. ContentType string `json:"contentType"`
  42. Body []byte `json:"body"` // Body is expected to be a Base64 encoded string.
  43. Scope []parse.ModifierType `json:"scope"`
  44. }
  45. // NewModifier constructs and returns a body.Modifier.
  46. func NewModifier(b []byte, contentType string) *Modifier {
  47. log.Debugf("body.NewModifier: len(b): %d, contentType %s", len(b), contentType)
  48. return &Modifier{
  49. contentType: contentType,
  50. body: b,
  51. boundary: randomBoundary(),
  52. }
  53. }
  54. // modifierFromJSON takes a JSON message as a byte slice and returns a
  55. // body.Modifier and an error.
  56. //
  57. // Example JSON Configuration message:
  58. // {
  59. // "scope": ["request", "response"],
  60. // "contentType": "text/plain",
  61. // "body": "c29tZSBkYXRhIHdpdGggACBhbmQg77u/" // Base64 encoded body
  62. // }
  63. func modifierFromJSON(b []byte) (*parse.Result, error) {
  64. msg := &modifierJSON{}
  65. if err := json.Unmarshal(b, msg); err != nil {
  66. return nil, err
  67. }
  68. mod := NewModifier(msg.Body, msg.ContentType)
  69. return parse.NewResult(mod, msg.Scope)
  70. }
  71. // ModifyRequest sets the Content-Type header and overrides the request body.
  72. func (m *Modifier) ModifyRequest(req *http.Request) error {
  73. log.Debugf("body.ModifyRequest: request: %s", req.URL)
  74. req.Body.Close()
  75. req.Header.Set("Content-Type", m.contentType)
  76. // Reset the Content-Encoding since we know that the new body isn't encoded.
  77. req.Header.Del("Content-Encoding")
  78. req.ContentLength = int64(len(m.body))
  79. req.Body = ioutil.NopCloser(bytes.NewReader(m.body))
  80. return nil
  81. }
  82. // SetBoundary set the boundary string used for multipart range responses.
  83. func (m *Modifier) SetBoundary(boundary string) {
  84. m.boundary = boundary
  85. }
  86. // ModifyResponse sets the Content-Type header and overrides the response body.
  87. func (m *Modifier) ModifyResponse(res *http.Response) error {
  88. log.Debugf("body.ModifyResponse: request: %s", res.Request.URL)
  89. // Replace the existing body, close it first.
  90. res.Body.Close()
  91. res.Header.Set("Content-Type", m.contentType)
  92. // Reset the Content-Encoding since we know that the new body isn't encoded.
  93. res.Header.Del("Content-Encoding")
  94. // If no range request header is present, return the body as the response body.
  95. if res.Request.Header.Get("Range") == "" {
  96. res.ContentLength = int64(len(m.body))
  97. res.Body = ioutil.NopCloser(bytes.NewReader(m.body))
  98. return nil
  99. }
  100. rh := res.Request.Header.Get("Range")
  101. rh = strings.ToLower(rh)
  102. sranges := strings.Split(strings.TrimLeft(rh, "bytes="), ",")
  103. var ranges [][]int
  104. for _, rng := range sranges {
  105. if strings.HasSuffix(rng, "-") {
  106. rng = fmt.Sprintf("%s%d", rng, len(m.body)-1)
  107. }
  108. rs := strings.Split(rng, "-")
  109. if len(rs) != 2 {
  110. res.StatusCode = http.StatusRequestedRangeNotSatisfiable
  111. return nil
  112. }
  113. start, err := strconv.Atoi(strings.TrimSpace(rs[0]))
  114. if err != nil {
  115. return err
  116. }
  117. end, err := strconv.Atoi(strings.TrimSpace(rs[1]))
  118. if err != nil {
  119. return err
  120. }
  121. if start > end {
  122. res.StatusCode = http.StatusRequestedRangeNotSatisfiable
  123. return nil
  124. }
  125. ranges = append(ranges, []int{start, end})
  126. }
  127. // Range request.
  128. res.StatusCode = http.StatusPartialContent
  129. // Single range request.
  130. if len(ranges) == 1 {
  131. start := ranges[0][0]
  132. end := ranges[0][1]
  133. seg := m.body[start : end+1]
  134. res.ContentLength = int64(len(seg))
  135. res.Body = ioutil.NopCloser(bytes.NewReader(seg))
  136. res.Header.Set("Content-Range", fmt.Sprintf("bytes %d-%d/%d", start, end, len(m.body)))
  137. return nil
  138. }
  139. // Multipart range request.
  140. var mpbody bytes.Buffer
  141. mpw := multipart.NewWriter(&mpbody)
  142. mpw.SetBoundary(m.boundary)
  143. for _, rng := range ranges {
  144. start, end := rng[0], rng[1]
  145. mimeh := make(textproto.MIMEHeader)
  146. mimeh.Set("Content-Type", m.contentType)
  147. mimeh.Set("Content-Range", fmt.Sprintf("bytes %d-%d/%d", start, end, len(m.body)))
  148. seg := m.body[start : end+1]
  149. pw, err := mpw.CreatePart(mimeh)
  150. if err != nil {
  151. return err
  152. }
  153. if _, err := pw.Write(seg); err != nil {
  154. return err
  155. }
  156. }
  157. mpw.Close()
  158. res.ContentLength = int64(len(mpbody.Bytes()))
  159. res.Body = ioutil.NopCloser(bytes.NewReader(mpbody.Bytes()))
  160. res.Header.Set("Content-Type", fmt.Sprintf("multipart/byteranges; boundary=%s", m.boundary))
  161. return nil
  162. }
  163. // randomBoundary generates a 30 character string for boundaries for mulipart range
  164. // requests. This func panics if io.Readfull fails.
  165. // Borrowed from: https://golang.org/src/mime/multipart/writer.go?#L73
  166. func randomBoundary() string {
  167. var buf [30]byte
  168. _, err := io.ReadFull(rand.Reader, buf[:])
  169. if err != nil {
  170. panic(err)
  171. }
  172. return fmt.Sprintf("%x", buf[:])
  173. }