ooni-probe-cli/internal/experiment/webconnectivity/iox.go
Simone Basso 6815dd8b2f
webconnectivity@v0.5: handle successful https chains (#960)
This diff includes a rule to recover from the "measurement failed" state that kicks in when we have a chain of successful redirects from the client side leading to a webpage _and_ any URL in the chain uses HTTPS. See https://github.com/ooni/probe/issues/2307.

While there, fix `i/e/w/iox.go` to avoid triggering the `./script/nocopyreadall.bash` script.
2022-09-14 08:40:13 +02:00

80 lines
2.0 KiB
Go

package webconnectivity
//
// Extensions to incrementally stream-reading a response body.
//
import (
"context"
"errors"
"io"
"github.com/ooni/probe-cli/v3/internal/netxlite"
)
// StreamAllContext streams from the given reader [r] until
// interrupted by [ctx] or when [r] hits the EOF.
//
// This function runs a background goroutine that should exit as soon
// as [ctx] is done or when [reader] is closed, if applicable.
//
// This function transforms an errors.Is(err, io.EOF) to a nil error
// such as the standard library's ReadAll does.
//
// This function might return a non-zero-length buffer along with
// an non-nil error in the case in which we could only read a portion
// of the body and then we were interrupted by the error.
func StreamAllContext(ctx context.Context, reader io.Reader) ([]byte, error) {
// TODO(bassosimone): consider merging into the ./internal/netxlite/iox.go file
// once this code has been used in testing for quite some time
datach, errch := make(chan []byte), make(chan error)
ctx, cancel := context.WithCancel(ctx)
defer cancel()
go func() {
buffer := make([]byte, 1<<13)
for {
count, err := reader.Read(buffer)
if count > 0 {
data := buffer[:count]
select {
case datach <- data:
// fallthrough to check error
case <-ctx.Done():
return
}
}
if err != nil {
select {
case errch <- err:
return
case <-ctx.Done():
return
}
}
}
}()
resultbuf := make([]byte, 0, 1<<17)
for {
select {
case data := <-datach:
// TODO(bassosimone): is there a more efficient way?
resultbuf = append(resultbuf, data...)
case err := <-errch:
if errors.Is(err, io.EOF) {
// see https://github.com/ooni/probe/issues/1965
return resultbuf, nil
}
return resultbuf, netxlite.NewTopLevelGenericErrWrapper(err)
case <-ctx.Done():
err := ctx.Err()
if errors.Is(err, context.DeadlineExceeded) {
return resultbuf, nil
}
return resultbuf, netxlite.NewTopLevelGenericErrWrapper(err)
}
}
}