207 lines
7.0 KiB
Go
207 lines
7.0 KiB
Go
|
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/engine/netx/errorx"
|
||
|
"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 errorx.ErrWrapper.
|
||
|
err = errorx.SafeErrWrapperBuilder{
|
||
|
Error: err,
|
||
|
Operation: errorx.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()
|
||
|
}
|