6a935d5407
1. introduce implementations of HTTPTransport and HTTPClient that apply an error wrapping policy using the constructor for a generic top-level error wrapper 2. make sure we use the implementations in point 1 when we are constructing HTTPTransport and HTTPClient 3. make sure we apply error wrapping using the constructor for a generic top-level error wrapper when reading bodies 4. acknowledge that error wrapping would be broken if we do not return the same classification _and_ operation when we wrap an already wrapped error, so fix the to code to do that 5. acknowledge that the classifiers already deal with preserving the error string and explain why this is a quirk and why we cannot remove it right now and what needs to happen to safely remove this quirk from the codebase Closes https://github.com/ooni/probe/issues/1860
59 lines
1.6 KiB
Go
59 lines
1.6 KiB
Go
package netxlite
|
|
|
|
import (
|
|
"context"
|
|
"io"
|
|
)
|
|
|
|
// ReadAllContext is like io.ReadAll but reads r in a
|
|
// background goroutine. This function will return
|
|
// earlier if the context is cancelled. In which case
|
|
// we will continue reading from the reader in the background
|
|
// goroutine, and we will discard the result. To stop
|
|
// the long-running goroutine, close the connection
|
|
// bound to the reader. Until such a connection is closed,
|
|
// you're leaking the backround goroutine and doing I/O.
|
|
func ReadAllContext(ctx context.Context, r io.Reader) ([]byte, error) {
|
|
datach, errch := make(chan []byte, 1), make(chan error, 1) // buffers
|
|
go func() {
|
|
data, err := io.ReadAll(r)
|
|
if err != nil {
|
|
errch <- err
|
|
return
|
|
}
|
|
datach <- data
|
|
}()
|
|
select {
|
|
case data := <-datach:
|
|
return data, nil
|
|
case <-ctx.Done():
|
|
return nil, NewTopLevelGenericErrWrapper(ctx.Err())
|
|
case err := <-errch:
|
|
return nil, NewTopLevelGenericErrWrapper(err)
|
|
}
|
|
}
|
|
|
|
// CopyContext is like io.Copy but may terminate earlier
|
|
// when the context expires. This function has the same
|
|
// caveats of ReadAllContext regarding the temporary leaking
|
|
// of the background I/O goroutine.
|
|
func CopyContext(ctx context.Context, dst io.Writer, src io.Reader) (int64, error) {
|
|
countch, errch := make(chan int64, 1), make(chan error, 1) // buffers
|
|
go func() {
|
|
count, err := io.Copy(dst, src)
|
|
if err != nil {
|
|
errch <- err
|
|
return
|
|
}
|
|
countch <- count
|
|
}()
|
|
select {
|
|
case count := <-countch:
|
|
return count, nil
|
|
case <-ctx.Done():
|
|
return 0, NewTopLevelGenericErrWrapper(ctx.Err())
|
|
case err := <-errch:
|
|
return 0, NewTopLevelGenericErrWrapper(err)
|
|
}
|
|
}
|