You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
For the past few months, I've been working on a fasthttp based HTTP client. I struggled to find a way to read only a maximum number of bytes from the HTTP response. All testing was performed against live servers.
Obviously, MaxResponseBodysize is the important factor here, so I began testing various implementations.
Below, I am providing sample code snippets that I tried until I got a working PoC.
In all examples, I am setting MaxResponseBodySize to 12288 bytes as I figured out that this includes response headers too, and for example, sites like github.com use a long list of headers.
Approach 1
const (
maxBodySize=12288// Limit the maximum body size we are willing to read completelyrwBufferSize=maxBodySize+4096RespBodyPreviewSize=1024// I want a sample preview of 1024 bytes from the response body
)
StreamResponseBody: false// This is the default setting in fasthttp
package main
import (
"crypto/tls""fmt""log""github.com/VictoriaMetrics/VictoriaMetrics/lib/bytesutil""github.com/valyala/fasthttp"
)
var (
strSpace= []byte(" ")
strCRLF= []byte("\r\n")
strColonSpace= []byte(": ")
)
varheaderBufPool bytesutil.ByteBufferPooltypeRawHTTPResponseDetailsstruct {
StatusCodeintResponsePreview []byte// I want a sample preview bytes from the response bodyResponseHeaders []byteContentType []byteContentLengthint64ServerInfo []byteResponseBytesint// Total bytes of the response received (Headers + Body up to maxBodySize)
}
const (
maxBodySize=12288// Limit the maximum body size we are willing to read completelyrwBufferSize=maxBodySize+4096RespBodyPreviewSize=1024// I want a sample preview of 1024 bytes from the response body
)
funcmain() {
url:="https://www.sample-videos.com/video321/mp4/360/big_buck_bunny_360p_30mb.mp4"// 30 mb videotlsConfig:=&tls.Config{
InsecureSkipVerify: true,
}
client:=&fasthttp.Client{
MaxResponseBodySize: maxBodySize, // Set the maximum response body sizeReadBufferSize: rwBufferSize, // Set read buffer sizeWriteBufferSize: rwBufferSize, // Set write buffer sizeStreamResponseBody: false, // By default, fasthttp uses StreamResponseBody = falseTLSConfig: tlsConfig,
}
req:=fasthttp.AcquireRequest()
deferfasthttp.ReleaseRequest(req)
req.SetRequestURI(url)
req.Header.SetMethod(fasthttp.MethodGet)
resp:=fasthttp.AcquireResponse()
deferfasthttp.ReleaseResponse(resp)
err:=client.Do(req, resp)
// Check for errors but ALLOW ErrBodyTooLarge!iferr!=nil&&err!=fasthttp.ErrBodyTooLarge {
log.Fatalf("Error fetching URL %s: %v", url, err)
}
respDetails:=RawHTTPResponseDetails{}
respDetails.StatusCode=resp.StatusCode()
respDetails.ResponseHeaders=GetResponseHeaders(&resp.Header, respDetails.StatusCode, respDetails.ResponseHeaders)
body:=resp.Body()
bodyLen:=len(body)
// Determine the preview sizepreviewSize:=RespBodyPreviewSizeifbodyLen<previewSize {
previewSize=bodyLen
}
// Create and populate the ResponsePreviewpreview:=make([]byte, previewSize)
copy(preview, body[:previewSize])
respDetails.ResponsePreview=preview// Populate other detailsrespDetails.ContentType=resp.Header.ContentType()
respDetails.ContentLength=int64(resp.Header.ContentLength())
respDetails.ServerInfo=resp.Header.Server()
// Calculate total received bytes (header size + received body size)respDetails.ResponseBytes=resp.Header.Len() +bodyLenfmt.Println("--- Collected Response Details ---")
fmt.Printf("Status Code: %d\n", respDetails.StatusCode)
fmt.Printf("\nResponse Headers (%d bytes):\n", len(respDetails.ResponseHeaders))
fmt.Println(string(respDetails.ResponseHeaders)) // Print headers including status linefmt.Printf("Content-Type: %s\n", string(respDetails.ContentType))
fmt.Printf("Content-Length Header: %d\n", respDetails.ContentLength)
fmt.Printf("Server: %s\n", string(respDetails.ServerInfo))
fmt.Printf("Total Bytes Received (Headers + Body Preview/Truncated Body): %d\n", respDetails.ResponseBytes)
fmt.Printf("\nResponse Body Preview (%d bytes):\n", len(respDetails.ResponsePreview))
fmt.Println(string(respDetails.ResponsePreview))
iferr==fasthttp.ErrBodyTooLarge {
fmt.Println("\nNote: Response body exceeded MaxResponseBodySize.")
fmt.Printf("Full response body is likely larger than the received %d bytes (maxBodySize = %d).\n", bodyLen, maxBodySize)
} elseifint64(bodyLen) >RespBodyPreviewSize&&respDetails.ContentLength>int64(RespBodyPreviewSize) {
fmt.Printf("\nNote: Full response body (%d bytes reported by Content-Length) is larger than the preview size (%d bytes).\n", respDetails.ContentLength, RespBodyPreviewSize)
} elseifint64(bodyLen) >RespBodyPreviewSize {
fmt.Printf("\nNote: Response body received (%d bytes) is larger than the preview size (%d bytes).\n", bodyLen, RespBodyPreviewSize)
}
}
// GetResponseHeaders helperfuncGetResponseHeaders(h*fasthttp.ResponseHeader, statusCodeint, dest []byte) []byte {
headerBuf:=headerBufPool.Get()
deferheaderBufPool.Put(headerBuf)
headerBuf.Write(h.Protocol())
headerBuf.Write(strSpace)
headerBuf.B=fasthttp.AppendUint(headerBuf.B, statusCode)
headerBuf.Write(strSpace)
headerBuf.Write(h.StatusMessage())
headerBuf.Write(strCRLF)
h.VisitAll(func(key, value []byte) {
headerBuf.Write(key)
headerBuf.Write(strColonSpace)
headerBuf.Write(value)
headerBuf.Write(strCRLF)
})
headerBuf.Write(strCRLF)
returnappend(dest[:0], headerBuf.B...)
}
Output:
go run .\main.go
--- Collected Response Details ---
Status Code: 200
Response Headers (268 bytes):
HTTP/1.1 200 OK
Content-Length: 31466742
Content-Type: video/mp4
Server: Apache/2.4.6 (CentOS) OpenSSL/1.0.2k-fips PHP/7.3.23
Date: Wed, 09 Apr 2025 09:00:29 GMT
Last-Modified: Tue, 20 Oct 2020 10:45:05 GMT
Etag: "1e024f6-5b217ec8840b6"
Accept-Ranges: bytes
Content-Type: video/mp4
Content-Length Header: 31466742
Server: Apache/2.4.6 (CentOS) OpenSSL/1.0.2k-fips PHP/7.3.23
Total Bytes Received (Headers + Body Preview/Truncated Body): 7
Response Body Preview (0 bytes):
Note: Response body exceeded MaxResponseBodySize.
Full response body is likely larger than the received 0 bytes (maxBodySize = 12288).
This approach failed, and it also failed on many other servers that didn't include a Content-Lenght header in the response.
Approach 2
const (
maxBodySize=12288// Limit the maximum body size we are willing to read completelyrwBufferSize=maxBodySize+4096RespBodyPreviewSize=1024// I want a sample preview of 1024 bytes from the response body
)
StreamResponseBody: true// Enabled StreamResponseBody
Looking back through my git history, other sample helper I tried for this approach:
// ReadLimitedResponseBodyStream reads limited bytes from a response body stream// Appends the result to dest slicefuncReadLimitedResponseBodyStream(stream io.Reader, previewSizeint, dest []byte) []byte {
// Create a buffer for previewpreviewBuf:=bytes.NewBuffer(make([]byte, 0, previewSize))
// Read limited amount of dataif_, err:=io.CopyN(previewBuf, stream, int64(previewSize)); err!=nil&&err!=io.EOF {
returnappend(dest[:0], strErrorReadingPreview...)
}
returnappend(dest[:0], previewBuf.Bytes()...)
}
While this approach worked better and allowed me to successfully obtain a response preview, it still failed in most test cases, though not all. It was noted that it failed in concurrent requests. Sample errors:
Error: error when reading response headers: cannot find whitespace in the first line of response ";\xbb\xaa\x9a\x15\xb2\b\xb8\xa8\x00\xe7\xa1\xda=Ӻ\xac=\x03\xa6\x87\xf5Xz\xad:\xbb\xc2\xd8g\xc28\x8d&F`\xa6\x12\xd0\xeb\xa9\r\xa7\x97\x98Ϋ\xddxg\xd9HȢж\x9a\x8b\x89\b\xb4+\x90\x14\f\xf7h\xc3\xd0\xdcF\\\xef[\x0e\xf8C\x0f\xf9\x1c\xd6\x17\xf9Y\x14\xf2\xfb\x17X\x04\xc8\xe2\xb6J\xb8>\\\x85\x0e2\xa4f\xa1\xc6GE\xb5\xd9mvT\xb9\xd6[]\x96ړ\xa04\x9b\r\x1a\
Approach 3 - Almost working!
const (
maxBodySize=12288// Limit the maximum body size we are willing to read completelyrwBufferSize=maxBodySize+4096RespBodyPreviewSize=1024// I want a sample preview of 1024 bytes from the response body
)
StreamResponseBody: true// Enabled StreamResponseBody
package main
import (
"crypto/tls""fmt""log""github.com/VictoriaMetrics/VictoriaMetrics/lib/bytesutil""github.com/valyala/fasthttp"
)
var (
strSpace= []byte(" ")
strCRLF= []byte("\r\n")
strColonSpace= []byte(": ")
)
varheaderBufPool bytesutil.ByteBufferPooltypeRawHTTPResponseDetailsstruct {
StatusCodeintResponsePreview []byte// I want a sample previw bytes from the response bodyResponseHeaders []byteContentType []byteContentLengthint64ServerInfo []byteResponseBytesint// Total bytes of the response received (Headers + Body up to maxBodySize)
}
const (
maxBodySize=12288// Limit the maximum body size we are willing to read completelyrwBufferSize=maxBodySize+4096RespBodyPreviewSize=1024// I want a sample previw of 1024 bytes from the response body
)
funcmain() {
url:="https://www.sample-videos.com/video321/mp4/360/big_buck_bunny_360p_30mb.mp4"// 30 mb videotlsConfig:=&tls.Config{
InsecureSkipVerify: true,
}
client:=&fasthttp.Client{
MaxResponseBodySize: maxBodySize, // Set the maximum response body sizeReadBufferSize: rwBufferSize, // Set read buffer sizeWriteBufferSize: rwBufferSize, // Set write buffer sizeStreamResponseBody: true, // By default, fasthttp uses StreamResponseBody = falseTLSConfig: tlsConfig,
}
req:=fasthttp.AcquireRequest()
deferfasthttp.ReleaseRequest(req)
req.SetRequestURI(url)
req.Header.SetMethod(fasthttp.MethodGet)
resp:=fasthttp.AcquireResponse()
deferfasthttp.ReleaseResponse(resp)
err:=client.Do(req, resp)
// Check for errors but ALLOW ErrBodyTooLarge!iferr!=nil&&err!=fasthttp.ErrBodyTooLarge {
log.Fatalf("Error fetching URL %s: %v", url, err)
}
respDetails:=RawHTTPResponseDetails{}
respDetails.StatusCode=resp.StatusCode()
respDetails.ResponseHeaders=GetResponseHeaders(&resp.Header, respDetails.StatusCode, respDetails.ResponseHeaders)
body:=resp.Body()
bodyLen:=len(body)
// Determine the preview sizepreviewSize:=RespBodyPreviewSizeifbodyLen<previewSize {
previewSize=bodyLen
}
// Create and populate the ResponsePreviewpreview:=make([]byte, previewSize)
copy(preview, body[:previewSize])
respDetails.ResponsePreview=preview// Populate other detailsrespDetails.ContentType=resp.Header.ContentType()
respDetails.ContentLength=int64(resp.Header.ContentLength())
respDetails.ServerInfo=resp.Header.Server()
// Calculate total received bytes (header size + received body size)respDetails.ResponseBytes=resp.Header.Len() +bodyLenfmt.Println("--- Collected Response Details ---")
fmt.Printf("Status Code: %d\n", respDetails.StatusCode)
fmt.Printf("\nResponse Headers (%d bytes):\n", len(respDetails.ResponseHeaders))
fmt.Println(string(respDetails.ResponseHeaders)) // Print headers including status linefmt.Printf("Content-Type: %s\n", string(respDetails.ContentType))
fmt.Printf("Content-Length Header: %d\n", respDetails.ContentLength)
fmt.Printf("Server: %s\n", string(respDetails.ServerInfo))
fmt.Printf("Total Bytes Received (Headers + Body Preview/Truncated Body): %d\n", respDetails.ResponseBytes)
fmt.Printf("\nResponse Body Preview (%d bytes):\n", len(respDetails.ResponsePreview))
fmt.Println(string(respDetails.ResponsePreview))
iferr==fasthttp.ErrBodyTooLarge {
fmt.Println("\nNote: Response body exceeded MaxResponseBodySize.")
fmt.Printf("Full response body is likely larger than the received %d bytes (maxBodySize = %d).\n", bodyLen, maxBodySize)
} elseifint64(bodyLen) >RespBodyPreviewSize&&respDetails.ContentLength>int64(RespBodyPreviewSize) {
fmt.Printf("\nNote: Full response body (%d bytes reported by Content-Length) is larger than the preview size (%d bytes).\n", respDetails.ContentLength, RespBodyPreviewSize)
} elseifint64(bodyLen) >RespBodyPreviewSize {
fmt.Printf("\nNote: Response body received (%d bytes) is larger than the preview size (%d bytes).\n", bodyLen, RespBodyPreviewSize)
}
}
// GetResponseHeaders helperfuncGetResponseHeaders(h*fasthttp.ResponseHeader, statusCodeint, dest []byte) []byte {
headerBuf:=headerBufPool.Get()
deferheaderBufPool.Put(headerBuf)
headerBuf.Write(h.Protocol())
headerBuf.Write(strSpace)
headerBuf.B=fasthttp.AppendUint(headerBuf.B, statusCode)
headerBuf.Write(strSpace)
headerBuf.Write(h.StatusMessage())
headerBuf.Write(strCRLF)
// Write all headersh.VisitAll(func(key, value []byte) {
headerBuf.Write(key)
headerBuf.Write(strColonSpace)
headerBuf.Write(value)
headerBuf.Write(strCRLF)
})
headerBuf.Write(strCRLF)
returnappend(dest[:0], headerBuf.B...)
}
Output:
go run .\main.go
--- Collected Response Details ---
Status Code: 200
Response Headers (268 bytes):
HTTP/1.1 200 OK
Content-Length: 31466742
Content-Type: video/mp4
Server: Apache/2.4.6 (CentOS) OpenSSL/1.0.2k-fips PHP/7.3.23
Date: Wed, 09 Apr 2025 09:06:19 GMT
Last-Modified: Tue, 20 Oct 2020 10:45:05 GMT
Etag: "1e024f6-5b217ec8840b6"
Accept-Ranges: bytes
Content-Type: video/mp4
Content-Length Header: 31466742
Server: Apache/2.4.6 (CentOS) OpenSSL/1.0.2k-fips PHP/7.3.23
Total Bytes Received (Headers + Body Preview/Truncated Body): 31466749
Response Body Preview (1024 bytes):
ftypisomisomiso2mp4freeދ_mdat�&
�!���0
��@�dJa=w�����k��
�}y�jT�H��hr��}���G�+�EY��e�����&��.�T�Ӻ�O�^��Kʩ�Yz�:��Og���;��K�o��6�`�v��w�̈́v���h&�F�����-�t"����aUY�b��[����
�o��$����ظ1ޑ���(/�w����v
���`�XN ��p�D��H�>�������g����~zխy�JUX��v��u��ґ��=�)��n]�4�`_��/L)]���j��{��C���r'*�"��w��u�f�l�m-��U�?�9�+�-�@=�8�4��������@�PP&
B�aXPN�Ap�DF �B��D/�x�_3�U�}7Ϸ���Ǚ��RU�\`սW�����?ֈ��0u2#�ۯD��u�%���J4]ɛ��g�X���k+r�t�P���HSW���>�0� ZS��b���u8�=$
�`)�0S��^����^ ������݂�`�YP␦
.�u��:J�]�S,�O��␦Ah]o*�M�ӓJTPM�w._~␦q���#���O�h$���FXo�crǖ�Eu���#H˞����[}��w@� Lh�@�`,$)�&��g����n�s[�VY.M(���ya�������y������ɑ}?��v��o�r�9��k:�����C��������s��Z��[�[�KNq ������ �tM�C�Ike�7�����ݡ]+��?���wn��nS�/4a_U`�"��EGA����Mh��J����zĤ ��b騭��+2������mf�0␦��"��w����q
Note: Full response body (31466742 bytes reported by Content-Length) is larger than the preview size (1024 bytes).
Using StreamResponsebody = true and reading the response using resp.Body() looked like the working approach; it worked for most of the requests. However, it also failed under high concurrency, and I'm still unsure of the exact cause.
I suspect that CloseBodyStream closes the stream too early, failing to retrieve the response:
I managed to get a final working PoC, this version worked in all test cases. I've successfully sent and retrieved more than 1 million requests/responses (concurrent) on many different servers.
const (
maxBodySize=12288// Limit the maximum body size (headers + body)rwBufferSize=maxBodySize+4096RespBodyPreviewSize=1024// I want a sample preview of 1024 bytes from the response body
)
StreamResponseBody: true
I found that resp.BodyWriteTo() calls Write() internally, so I ended up using a LimitedWriter implementation:
package main
import (
"crypto/tls""fmt""io""log""github.com/VictoriaMetrics/VictoriaMetrics/lib/bytesutil""github.com/valyala/fasthttp"
)
var (
strSpace= []byte(" ")
strCRLF= []byte("\r\n")
strColonSpace= []byte(": ")
)
varheaderBufPool bytesutil.ByteBufferPoolvarrespPreviewBufPool bytesutil.ByteBufferPooltypeRawHTTPResponseDetailsstruct {
StatusCodeintResponsePreview []byte// I want a sample preview bytes from the response bodyResponseHeaders []byteContentType []byteContentLengthint64ServerInfo []byteResponseBytesint// Total bytes of the response received (Headers + Body up to maxBodySize)
}
typeLimitedWriterstruct {
W io.Writer// Underlying writerNint64// Max bytes remaining
}
func (l*LimitedWriter) Write(p []byte) (nint, errerror) {
ifl.N<=0 {
return0, io.EOF
}
ifint64(len(p)) >l.N {
p=p[0:l.N]
}
n, err=l.W.Write(p)
l.N-=int64(n)
return
}
const (
maxBodySize=12288// Limit the maximum body size (headers + body)rwBufferSize=maxBodySize+4096RespBodyPreviewSize=1024// I want a sample preview of 1024 bytes from the response body
)
funcmain() {
url:="https://www.sample-videos.com/video321/mp4/360/big_buck_bunny_360p_30mb.mp4"// 30 mb videotlsConfig:=&tls.Config{
InsecureSkipVerify: true,
}
client:=&fasthttp.Client{
MaxResponseBodySize: maxBodySize,
ReadBufferSize: rwBufferSize,
WriteBufferSize: rwBufferSize,
StreamResponseBody: true, // Enabling streaming mode is crucial!TLSConfig: tlsConfig,
}
req:=fasthttp.AcquireRequest()
deferfasthttp.ReleaseRequest(req)
req.SetRequestURI(url)
req.Header.SetMethod(fasthttp.MethodGet)
resp:=fasthttp.AcquireResponse()
deferfasthttp.ReleaseResponse(resp)
err:=client.Do(req, resp)
iferr!=nil&&err!=fasthttp.ErrBodyTooLarge {
log.Fatalf("Error fetching URL %s: %v", url, err)
}
respDetails:=RawHTTPResponseDetails{}
respDetails.StatusCode=resp.StatusCode()
respDetails.ContentType=resp.Header.ContentType()
respDetails.ContentLength=int64(resp.Header.ContentLength())
respDetails.ServerInfo=resp.Header.Server()
respDetails.ResponseHeaders=GetResponseHeaders(&resp.Header, respDetails.StatusCode, respDetails.ResponseHeaders)
previewBuf:=respPreviewBufPool.Get()
deferrespPreviewBufPool.Put(previewBuf)
// Create the LimitedWriter to control how much is read from the streamlimitedWriter:=&LimitedWriter{
W: previewBuf, // Write into the pooled bufferN: int64(RespBodyPreviewSize), // Limit preview size
}
// Read stream into buffer via limitedWriteriferr:=resp.BodyWriteTo(limitedWriter); err!=nil&&err!=io.EOF {
log.Printf("Warning: Error reading response body stream: %v\n", err)
}
iflen(previewBuf.B) >0 {
respDetails.ResponsePreview=append(respDetails.ResponsePreview[:0], previewBuf.B...)
respDetails.ResponseBytes=resp.Header.Len() +len(previewBuf.B)
}
fmt.Println("--- Collected Response Details (Streaming Mode) ---")
fmt.Printf("Status Code: %d\n", respDetails.StatusCode)
fmt.Printf("\nResponse Headers (%d bytes):\n%s", len(respDetails.ResponseHeaders), string(respDetails.ResponseHeaders))
fmt.Printf("Content-Type: %s\n", string(respDetails.ContentType))
fmt.Printf("Content-Length Header: %d\n", respDetails.ContentLength)
fmt.Printf("Server: %s\n", string(respDetails.ServerInfo))
fmt.Printf("Total Bytes Received (Headers + Body Preview): %d\n", respDetails.ResponseBytes)
fmt.Printf("\nResponse Body Preview (%d bytes):\n", len(respDetails.ResponsePreview))
iflen(respDetails.ResponsePreview) >0 {
fmt.Println(string(respDetails.ResponsePreview))
} else {
fmt.Println("<empty preview>")
}
}
// GetResponseHeaders helperfuncGetResponseHeaders(h*fasthttp.ResponseHeader, statusCodeint, dest []byte) []byte {
headerBuf:=headerBufPool.Get()
deferheaderBufPool.Put(headerBuf)
headerBuf.Write(h.Protocol())
headerBuf.Write(strSpace)
headerBuf.B=fasthttp.AppendUint(headerBuf.B, statusCode)
headerBuf.Write(strSpace)
headerBuf.Write(h.StatusMessage())
headerBuf.Write(strCRLF)
h.VisitAll(func(key, value []byte) {
headerBuf.Write(key)
headerBuf.Write(strColonSpace)
headerBuf.Write(value)
headerBuf.Write(strCRLF)
})
headerBuf.Write(strCRLF)
returnappend(dest[:0], headerBuf.B...)
}
Output:
go run .\main.go
2025/04/09 13:33:21 Warning: Error reading response body stream: short write
--- Collected Response Details (Streaming Mode) ---
Status Code: 200
Response Headers (268 bytes):
HTTP/1.1 200 OK
Content-Length: 31466742
Content-Type: video/mp4
Server: Apache/2.4.6 (CentOS) OpenSSL/1.0.2k-fips PHP/7.3.23
Date: Wed, 09 Apr 2025 10:22:50 GMT
Last-Modified: Tue, 20 Oct 2020 10:45:05 GMT
Etag: "1e024f6-5b217ec8840b6"
Accept-Ranges: bytes
Content-Type: video/mp4
Content-Length Header: 31466742
Server: Apache/2.4.6 (CentOS) OpenSSL/1.0.2k-fips PHP/7.3.23
Total Bytes Received (Headers + Body Preview): 1031
Response Body Preview (1024 bytes):
ftypisomisomiso2mp4freeދ_mdat�&
�!���0
��@�dJa=w�����k��
�}y�jT�H��hr��}���G�+�EY��e�����&��.�T�Ӻ�O�^��Kʩ�Yz�:��Og���;��K�o��6�`�v��w�̈́v���h&�F�����-�t"����aUY�b��[����
�o��$����ظ1ޑ���(/�w����v
���`�XN ��p�D��H�>�������g����~zխy�JUX��v��u��ґ��=�)��n]�4�`_��/L)]���j��{��C���r'*�"��w��u�f�l�m-��U�?�9�+�-�@=�8�4��������@�PP&
B�aXPN�Ap�DF �B��D/�x�_3�U�}7Ϸ���Ǚ��RU�\`սW�����?ֈ��0u2#�ۯD��u�%���J4]ɛ��g�X���k+r�t�P���HSW���>�0� ZS��b���u8�=$
�`)�0S��^����^ ������݂�`�YP␦
.�u��:J�]�S,�O��␦Ah]o*�M�ӓJTPM�w._~␦q���#���O�h$���FXo�crǖ�Eu���#H˞����[}��w@� Lh�@�`,$)�&��g����n�s[�VY.M(���ya�������y������ɑ}?��v��o�r�9��k:�����C��������s��Z��[�[�KNq ������ �tM�C�Ike�7�����ݡ]+��?���wn��nS�/4a_U`�"��EGA����Mh��J����zĤ ��b騭��+2������mf�0␦��"��w����q
Approach 4 seems to be working fine for me. I noticed that sometimes it returns io.ErrShortWrite, but I think this is expected based on my implementation.
My question now is, what would be the correct way to read up to MaxResponseBodySize and also capture a preview up to N bytes from the response? Or if I should stick to my approach.
The text was updated successfully, but these errors were encountered:
I have never done this myself but your approach 4 seems to be a good approach. I'll keep this issue open for others who might want to do the same. Thanks for exploring this!
Hello,
For the past few months, I've been working on a fasthttp based HTTP client. I struggled to find a way to read only a maximum number of bytes from the HTTP response. All testing was performed against live servers.
Obviously,
MaxResponseBodysize
is the important factor here, so I began testing various implementations.fasthttp/client.go
Line 278 in 4c71125
Other notable configurations:
fasthttp/client.go
Line 255 in 4c71125
fasthttp/client.go
Line 260 in 4c71125
Below, I am providing sample code snippets that I tried until I got a working PoC.
In all examples, I am setting
MaxResponseBodySize
to12288
bytes as I figured out that this includes response headers too, and for example, sites like github.com use a long list of headers.Approach 1
Response read via
resp.Body()
fasthttp/http.go
Line 411 in 4c71125
Sample code snippet:
Output:
This approach failed, and it also failed on many other servers that didn't include a
Content-Lenght
header in the response.Approach 2
Response is read via
resp.BodyStream()
https://github.com/valyala/fasthttp/blob/4c71125994a1a67c8c6cb979142ae4269c5d89f1/http.go#L333C1-L335C2
Based on these testcases, I tried to use
resp.ReadLimitBody()
and ensuredresp.CloseBodyStream()
fasthttp/http_test.go
Line 2962 in 4c71125
fasthttp/http.go
Line 337 in 4c71125
Looking back through my git history, other sample helper I tried for this approach:
While this approach worked better and allowed me to successfully obtain a response preview, it still failed in most test cases, though not all. It was noted that it failed in concurrent requests. Sample errors:
Approach 3 - Almost working!
Response read via
resp.Body()
fasthttp/http.go
Line 411 in 4c71125
Code snippet:
Output:
Using
StreamResponsebody = true
and reading the response usingresp.Body()
looked like the working approach; it worked for most of the requests. However, it also failed under high concurrency, and I'm still unsure of the exact cause.I suspect that CloseBodyStream closes the stream too early, failing to retrieve the response:
fasthttp/http.go
Line 415 in 4c71125
fasthttp/http.go
Line 416 in 4c71125
Approach 4 - Finally working
I managed to get a final working PoC, this version worked in all test cases. I've successfully sent and retrieved more than 1 million requests/responses (concurrent) on many different servers.
I found that
resp.BodyWriteTo()
callsWrite()
internally, so I ended up using aLimitedWriter
implementation:fasthttp/http.go
Line 637 in 4c71125
Output:
Approach 4 seems to be working fine for me. I noticed that sometimes it returns
io.ErrShortWrite
, but I think this is expected based on my implementation.My question now is, what would be the correct way to read up to MaxResponseBodySize and also capture a preview up to N bytes from the response? Or if I should stick to my approach.
The text was updated successfully, but these errors were encountered: