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  }