ooni-probe-cli/internal/engine/legacy/netx/http.go

207 lines
7.0 KiB
Go
Raw Normal View History

package netx
import (
"net/http"
"net/url"
"time"
"github.com/ooni/probe-cli/v3/internal/engine/legacy/netx/handlers"
"github.com/ooni/probe-cli/v3/internal/engine/legacy/netx/modelx"
"github.com/ooni/probe-cli/v3/internal/engine/legacy/netx/oldhttptransport"
"github.com/ooni/probe-cli/v3/internal/errorsx"
"golang.org/x/net/http2"
)
// HTTPTransport performs single HTTP transactions and emits
// measurement events as they happen.
type HTTPTransport struct {
Beginning time.Time
Dialer *Dialer
Handler modelx.Handler
Transport *http.Transport
roundTripper http.RoundTripper
}
func newHTTPTransport(
beginning time.Time,
handler modelx.Handler,
dialer *Dialer,
disableKeepAlives bool,
proxyFunc func(*http.Request) (*url.URL, error),
) *HTTPTransport {
baseTransport := &http.Transport{
// The following values are copied from Go 1.12 docs and match
// what should be used by the default transport
ExpectContinueTimeout: 1 * time.Second,
IdleConnTimeout: 90 * time.Second,
MaxIdleConns: 100,
Proxy: proxyFunc,
TLSHandshakeTimeout: 10 * time.Second,
DisableKeepAlives: disableKeepAlives,
}
ooniTransport := oldhttptransport.New(baseTransport)
// Configure h2 and make sure that the custom TLSConfig we use for dialing
// is actually compatible with upgrading to h2. (This mainly means we
// need to make sure we include "h2" in the NextProtos array.) Because
// http2.ConfigureTransport only returns error when we have already
// configured http2, it is safe to ignore the return value.
http2.ConfigureTransport(baseTransport)
// Since we're not going to use our dialer for TLS, the main purpose of
// the following line is to make sure ForseSpecificSNI has impact on the
// config we are going to use when doing TLS. The code is as such since
// we used to force net/http through using dialer.DialTLS.
dialer.TLSConfig = baseTransport.TLSClientConfig
// Arrange the configuration such that we always use `dialer` for dialing
// cleartext connections. The net/http code will dial TLS connections.
baseTransport.DialContext = dialer.DialContext
// Better for Cloudflare DNS and also better because we have less
// noisy events and we can better understand what happened.
baseTransport.MaxConnsPerHost = 1
// The following (1) reduces the number of headers that Go will
// automatically send for us and (2) ensures that we always receive
// back the true headers, such as Content-Length. This change is
// functional to OONI's goal of observing the network.
baseTransport.DisableCompression = true
return &HTTPTransport{
Beginning: beginning,
Dialer: dialer,
Handler: handler,
Transport: baseTransport,
roundTripper: ooniTransport,
}
}
// RoundTrip executes a single HTTP transaction, returning
// a Response for the provided Request.
func (t *HTTPTransport) RoundTrip(
req *http.Request,
) (resp *http.Response, err error) {
ctx := maybeWithMeasurementRoot(req.Context(), t.Beginning, t.Handler)
req = req.WithContext(ctx)
resp, err = t.roundTripper.RoundTrip(req)
// For safety wrap the error as modelx.HTTPRoundTripOperation but this
// will only be used if the error chain does not contain any
// other major operation failure. See errorsx.ErrWrapper.
err = errorsx.SafeErrWrapperBuilder{
Error: err,
Operation: errorsx.HTTPRoundTripOperation,
}.MaybeBuild()
return resp, err
}
// CloseIdleConnections closes the idle connections.
func (t *HTTPTransport) CloseIdleConnections() {
// Adapted from net/http code
type closeIdler interface {
CloseIdleConnections()
}
if tr, ok := t.roundTripper.(closeIdler); ok {
tr.CloseIdleConnections()
}
}
// NewHTTPTransportWithProxyFunc creates a transport without any
// handler attached using the specified proxy func.
func NewHTTPTransportWithProxyFunc(
proxyFunc func(*http.Request) (*url.URL, error),
) *HTTPTransport {
return newHTTPTransport(time.Now(), handlers.NoHandler, NewDialer(), false, proxyFunc)
}
// NewHTTPTransport creates a new HTTP transport.
func NewHTTPTransport() *HTTPTransport {
return NewHTTPTransportWithProxyFunc(http.ProxyFromEnvironment)
}
// ConfigureDNS is exactly like netx.Dialer.ConfigureDNS.
func (t *HTTPTransport) ConfigureDNS(network, address string) error {
return t.Dialer.ConfigureDNS(network, address)
}
// SetResolver is exactly like netx.Dialer.SetResolver.
func (t *HTTPTransport) SetResolver(r modelx.DNSResolver) {
t.Dialer.SetResolver(r)
}
// SetCABundle internally calls netx.Dialer.SetCABundle and
// therefore it has the same caveats and limitations.
func (t *HTTPTransport) SetCABundle(path string) error {
return t.Dialer.SetCABundle(path)
}
// ForceSpecificSNI forces using a specific SNI.
func (t *HTTPTransport) ForceSpecificSNI(sni string) error {
return t.Dialer.ForceSpecificSNI(sni)
}
// ForceSkipVerify forces to skip certificate verification
func (t *HTTPTransport) ForceSkipVerify() error {
return t.Dialer.ForceSkipVerify()
}
// HTTPClient is a replacement for http.HTTPClient.
type HTTPClient struct {
// HTTPClient is the underlying client. Pass this client to existing code
// that expects an *http.HTTPClient. For this reason we can't embed it.
HTTPClient *http.Client
// Transport is the transport configured by NewClient to be used
// by the HTTPClient field.
Transport *HTTPTransport
}
// NewHTTPClientWithProxyFunc creates a new client using the
// specified proxyFunc for handling proxying.
func NewHTTPClientWithProxyFunc(
proxyFunc func(*http.Request) (*url.URL, error),
) *HTTPClient {
transport := NewHTTPTransportWithProxyFunc(proxyFunc)
return &HTTPClient{
HTTPClient: &http.Client{Transport: transport},
Transport: transport,
}
}
// NewHTTPClient creates a new client instance.
func NewHTTPClient() *HTTPClient {
return NewHTTPClientWithProxyFunc(http.ProxyFromEnvironment)
}
// NewHTTPClientWithoutProxy creates a new client instance that
// does not use any kind of proxy.
func NewHTTPClientWithoutProxy() *HTTPClient {
return NewHTTPClientWithProxyFunc(nil)
}
// ConfigureDNS internally calls netx.Dialer.ConfigureDNS and
// therefore it has the same caveats and limitations.
func (c *HTTPClient) ConfigureDNS(network, address string) error {
return c.Transport.ConfigureDNS(network, address)
}
// SetResolver internally calls netx.Dialer.SetResolver
func (c *HTTPClient) SetResolver(r modelx.DNSResolver) {
c.Transport.SetResolver(r)
}
// SetCABundle internally calls netx.Dialer.SetCABundle and
// therefore it has the same caveats and limitations.
func (c *HTTPClient) SetCABundle(path string) error {
return c.Transport.SetCABundle(path)
}
// ForceSpecificSNI forces using a specific SNI.
func (c *HTTPClient) ForceSpecificSNI(sni string) error {
return c.Transport.ForceSpecificSNI(sni)
}
// ForceSkipVerify forces to skip certificate verification
func (c *HTTPClient) ForceSkipVerify() error {
return c.Transport.ForceSkipVerify()
}
// CloseIdleConnections closes the idle connections.
func (c *HTTPClient) CloseIdleConnections() {
c.Transport.CloseIdleConnections()
}