main
Raw Download raw file
  1package snapshot
  2
  3import (
  4	"context"
  5	"fmt"
  6	"io"
  7	"net/http"
  8	"net/url"
  9	"time"
 10)
 11
 12func Capture(ctx context.Context, target *url.URL, opts ...Option) (*Result, error) {
 13
 14	options := defaults()
 15	for _, o := range opts {
 16		o.apply(options)
 17	}
 18
 19	req, err := http.NewRequestWithContext(ctx, "GET", target.String(), nil)
 20	if err != nil {
 21		return nil, err
 22	}
 23	req.Header.Set("User-Agent", options.UserAgent)
 24
 25	client := &http.Client{
 26		Timeout: options.Timeout,
 27	}
 28
 29	started := time.Now().UTC()
 30	result := &Result{
 31		Request: Request{
 32			Method:  req.Method,
 33			URL:     target.String(),
 34			Started: started,
 35
 36			Headers:   req.Header.Clone(),
 37			UserAgent: options.UserAgent,
 38			TimeoutMS: options.Timeout.Milliseconds(),
 39		},
 40	}
 41
 42	resp, err := client.Do(req)
 43	if err != nil {
 44		result.OK = false
 45		result.Failure = &Failure{Stage: _do_request, Error: err.Error()}
 46		return result, nil
 47	}
 48	defer resp.Body.Close()
 49
 50	result.StatusCode = resp.StatusCode
 51	result.FinalURL = resp.Request.URL.String()
 52
 53	result.OK = resp.StatusCode >= 200 && resp.StatusCode < 300
 54	if !result.OK {
 55		result.Failure = &Failure{
 56			Stage: _http_status,
 57			Error: fmt.Sprintf("http status %d", resp.StatusCode),
 58			Transient: resp.StatusCode == http.StatusTooManyRequests ||
 59				resp.StatusCode == http.StatusRequestTimeout ||
 60				resp.StatusCode > 499,
 61		}
 62	}
 63
 64	sink, err := options.BodySink()
 65	if err != nil {
 66		result.OK = false
 67		result.Failure = &Failure{
 68			Stage: _body_open,
 69			Error: err.Error(),
 70		}
 71		return result, nil
 72	}
 73
 74	_, err = io.Copy(sink, resp.Body)
 75	if err != nil {
 76		sinkAborter, ok := sink.WriteCloser.(aborter)
 77		if ok {
 78			sinkAborter.Abort()
 79		} else {
 80			sink.Close()
 81		}
 82		result.OK = false
 83		result.Failure = &Failure{
 84			Stage: _body_write,
 85			Error: err.Error(),
 86		}
 87		return result, nil
 88	}
 89
 90	err = sink.Close()
 91	if err != nil {
 92		result.OK = false
 93		result.Failure = &Failure{
 94			Stage: _body_commit,
 95			Error: err.Error(),
 96		}
 97		return result, nil
 98	}
 99	if sink.Path != "" {
100		result.BodyPath = sink.Path
101	}
102
103	return result, nil
104}