"fmt"
"io"
"log"
+ "log/slog"
"net/http"
"strings"
"github.com/RoaringBitmap/roaring"
+
"github.com/anacrolix/missinggo/v2/panicif"
"github.com/anacrolix/torrent/metainfo"
}
type Client struct {
+ Logger *slog.Logger
HttpClient *http.Client
Url string
// Max concurrent requests to a WebSeed for a given torrent.
Body: body,
}
go func() {
- err := readRequestPartResponses(ctx, w, requestParts)
+ err := ws.readRequestPartResponses(ctx, w, requestParts)
panicif.Err(w.CloseWithError(err))
}()
return req
return me.Msg
}
+// Warn about bad content-lengths.
+func (me *Client) checkContentLength(resp *http.Response, part requestPart, expectedLen int64) {
+ if resp.ContentLength == -1 {
+ return
+ }
+ switch resp.Header.Get("Content-Encoding") {
+ case "identity", "":
+ default:
+ return
+ }
+ if resp.ContentLength != expectedLen {
+ me.Logger.Warn("unexpected identity response Content-Length value",
+ "actual", resp.ContentLength,
+ "expected", expectedLen,
+ "url", part.req.URL)
+ }
+}
+
// Reads the part in full. All expected bytes must be returned or there will an error returned.
-func recvPartResult(ctx context.Context, w io.Writer, part requestPart, resp *http.Response) error {
+func (me *Client) recvPartResult(ctx context.Context, w io.Writer, part requestPart, resp *http.Response) error {
defer resp.Body.Close()
var body io.Reader = resp.Body
if part.responseBodyWrapper != nil {
}
switch resp.StatusCode {
case http.StatusPartialContent:
+ // The response should be just as long as we requested.
+ me.checkContentLength(resp, part, part.e.Length)
copied, err := io.Copy(w, body)
if err != nil {
return err
}
return nil
case http.StatusOK:
+ // The response is from the beginning.
+ me.checkContentLength(resp, part, part.e.End())
// This number is based on
// https://archive.org/download/BloodyPitOfHorror/BloodyPitOfHorror.asr.srt. It seems that
// archive.org might be using a webserver implementation that refuses to do partial
_, err = io.CopyN(w, body, part.e.Length)
return err
case http.StatusServiceUnavailable:
+ // TODO: Include all of Erigon's cases here?
return ErrTooFast
default:
// TODO: Could we have a slog.Valuer or something to allow callers to unpack reasonable values?
var ErrTooFast = errors.New("making requests too fast")
-func readRequestPartResponses(ctx context.Context, w io.Writer, parts []requestPart) (err error) {
+func (me *Client) readRequestPartResponses(ctx context.Context, w io.Writer, parts []requestPart) (err error) {
for _, part := range parts {
var resp *http.Response
resp, err = part.do()
if err == nil {
- err = recvPartResult(ctx, w, part, resp)
+ err = me.recvPartResult(ctx, w, part, resp)
}
if err != nil {
err = fmt.Errorf("reading %q at %q: %w", part.req.URL, part.req.Header.Get("Range"), err)