refactor: move tracex outside of engine/netx (#782)
* refactor: move tracex outside of engine/netx Consistently with https://github.com/ooni/probe/issues/2121 and https://github.com/ooni/probe/issues/2115, we can now move tracex outside of engine/netx. The main reason why this makes sense now is that the package is now changed significantly from the one that we imported from ooni/probe-engine. We have improved its implementation, which had not been touched significantly for quite some time, and converted it to unit testing. I will document tomorrow some extra work I'd like to do with this package but likely could not do $soon. * go fmt * regen tutorials
This commit is contained in:
parent
d397036073
commit
58adb68b2c
50 changed files with 34 additions and 34 deletions
125
internal/tracex/http.go
Normal file
125
internal/tracex/http.go
Normal file
|
|
@ -0,0 +1,125 @@
|
|||
package tracex
|
||||
|
||||
//
|
||||
// HTTP
|
||||
//
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"io"
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
"github.com/ooni/probe-cli/v3/internal/model"
|
||||
"github.com/ooni/probe-cli/v3/internal/netxlite"
|
||||
)
|
||||
|
||||
// httpCloneRequestHeaders 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 httpCloneRequestHeaders(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
|
||||
}
|
||||
|
||||
// HTTPTransportSaver is a RoundTripper that saves
|
||||
// events related to the HTTP transaction
|
||||
type HTTPTransportSaver struct {
|
||||
// HTTPTransport is the MANDATORY underlying HTTP transport.
|
||||
HTTPTransport model.HTTPTransport
|
||||
|
||||
// Saver is the MANDATORY saver to use.
|
||||
Saver *Saver
|
||||
|
||||
// SnapshotSize is the OPTIONAL maximum body snapshot size (if not set, we'll
|
||||
// use 1<<17, which we've been using since the ooni/netx days)
|
||||
SnapshotSize int64
|
||||
}
|
||||
|
||||
// HTTPRoundTrip performs the round trip with the given transport and
|
||||
// the given arguments and saves the results into the saver.
|
||||
//
|
||||
// The maxBodySnapshotSize argument controls the maximum size of the
|
||||
// body snapshot that we collect along with the HTTP round trip.
|
||||
func (txp *HTTPTransportSaver) RoundTrip(req *http.Request) (*http.Response, error) {
|
||||
|
||||
// TODO(bassosimone): we're currently using the started time for
|
||||
// the transaction done event, which contrasts with what we do for
|
||||
// every other event. What does the spec say?
|
||||
|
||||
started := time.Now()
|
||||
txp.Saver.Write(&EventHTTPTransactionStart{&EventValue{
|
||||
HTTPRequestHeaders: httpCloneRequestHeaders(req),
|
||||
HTTPMethod: req.Method,
|
||||
HTTPURL: req.URL.String(),
|
||||
Transport: txp.HTTPTransport.Network(),
|
||||
Time: started,
|
||||
}})
|
||||
ev := &EventValue{
|
||||
HTTPRequestHeaders: httpCloneRequestHeaders(req),
|
||||
HTTPMethod: req.Method,
|
||||
HTTPURL: req.URL.String(),
|
||||
Transport: txp.HTTPTransport.Network(),
|
||||
Time: started,
|
||||
}
|
||||
defer txp.Saver.Write(&EventHTTPTransactionDone{ev})
|
||||
|
||||
resp, err := txp.HTTPTransport.RoundTrip(req)
|
||||
|
||||
if err != nil {
|
||||
ev.Duration = time.Since(started)
|
||||
ev.Err = err
|
||||
return nil, err
|
||||
}
|
||||
|
||||
ev.HTTPStatusCode = resp.StatusCode
|
||||
ev.HTTPResponseHeaders = resp.Header.Clone()
|
||||
|
||||
maxBodySnapshotSize := txp.snapshotSize()
|
||||
r := io.LimitReader(resp.Body, maxBodySnapshotSize)
|
||||
body, err := netxlite.ReadAllContext(req.Context(), r)
|
||||
|
||||
if err != nil {
|
||||
ev.Duration = time.Since(started)
|
||||
ev.Err = err
|
||||
return nil, err
|
||||
}
|
||||
|
||||
resp.Body = &httpReadableAgainBody{ // allow for reading again the whole body
|
||||
Reader: io.MultiReader(bytes.NewReader(body), resp.Body),
|
||||
Closer: resp.Body,
|
||||
}
|
||||
|
||||
ev.Duration = time.Since(started)
|
||||
ev.HTTPResponseBody = body
|
||||
ev.HTTPResponseBodyIsTruncated = int64(len(body)) >= maxBodySnapshotSize
|
||||
|
||||
return resp, nil
|
||||
}
|
||||
|
||||
func (txp *HTTPTransportSaver) CloseIdleConnections() {
|
||||
txp.HTTPTransport.CloseIdleConnections()
|
||||
}
|
||||
|
||||
func (txp *HTTPTransportSaver) Network() string {
|
||||
return txp.HTTPTransport.Network()
|
||||
}
|
||||
|
||||
func (txp *HTTPTransportSaver) snapshotSize() int64 {
|
||||
if txp.SnapshotSize > 0 {
|
||||
return txp.SnapshotSize
|
||||
}
|
||||
return 1 << 17
|
||||
}
|
||||
|
||||
type httpReadableAgainBody struct {
|
||||
io.Reader
|
||||
io.Closer
|
||||
}
|
||||
|
||||
var _ model.HTTPTransport = &HTTPTransportSaver{}
|
||||
Loading…
Reference in a new issue