fix(http body saver): properly deal with EOF terminated bodies (#226)

See https://github.com/ooni/probe-engine/issues/1191
This commit is contained in:
Simone Basso 2021-02-11 14:57:14 +01:00 committed by GitHub
parent c41114261c
commit 3155549513
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 69 additions and 0 deletions

View File

@ -2,6 +2,7 @@ package httptransport
import ( import (
"bytes" "bytes"
"errors"
"io" "io"
"io/ioutil" "io/ioutil"
"net/http" "net/http"
@ -125,6 +126,7 @@ func (txp SaverBodyHTTPTransport) RoundTrip(req *http.Request) (*http.Response,
return nil, err return nil, err
} }
data, err := saverSnapRead(resp.Body, snapsize) data, err := saverSnapRead(resp.Body, snapsize)
err = ignoreExpectedEOF(err, resp)
if err != nil { if err != nil {
resp.Body.Close() resp.Body.Close()
return nil, err return nil, err
@ -139,6 +141,22 @@ func (txp SaverBodyHTTPTransport) RoundTrip(req *http.Request) (*http.Response,
return resp, nil return resp, nil
} }
// ignoreExpectedEOF converts an error signalling the end of the body
// into a success. We know that we are in such condition when the
// resp.Close hint flag is set to true. (Thanks, stdlib!)
//
// See https://github.com/ooni/probe-engine/issues/1191 for an analysis
// of how this error was impacting measurements and data quality.
func ignoreExpectedEOF(err error, resp *http.Response) error {
if err == nil {
return nil
}
if errors.Is(err, io.EOF) && resp.Close {
return nil
}
return err
}
func saverSnapRead(r io.ReadCloser, snapsize int) ([]byte, error) { func saverSnapRead(r io.ReadCloser, snapsize int) ([]byte, error) {
return ioutil.ReadAll(io.LimitReader(r, int64(snapsize))) return ioutil.ReadAll(io.LimitReader(r, int64(snapsize)))
} }

View File

@ -0,0 +1,51 @@
package httptransport
import (
"errors"
"fmt"
"io"
"net/http"
"testing"
)
func composeWithEOFError(msg string) error {
return fmt.Errorf("%w: %s", io.EOF, msg)
}
func TestIgnoreExpectedEOFWithNoError(t *testing.T) {
if err := ignoreExpectedEOF(nil, nil); err != nil {
t.Fatal(err)
}
}
func TestIgnoreExpectedEOFWithEOFErrorButNoCloseHint(t *testing.T) {
resp := &http.Response{}
in := composeWithEOFError("antani")
if err := ignoreExpectedEOF(in, resp); !errors.Is(err, io.EOF) {
t.Fatalf("not the error we expected: %+v", err)
}
}
func TestIgnoreExpectedEOFWithEOFErrorAndCloseHint(t *testing.T) {
resp := &http.Response{Close: true}
in := composeWithEOFError("antani")
if err := ignoreExpectedEOF(in, resp); err != nil {
t.Fatal(err)
}
}
func TestIgnoreExpectedEOFAnyOtherErrorAndCloseHint(t *testing.T) {
resp := &http.Response{Close: true}
in := errors.New("antani")
if err := ignoreExpectedEOF(in, resp); !errors.Is(err, in) {
t.Fatalf("not the error we expected: %+v", err)
}
}
func TestIgnoreExpectedEOFAnyOtherErrorAndNoCloseHint(t *testing.T) {
resp := &http.Response{Close: false /*explicit*/}
in := errors.New("antani")
if err := ignoreExpectedEOF(in, resp); !errors.Is(err, in) {
t.Fatalf("not the error we expected: %+v", err)
}
}