body_wrapper.go 6.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271
  1. package httpexpect
  2. import (
  3. "bytes"
  4. "context"
  5. "errors"
  6. "io"
  7. "io/ioutil"
  8. "runtime"
  9. "sync"
  10. )
  11. // Wrapper for request or response body reader.
  12. //
  13. // Allows to read body multiple times using two approaches:
  14. // - use Read to read body contents and Rewind to restart reading from beginning
  15. // - use GetBody to get new reader for body contents
  16. //
  17. // When bodyWrapper is created, it does not read anything. Also, until anything is
  18. // read, rewind operations are no-op.
  19. //
  20. // When the user starts reading body, bodyWrapper automatically copies retrieved
  21. // content in memory. Then, when the body is fully read and Rewind is requested,
  22. // it will close original body and switch to reading body from memory.
  23. //
  24. // If Rewind, GetBody, or Close is invoked before the body is fully read first time,
  25. // bodyWrapper automatically performs full read.
  26. //
  27. // At any moment, the user can call DisableRewinds. In this case, Rewind and GetBody
  28. // functionality is disabled, memory cache is cleared, and bodyWrapper switches to
  29. // reading original body (if it's not fully read yet).
  30. //
  31. // bodyWrapper automatically creates finalizer that will close original body if the
  32. // user never reads it fully or calls Closes.
  33. type bodyWrapper struct {
  34. // Protects all operations.
  35. mu sync.Mutex
  36. // Original reader of HTTP response body.
  37. httpReader io.ReadCloser
  38. // Cancellation function for original HTTP response.
  39. // If set, called after HTTP response is fully read into memory.
  40. httpCancelFunc context.CancelFunc
  41. // Reader for HTTP response body stored in memory.
  42. // Rewind() resets this reader to start from the beginning.
  43. memReader io.Reader
  44. // HTTP response body stored in memory.
  45. memBytes []byte
  46. // Cached read and close errors.
  47. readErr error
  48. closeErr error
  49. // If true, Read will not store bytes in memory, and memBytes and memReader
  50. // won't be used.
  51. isRewindDisabled bool
  52. // True means that HTTP response was fully read into memory already.
  53. isFullyRead bool
  54. // True means that a read operation of any type was called at least once.
  55. isReadBefore bool
  56. }
  57. func newBodyWrapper(reader io.ReadCloser, cancelFunc context.CancelFunc) *bodyWrapper {
  58. bw := &bodyWrapper{
  59. httpReader: reader,
  60. httpCancelFunc: cancelFunc,
  61. }
  62. // Finalizer will close body if closeAndCancel was never called.
  63. runtime.SetFinalizer(bw, (*bodyWrapper).Close)
  64. return bw
  65. }
  66. // Read body contents.
  67. func (bw *bodyWrapper) Read(p []byte) (int, error) {
  68. bw.mu.Lock()
  69. defer bw.mu.Unlock()
  70. bw.isReadBefore = true
  71. if bw.isRewindDisabled && !bw.isFullyRead {
  72. // Regular read from original HTTP response.
  73. return bw.httpReader.Read(p)
  74. } else if !bw.isFullyRead {
  75. // Read from original HTTP response + store into memory.
  76. return bw.httpReadNext(p)
  77. } else {
  78. // Read from memory.
  79. return bw.memReadNext(p)
  80. }
  81. }
  82. // Close body.
  83. func (bw *bodyWrapper) Close() error {
  84. bw.mu.Lock()
  85. defer bw.mu.Unlock()
  86. // Preserve original reader error.
  87. err := bw.closeErr
  88. // Rewind or GetBody may be called later, so be sure to
  89. // read body into memory before closing.
  90. if !bw.isRewindDisabled && !bw.isFullyRead {
  91. bw.isReadBefore = true
  92. if readErr := bw.httpReadFull(); readErr != nil {
  93. err = readErr
  94. }
  95. }
  96. // Close original reader.
  97. closeErr := bw.closeAndCancel()
  98. if closeErr != nil {
  99. err = closeErr
  100. }
  101. // Reset memory reader.
  102. bw.memReader = bytes.NewReader(nil)
  103. return err
  104. }
  105. // Rewind reading to the beginning.
  106. func (bw *bodyWrapper) Rewind() {
  107. bw.mu.Lock()
  108. defer bw.mu.Unlock()
  109. // Rewind is no-op if disabled.
  110. if bw.isRewindDisabled {
  111. return
  112. }
  113. // Rewind is no-op until first read operation.
  114. if !bw.isReadBefore {
  115. return
  116. }
  117. // If HTTP response is not fully read yet, do it now.
  118. // If error occurs, it will be reported next read operation.
  119. if !bw.isFullyRead {
  120. _ = bw.httpReadFull()
  121. }
  122. // Reset memory reader.
  123. bw.memReader = bytes.NewReader(bw.memBytes)
  124. }
  125. // Create new reader to retrieve body contents.
  126. // New reader always reads body from the beginning.
  127. // Does not affected by Rewind().
  128. func (bw *bodyWrapper) GetBody() (io.ReadCloser, error) {
  129. bw.mu.Lock()
  130. defer bw.mu.Unlock()
  131. bw.isReadBefore = true
  132. // Preserve original reader error.
  133. if bw.readErr != nil {
  134. return nil, bw.readErr
  135. }
  136. // GetBody() requires rewinds to be enabled.
  137. if bw.isRewindDisabled {
  138. return nil, errors.New("rewinds are disabled, cannot get body")
  139. }
  140. // If HTTP response is not fully read yet, do it now.
  141. if !bw.isFullyRead {
  142. if err := bw.httpReadFull(); err != nil {
  143. return nil, err
  144. }
  145. }
  146. // Return fresh reader for memory chunk.
  147. return ioutil.NopCloser(bytes.NewReader(bw.memBytes)), nil
  148. }
  149. // Disables storing body contents in memory and clears the cache.
  150. func (bw *bodyWrapper) DisableRewinds() {
  151. bw.mu.Lock()
  152. defer bw.mu.Unlock()
  153. bw.isRewindDisabled = true
  154. }
  155. func (bw *bodyWrapper) memReadNext(p []byte) (int, error) {
  156. n, err := bw.memReader.Read(p)
  157. if err == io.EOF && bw.readErr != nil {
  158. err = bw.readErr
  159. }
  160. return n, err
  161. }
  162. func (bw *bodyWrapper) httpReadNext(p []byte) (int, error) {
  163. n, err := bw.httpReader.Read(p)
  164. if n > 0 {
  165. bw.memBytes = append(bw.memBytes, p[:n]...)
  166. }
  167. if err != nil {
  168. if err != io.EOF {
  169. bw.readErr = err
  170. }
  171. if closeErr := bw.closeAndCancel(); closeErr != nil && err == io.EOF {
  172. err = closeErr
  173. }
  174. // Switch to reading from memory.
  175. bw.isFullyRead = true
  176. bw.memReader = bytes.NewReader(nil)
  177. }
  178. return n, err
  179. }
  180. func (bw *bodyWrapper) httpReadFull() error {
  181. b, err := ioutil.ReadAll(bw.httpReader)
  182. // Switch to reading from memory.
  183. bw.isFullyRead = true
  184. bw.memBytes = append(bw.memBytes, b...)
  185. bw.memReader = bytes.NewReader(bw.memBytes[len(bw.memBytes)-len(b):])
  186. if err != nil {
  187. bw.readErr = err
  188. }
  189. if closeErr := bw.closeAndCancel(); closeErr != nil && err == nil {
  190. err = closeErr
  191. }
  192. return err
  193. }
  194. func (bw *bodyWrapper) closeAndCancel() error {
  195. if bw.httpReader == nil && bw.httpCancelFunc == nil {
  196. return bw.closeErr
  197. }
  198. if bw.httpReader != nil {
  199. err := bw.httpReader.Close()
  200. bw.httpReader = nil
  201. if bw.readErr == nil {
  202. bw.readErr = err
  203. }
  204. if bw.closeErr == nil {
  205. bw.closeErr = err
  206. }
  207. }
  208. if bw.httpCancelFunc != nil {
  209. bw.httpCancelFunc()
  210. bw.httpCancelFunc = nil
  211. }
  212. // Finalizer is not needed anymore.
  213. runtime.SetFinalizer(bw, nil)
  214. return bw.closeErr
  215. }