c740be987b
Rather than matching a string, match a type. This is more robust considering future refactorings. We're confident the names did not change in _this_ refactoring because we're still testing the same strings in the tests. Part of https://github.com/ooni/probe/issues/2121
140 lines
3.7 KiB
Go
140 lines
3.7 KiB
Go
package tracex
|
|
|
|
//
|
|
// HTTP
|
|
//
|
|
|
|
import (
|
|
"bytes"
|
|
"context"
|
|
"io"
|
|
"net/http"
|
|
"time"
|
|
|
|
"github.com/ooni/probe-cli/v3/internal/model"
|
|
"github.com/ooni/probe-cli/v3/internal/netxlite"
|
|
)
|
|
|
|
// SaverMetadataHTTPTransport is a RoundTripper that saves
|
|
// events related to HTTP request and response metadata
|
|
type SaverMetadataHTTPTransport struct {
|
|
model.HTTPTransport
|
|
Saver *Saver
|
|
}
|
|
|
|
// RoundTrip implements RoundTripper.RoundTrip
|
|
func (txp SaverMetadataHTTPTransport) RoundTrip(req *http.Request) (*http.Response, error) {
|
|
txp.Saver.Write(&EventHTTPRequestMetadata{&EventValue{
|
|
HTTPHeaders: httpCloneHeaders(req),
|
|
HTTPMethod: req.Method,
|
|
HTTPURL: req.URL.String(),
|
|
Transport: txp.HTTPTransport.Network(),
|
|
Time: time.Now(),
|
|
}})
|
|
resp, err := txp.HTTPTransport.RoundTrip(req)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
txp.Saver.Write(&EventHTTPResponseMetadata{&EventValue{
|
|
HTTPHeaders: resp.Header,
|
|
HTTPStatusCode: resp.StatusCode,
|
|
Time: time.Now(),
|
|
}})
|
|
return resp, err
|
|
}
|
|
|
|
// httpCCloneHeaders returns a clone of the headers where we have
|
|
// also set the host header, which normally is not set by
|
|
// golang until it serializes the request itself.
|
|
func httpCloneHeaders(req *http.Request) http.Header {
|
|
header := req.Header.Clone()
|
|
if req.Host != "" {
|
|
header.Set("Host", req.Host)
|
|
} else {
|
|
header.Set("Host", req.URL.Host)
|
|
}
|
|
return header
|
|
}
|
|
|
|
// SaverTransactionHTTPTransport is a RoundTripper that saves
|
|
// events related to the HTTP transaction
|
|
type SaverTransactionHTTPTransport struct {
|
|
model.HTTPTransport
|
|
Saver *Saver
|
|
}
|
|
|
|
// RoundTrip implements RoundTripper.RoundTrip
|
|
func (txp SaverTransactionHTTPTransport) RoundTrip(req *http.Request) (*http.Response, error) {
|
|
txp.Saver.Write(&EventHTTPTransactionStart{&EventValue{
|
|
Time: time.Now(),
|
|
}})
|
|
resp, err := txp.HTTPTransport.RoundTrip(req)
|
|
txp.Saver.Write(&EventHTTPTransactionDone{&EventValue{
|
|
Err: err,
|
|
Time: time.Now(),
|
|
}})
|
|
return resp, err
|
|
}
|
|
|
|
// SaverBodyHTTPTransport is a RoundTripper that saves
|
|
// body events occurring during the round trip
|
|
type SaverBodyHTTPTransport struct {
|
|
model.HTTPTransport
|
|
Saver *Saver
|
|
SnapshotSize int
|
|
}
|
|
|
|
// RoundTrip implements RoundTripper.RoundTrip
|
|
func (txp SaverBodyHTTPTransport) RoundTrip(req *http.Request) (*http.Response, error) {
|
|
const defaultSnapSize = 1 << 17
|
|
snapsize := defaultSnapSize
|
|
if txp.SnapshotSize != 0 {
|
|
snapsize = txp.SnapshotSize
|
|
}
|
|
if req.Body != nil {
|
|
data, err := httpSaverSnapRead(req.Context(), req.Body, snapsize)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
req.Body = httpSaverCompose(data, req.Body)
|
|
txp.Saver.Write(&EventHTTPRequestBodySnapshot{&EventValue{
|
|
DataIsTruncated: len(data) >= snapsize,
|
|
Data: data,
|
|
Time: time.Now(),
|
|
}})
|
|
}
|
|
resp, err := txp.HTTPTransport.RoundTrip(req)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
data, err := httpSaverSnapRead(req.Context(), resp.Body, snapsize)
|
|
if err != nil {
|
|
resp.Body.Close()
|
|
return nil, err
|
|
}
|
|
resp.Body = httpSaverCompose(data, resp.Body)
|
|
txp.Saver.Write(&EventHTTPResponseBodySnapshot{&EventValue{
|
|
DataIsTruncated: len(data) >= snapsize,
|
|
Data: data,
|
|
Time: time.Now(),
|
|
}})
|
|
return resp, nil
|
|
}
|
|
|
|
func httpSaverSnapRead(ctx context.Context, r io.ReadCloser, snapsize int) ([]byte, error) {
|
|
return netxlite.ReadAllContext(ctx, io.LimitReader(r, int64(snapsize)))
|
|
}
|
|
|
|
func httpSaverCompose(data []byte, r io.ReadCloser) io.ReadCloser {
|
|
return httpSaverReadCloser{Closer: r, Reader: io.MultiReader(bytes.NewReader(data), r)}
|
|
}
|
|
|
|
type httpSaverReadCloser struct {
|
|
io.Closer
|
|
io.Reader
|
|
}
|
|
|
|
var _ model.HTTPTransport = SaverMetadataHTTPTransport{}
|
|
var _ model.HTTPTransport = SaverBodyHTTPTransport{}
|
|
var _ model.HTTPTransport = SaverTransactionHTTPTransport{}
|