github.com/searKing/golang/go@v1.2.74/net/http/rewind.go (about) 1 // Copyright 2022 The searKing Author. All rights reserved. 2 // Use of this source code is governed by a BSD-style 3 // license that can be found in the LICENSE file. 4 5 package http 6 7 import ( 8 "bytes" 9 "errors" 10 "io" 11 "io/ioutil" 12 "net/http" 13 "os" 14 "reflect" 15 "strings" 16 17 io_ "github.com/searKing/golang/go/io" 18 ) 19 20 var ( 21 ErrBodyNotRewindable = errors.New("body not rewindable") 22 ) 23 var nopCloserType = reflect.TypeOf(io.NopCloser(nil)) 24 25 func RequestRewindableWithFileName(name string) (body io.ReadCloser, getBody func() (io.ReadCloser, error), err error) { 26 return BodyRewindableWithFilePosition(name, 0, io.SeekCurrent) 27 } 28 29 func BodyRewindableWithFilePosition(name string, offset int64, whence int) (body io.ReadCloser, getBody func() (io.ReadCloser, error), err error) { 30 file, err := os.Open(name) 31 if err != nil { 32 return nil, nil, err 33 } 34 35 if _, err = file.Seek(offset, whence); err != nil { 36 return nil, nil, err 37 } 38 39 return BodyRewindableWithFile(file) 40 } 41 42 // BodyRewindableWithFile returns a Request suitable for use with Redirect, like 307 redirect for PUT or POST. 43 // Only a nil GetBody in Request may be replace with a rewindable GetBody, which is a Body *os.File. 44 // See: https://github.com/golang/go/issues/7912 45 // See also: https://go-review.googlesource.com/c/go/+/29852/13/src/net/http/client.go#391 46 func BodyRewindableWithFile(file *os.File) (body io.ReadCloser, getBody func() (io.ReadCloser, error), err error) { 47 offset, err := file.Seek(0, io.SeekCurrent) 48 if err != nil { 49 return nil, nil, err 50 } 51 52 return file, func() (io.ReadCloser, error) { 53 file_, err_ := os.Open(file.Name()) 54 if err_ != nil { 55 return nil, err 56 } 57 58 if _, err_ = file_.Seek(offset, io.SeekStart); err != nil { 59 return nil, err_ 60 } 61 return file_, err_ 62 }, nil 63 } 64 65 // RequestWithBodyRewindable returns a Request suitable for use with Redirect, like 307 redirect for PUT or POST. 66 // Only a nil GetBody in Request may be replaced with a rewindable GetBody, which is a Body replayer. 67 // A body with a type not ioutil.NopCloser(nil) may return error as the Body in Request will be closed before redirect automatically. 68 // So you can close body by yourself to ensure rewindable always: 69 // Examples: 70 // body := req.Body 71 // defer body.Close() // body will not be closed inside 72 // req.Body = ioutil.NopCloser(body) 73 // _ = RequestWithBodyRewindable(req) 74 // // do http requests... 75 // 76 // See: https://github.com/golang/go/issues/7912 77 // See also: https://go-review.googlesource.com/c/go/+/29852/13/src/net/http/client.go#391 78 func RequestWithBodyRewindable(req *http.Request) error { 79 if req.Body == nil || req.Body == http.NoBody { 80 // No copying needed. 81 return nil 82 } 83 84 // If the request body can be reset back to its original 85 // state via the optional req.GetBody, do that. 86 if req.GetBody != nil { 87 return nil 88 } 89 90 body, neverClose := isNeverCloseReader(req.Body) 91 if !neverClose { 92 // Body in Request will be closed before redirect automatically, so io.Seeker can not be used. 93 94 // take care of *os.File, which can be reopen 95 switch body_ := body.(type) { 96 case *os.File: 97 _, getBody, err := BodyRewindableWithFile(body_) 98 if err != nil { 99 return err 100 } 101 req.GetBody = getBody 102 return nil 103 } 104 return ErrBodyNotRewindable 105 } 106 107 // handle never closed body 108 109 // NewRequest and NewRequestWithContext in net/http will handle 110 // See: https://github.com/golang/go/blob/2117ea9737bc9cb2e30cb087b76a283f68768819/src/net/http/request.go#L873 111 switch v := body.(type) { 112 case *bytes.Buffer: 113 req.ContentLength = int64(v.Len()) 114 buf := v.Bytes() 115 req.GetBody = func() (io.ReadCloser, error) { 116 r := bytes.NewReader(buf) 117 return io.NopCloser(r), nil 118 } 119 return nil 120 case *bytes.Reader: 121 req.ContentLength = int64(v.Len()) 122 snapshot := *v 123 req.GetBody = func() (io.ReadCloser, error) { 124 r := snapshot 125 return io.NopCloser(&r), nil 126 } 127 return nil 128 case *strings.Reader: 129 req.ContentLength = int64(v.Len()) 130 snapshot := *v 131 req.GetBody = func() (io.ReadCloser, error) { 132 r := snapshot 133 return io.NopCloser(&r), nil 134 } 135 return nil 136 case *os.File: 137 _, getBody, err := BodyRewindableWithFile(v) 138 if err != nil { 139 return err 140 } 141 req.GetBody = getBody 142 return nil 143 } 144 145 // Handle unknown types 146 147 // Use io.Seeker to rewind if Seek succeed 148 { 149 if seeker, ok := body.(io.Seeker); ok { 150 offset, err := seeker.Seek(0, io.SeekCurrent) 151 if err == nil { 152 req.GetBody = func() (io.ReadCloser, error) { 153 _, err := seeker.Seek(offset, io.SeekStart) 154 if err != nil { 155 return nil, err 156 } 157 return req.Body, nil 158 } 159 } 160 } 161 } 162 163 // Use a replay reader to capture any body sent in case we have to replay it again 164 // All data will be buffered in memory in case of reread. 165 { 166 replayR := io_.ReplayReader(req.Body) 167 replayRC := replayReadCloser{Reader: replayR, Closer: req.Body} 168 req.Body = replayRC 169 req.GetBody = func() (io.ReadCloser, error) { 170 replayR.Replay() 171 172 // Refresh the body reader so the body can be sent again 173 // take care of req.Body set to nil by caller outside 174 if req.Body == nil { 175 return nil, nil 176 } 177 return ioutil.NopCloser(replayR), nil 178 } 179 } 180 return nil 181 } 182 183 type replayReadCloser struct { 184 io.Reader 185 io.Closer 186 } 187 188 // isNeverCloseReader reports whether r is a type known to not closed. 189 // Its caller uses this as an optional optimization to 190 // send fewer TCP packets. 191 func isNeverCloseReader(r io.Reader) (rr io.Reader, nopClose bool) { 192 return _isNeverCloseReader(r, false) 193 } 194 195 func _isNeverCloseReader(r io.Reader, nopclose bool) (rr io.Reader, nopClose bool) { 196 switch r.(type) { 197 case *bytes.Reader, *bytes.Buffer, *strings.Reader: 198 return r, true 199 } 200 if reflect.TypeOf(r) == nopCloserType { 201 return _isNeverCloseReader(reflect.ValueOf(r).Field(0).Interface().(io.Reader), true) 202 } 203 return r, nopclose 204 }