-
Notifications
You must be signed in to change notification settings - Fork 1.9k
Expand file tree
/
Copy pathstreaming.go
More file actions
148 lines (136 loc) · 3.33 KB
/
Copy pathstreaming.go
File metadata and controls
148 lines (136 loc) · 3.33 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
package fasthttp
import (
"bufio"
"bytes"
"io"
"sync"
"github.com/valyala/bytebufferpool"
)
type bodyStreamHeader interface {
ContentLength() int
ReadTrailer(r *bufio.Reader) error
}
type requestStream struct {
header bodyStreamHeader
prefetchedBytes bytes.Reader
reader *bufio.Reader
contentLength int
totalBytesRead int
chunkLeft int
strictEOF bool
eof bool
}
func (rs *requestStream) Read(p []byte) (int, error) {
if rs.reader == nil {
panic("BUG: reading released body stream")
}
// The stream is terminal once the body has ended. Without this, a chunked
// stream re-enters parseChunkSize on the next Read and blocks waiting for a
// chunk header that is never coming: a keep-alive connection stays open
// after the body ends, so nothing wakes the read. Any caller that reads a
// streamed body to EOF and then reads again - draining before release is the
// common case - would park a goroutine and never release the connection.
if rs.eof {
return 0, io.EOF
}
var (
n int
err error
)
contentLength := rs.contentLength
if contentLength == -1 {
if rs.chunkLeft == 0 {
chunkSize, err := parseChunkSize(rs.reader)
if err != nil {
return 0, err
}
if chunkSize == 0 {
err = rs.header.ReadTrailer(rs.reader)
if err != nil && err != io.EOF {
return 0, err
}
rs.eof = true
return 0, io.EOF
}
rs.chunkLeft = chunkSize
}
bytesToRead := min(rs.chunkLeft, len(p))
n, err = rs.reader.Read(p[:bytesToRead])
rs.totalBytesRead += n
rs.chunkLeft -= n
if err == io.EOF {
err = io.ErrUnexpectedEOF
}
if err == nil && rs.chunkLeft == 0 {
err = readCrLf(rs.reader)
}
return n, err
}
if rs.totalBytesRead == contentLength {
rs.eof = true
return 0, io.EOF
}
prefetchedSize := int(rs.prefetchedBytes.Size())
if prefetchedSize > rs.totalBytesRead {
left := prefetchedSize - rs.totalBytesRead
if len(p) > left {
p = p[:left]
}
n, err := rs.prefetchedBytes.Read(p)
rs.totalBytesRead += n
if rs.totalBytesRead == contentLength {
rs.eof = true
return n, io.EOF
}
return n, err
}
left := contentLength - rs.totalBytesRead
if left > 0 && len(p) > left {
p = p[:left]
}
n, err = rs.reader.Read(p)
rs.totalBytesRead += n
if err == io.EOF && rs.strictEOF && contentLength >= 0 && rs.totalBytesRead < contentLength {
err = io.ErrUnexpectedEOF
}
if err != nil {
if err == io.EOF {
rs.eof = true
}
return n, err
}
if rs.totalBytesRead == contentLength {
rs.eof = true
err = io.EOF
}
return n, err
}
func acquireRequestStream(b *bytebufferpool.ByteBuffer, r *bufio.Reader, h bodyStreamHeader) *requestStream {
rs := requestStreamPool.Get().(*requestStream) //nolint:forcetypeassert
rs.prefetchedBytes.Reset(b.B)
rs.reader = r
rs.header = h
rs.contentLength = h.ContentLength()
return rs
}
func acquireResponseStream(b *bytebufferpool.ByteBuffer, r *bufio.Reader, h bodyStreamHeader) *requestStream {
rs := acquireRequestStream(b, r, h)
rs.strictEOF = true
return rs
}
func releaseRequestStream(rs *requestStream) {
rs.prefetchedBytes.Reset(nil)
rs.totalBytesRead = 0
rs.chunkLeft = 0
rs.reader = nil
rs.header = nil
rs.contentLength = 0
rs.eof = false
rs.strictEOF = false
requestStreamPool.Put(rs)
}
var requestStreamPool = sync.Pool{
New: func() any {
return &requestStream{}
},
}