Source File
transfer.go
Belonging Package
net/http
package http
import (
)
var ErrLineTooLong = internal.ErrLineTooLong
type errorReader struct {
err error
}
func ( errorReader) ( []byte) ( int, error) {
return 0, .err
}
type byteReader struct {
b byte
done bool
}
func ( *byteReader) ( []byte) ( int, error) {
if .done {
return 0, io.EOF
}
if len() == 0 {
return 0, nil
}
.done = true
[0] = .b
return 1, io.EOF
}
type transferWriter struct {
Method string
Body io.Reader
BodyCloser io.Closer
ResponseToHEAD bool
ContentLength int64 // -1 means unknown, 0 means exactly none
Close bool
TransferEncoding []string
Header Header
Trailer Header
IsResponse bool
bodyReadError error // any non-EOF error from reading Body
FlushHeaders bool // flush headers to network before body
ByteReadCh chan readResult // non-nil if probeRequestBody called
}
func ( interface{}) ( *transferWriter, error) {
= &transferWriter{}
:= false
switch rr := .(type) {
case *Request:
if .ContentLength != 0 && .Body == nil {
return nil, fmt.Errorf("http: Request.ContentLength=%d with nil Body", .ContentLength)
}
.Method = valueOrDefault(.Method, "GET")
.Close = .Close
.TransferEncoding = .TransferEncoding
.Header = .Header
.Trailer = .Trailer
.Body = .Body
.BodyCloser = .Body
.ContentLength = .outgoingLength()
if .ContentLength < 0 && len(.TransferEncoding) == 0 && .shouldSendChunkedRequestBody() {
.TransferEncoding = []string{"chunked"}
if .ContentLength != 0 && !isKnownInMemoryReader(.Body) {
.FlushHeaders = true
}
= true // Transport requests are always 1.1 or 2.0
case *Response:
.IsResponse = true
if .Request != nil {
.Method = .Request.Method
}
.Body = .Body
.BodyCloser = .Body
.ContentLength = .ContentLength
.Close = .Close
.TransferEncoding = .TransferEncoding
.Header = .Header
.Trailer = .Trailer
= .ProtoAtLeast(1, 1)
.ResponseToHEAD = noResponseBodyExpected(.Method)
}
if .ResponseToHEAD {
.Body = nil
if chunked(.TransferEncoding) {
.ContentLength = -1
}
} else {
if ! || .Body == nil {
.TransferEncoding = nil
}
if chunked(.TransferEncoding) {
.ContentLength = -1
} else if .Body == nil { // no chunking, no body
.ContentLength = 0
}
}
if !chunked(.TransferEncoding) {
.Trailer = nil
}
return , nil
}
.probeRequestBody() // adjusts t.Body, t.ContentLength
return .Body != nil
return true
}
func ( *transferWriter) () {
.ByteReadCh = make(chan readResult, 1)
go func( io.Reader) {
var [1]byte
var readResult
.n, .err = .Read([:])
if .n == 1 {
.b = [0]
}
.ByteReadCh <-
}(.Body)
:= time.NewTimer(200 * time.Millisecond)
select {
case := <-.ByteReadCh:
.Stop()
.Body = nil
.ContentLength = 0
} else if .n == 1 {
if .err != nil {
.Body = io.MultiReader(&byteReader{b: .b}, errorReader{.err})
} else {
.Body = io.MultiReader(&byteReader{b: .b}, .Body)
}
} else if .err != nil {
.Body = errorReader{.err}
}
.FlushHeaders = true
}
}
func ( string) bool {
return == "HEAD"
}
func ( *transferWriter) () bool {
if chunked(.TransferEncoding) {
return false
}
if .ContentLength > 0 {
return true
}
if .ContentLength < 0 {
return false
if .Method == "POST" || .Method == "PUT" || .Method == "PATCH" {
return true
}
if .ContentLength == 0 && isIdentity(.TransferEncoding) {
if .Method == "GET" || .Method == "HEAD" {
return false
}
return true
}
return false
}
func ( *transferWriter) ( io.Writer, *httptrace.ClientTrace) error {
if .Close && !hasToken(.Header.get("Connection"), "close") {
if , := io.WriteString(, "Connection: close\r\n"); != nil {
return
}
if != nil && .WroteHeaderField != nil {
.WroteHeaderField("Connection", []string{"close"})
}
}
if .shouldSendContentLength() {
if , := io.WriteString(, "Content-Length: "); != nil {
return
}
if , := io.WriteString(, strconv.FormatInt(.ContentLength, 10)+"\r\n"); != nil {
return
}
if != nil && .WroteHeaderField != nil {
.WroteHeaderField("Content-Length", []string{strconv.FormatInt(.ContentLength, 10)})
}
} else if chunked(.TransferEncoding) {
if , := io.WriteString(, "Transfer-Encoding: chunked\r\n"); != nil {
return
}
if != nil && .WroteHeaderField != nil {
.WroteHeaderField("Transfer-Encoding", []string{"chunked"})
}
}
if , := io.WriteString(, "Trailer: "+strings.Join(, ",")+"\r\n"); != nil {
return
}
if != nil && .WroteHeaderField != nil {
.WroteHeaderField("Trailer", )
}
}
}
return nil
}
func ( *transferWriter) ( io.Writer) ( error) {
var int64
:= false
defer func() {
if || .BodyCloser == nil {
return
}
if := .BodyCloser.Close(); != nil && == nil {
=
}
}()
if .Body != nil {
var = .unwrapBody()
if chunked(.TransferEncoding) {
if , := .(*bufio.Writer); && !.IsResponse {
= &internal.FlushAfterChunkWriter{Writer: }
}
:= internal.NewChunkedWriter()
_, = .doBodyCopy(, )
if == nil {
= .Close()
}
} else if .ContentLength == -1 {
:=
if .Method == "CONNECT" {
= bufioFlushWriter{}
}
, = .doBodyCopy(, )
} else {
, = .doBodyCopy(, io.LimitReader(, .ContentLength))
if != nil {
return
}
var int64
, = .doBodyCopy(io.Discard, )
+=
}
if != nil {
return
}
}
if .BodyCloser != nil {
= true
if := .BodyCloser.Close(); != nil {
return
}
}
if !.ResponseToHEAD && .ContentLength != -1 && .ContentLength != {
return fmt.Errorf("http: ContentLength=%d with Body length %d",
.ContentLength, )
}
_, = io.WriteString(, "\r\n")
}
return
}
func ( *transferWriter) () io.Reader {
if reflect.TypeOf(.Body) == nopCloserType {
return reflect.ValueOf(.Body).Field(0).Interface().(io.Reader)
}
if , := .Body.(*readTrackingBody); {
.didRead = true
return .ReadCloser
}
return .Body
}
Body io.ReadCloser
ContentLength int64
Chunked bool
Close bool
Trailer Header
}
func ( *transferReader) (, int) bool {
return .ProtoMajor > || (.ProtoMajor == && .ProtoMinor >= )
}
func ( int) bool {
switch {
case >= 100 && <= 199:
return false
case == 204:
return false
case == 304:
return false
}
return true
}
var (
suppressedHeaders304 = []string{"Content-Type", "Content-Length", "Transfer-Encoding"}
suppressedHeadersNoBody = []string{"Content-Length", "Transfer-Encoding"}
)
func ( int) []string {
switch {
return suppressedHeaders304
case !bodyAllowedForStatus():
return suppressedHeadersNoBody
}
return nil
}
func ( interface{}, *bufio.Reader) ( error) {
:= &transferReader{RequestMethod: "GET"}
:= false
switch rr := .(type) {
case *Response:
.Header = .Header
.StatusCode = .StatusCode
.ProtoMajor = .ProtoMajor
.ProtoMinor = .ProtoMinor
.Close = shouldClose(.ProtoMajor, .ProtoMinor, .Header, true)
= true
if .Request != nil {
.RequestMethod = .Request.Method
}
case *Request:
.Header = .Header
.RequestMethod = .Method
.ProtoMajor = .ProtoMajor
.StatusCode = 200
.Close = .Close
default:
panic("unexpected type")
}
if .ProtoMajor == 0 && .ProtoMinor == 0 {
.ProtoMajor, .ProtoMinor = 1, 1
}
if := .parseTransferEncoding(); != nil {
return
}
, := fixLength(, .StatusCode, .RequestMethod, .Header, .Chunked)
if != nil {
return
}
if && .RequestMethod == "HEAD" {
if , := parseContentLength(.Header.get("Content-Length")); != nil {
return
} else {
.ContentLength =
}
} else {
.ContentLength =
}
.Trailer, = fixTrailer(.Header, .Chunked)
if != nil {
return
}
switch .(type) {
case *Response:
switch {
case .Chunked:
if noResponseBodyExpected(.RequestMethod) || !bodyAllowedForStatus(.StatusCode) {
.Body = NoBody
} else {
.Body = &body{src: internal.NewChunkedReader(), hdr: , r: , closing: .Close}
}
case == 0:
.Body = NoBody
case > 0:
.Body = &body{src: io.LimitReader(, ), closing: .Close}
switch rr := .(type) {
case *Request:
.Body = .Body
.ContentLength = .ContentLength
if .Chunked {
.TransferEncoding = []string{"chunked"}
}
.Close = .Close
.Trailer = .Trailer
case *Response:
.Body = .Body
.ContentLength = .ContentLength
if .Chunked {
.TransferEncoding = []string{"chunked"}
}
.Close = .Close
.Trailer = .Trailer
}
return nil
}
type unsupportedTEError struct {
err string
}
func ( *unsupportedTEError) () string {
return .err
}
func ( error) bool {
, := .(*unsupportedTEError)
return
}
if !.protoAtLeast(1, 1) {
return nil
}
if len() != 1 {
return &unsupportedTEError{fmt.Sprintf("too many transfer encodings: %q", )}
}
if strings.ToLower(textproto.TrimString([0])) != "chunked" {
return &unsupportedTEError{fmt.Sprintf("unsupported transfer encoding: %q", [0])}
}
:= textproto.TrimString([0])
for , := range [1:] {
if != textproto.TrimString() {
return 0, fmt.Errorf("http: message cannot contain multiple Content-Length headers; got %q", )
}
}
if {
return -1, nil
}
var string
if len() == 1 {
= textproto.TrimString([0])
}
if != "" {
, := parseContentLength()
if != nil {
return -1,
}
return , nil
}
.Del("Content-Length")
return 0, nil
}
return -1, nil
}
func (, int, Header, bool) bool {
if < 1 {
return true
}
:= ["Connection"]
:= httpguts.HeaderValuesContainsToken(, "close")
if == 1 && == 0 {
return || !httpguts.HeaderValuesContainsToken(, "keep-alive")
}
if && {
.Del("Connection")
}
return
}
return nil, nil
}
.Del("Trailer")
:= make(Header)
var error
for , := range {
foreachHeaderElement(, func( string) {
= CanonicalHeaderKey()
switch {
case "Transfer-Encoding", "Trailer", "Content-Length":
if == nil {
= badStringError("bad trailer key", )
return
}
}
[] = nil
})
}
if != nil {
return nil,
}
if len() == 0 {
return nil, nil
}
return , nil
}
type body struct {
src io.Reader
hdr interface{} // non-nil (Response or Request) value means read trailer
r *bufio.Reader // underlying wire-format reader for the trailer
closing bool // is the connection to be closed after reading body?
doEarlyClose bool // whether Close should stop early
mu sync.Mutex // guards following, and calls to Read and Close
sawEOF bool
closed bool
earlyClose bool // Close called and we didn't read to the end of src
onHitEOF func() // if non-nil, func to call when EOF is Read
}
var ErrBodyReadAfterClose = errors.New("http: invalid Read on closed Body")
func ( *body) ( []byte) ( int, error) {
.mu.Lock()
defer .mu.Unlock()
if .closed {
return 0, ErrBodyReadAfterClose
}
return .readLocked()
}
if .hdr != nil {
if := .readTrailer(); != nil {
if , := .src.(*io.LimitedReader); && .N > 0 {
= io.ErrUnexpectedEOF
}
}
}
, := .Peek()
if bytes.HasSuffix(, doubleCRLF) {
return true
}
if != nil {
break
}
}
return false
}
var errTrailerEOF = errors.New("http: unexpected EOF reading trailer")
if !seeUpcomingDoubleCRLF(.r) {
return errors.New("http: suspiciously long trailer after chunked body")
}
, := textproto.NewReader(.r).ReadMIMEHeader()
if != nil {
if == io.EOF {
return errTrailerEOF
}
return
}
switch rr := .hdr.(type) {
case *Request:
mergeSetHeader(&.Trailer, Header())
case *Response:
mergeSetHeader(&.Trailer, Header())
}
return nil
}
func ( *Header, Header) {
if * == nil {
* =
return
}
for , := range {
(*)[] =
}
}
.earlyClose = true
} else {
, = io.CopyN(io.Discard, bodyLocked{}, maxPostHandlerReadBytes)
if == io.EOF {
= nil
}
if == maxPostHandlerReadBytes {
.earlyClose = true
}
}
type bodyLocked struct {
b *body
}
func ( bodyLocked) ( []byte) ( int, error) {
if .b.closed {
return 0, ErrBodyReadAfterClose
}
return .b.readLocked()
}
type finishAsyncByteRead struct {
tw *transferWriter
}
func ( finishAsyncByteRead) ( []byte) ( int, error) {
if len() == 0 {
return
}
:= <-.tw.ByteReadCh
, = .n, .err
if == 1 {
[0] = .b
}
return
}
var nopCloserType = reflect.TypeOf(io.NopCloser(nil))
![]() |
The pages are generated with Golds v0.3.2-preview. (GOOS=darwin GOARCH=amd64) Golds is a Go 101 project developed by Tapir Liu. PR and bug reports are welcome and can be submitted to the issue list. Please follow @Go100and1 (reachable from the left QR code) to get the latest news of Golds. |