2021-06-26 18:11:47 +02:00
|
|
|
package netxlite
|
|
|
|
|
2021-07-01 15:26:08 +02:00
|
|
|
import (
|
|
|
|
"context"
|
|
|
|
"net"
|
|
|
|
"net/http"
|
|
|
|
"time"
|
|
|
|
)
|
2021-06-26 18:11:47 +02:00
|
|
|
|
|
|
|
// HTTPTransport is an http.Transport-like structure.
|
|
|
|
type HTTPTransport interface {
|
|
|
|
// RoundTrip performs the HTTP round trip.
|
|
|
|
RoundTrip(req *http.Request) (*http.Response, error)
|
|
|
|
|
|
|
|
// CloseIdleConnections closes idle connections.
|
|
|
|
CloseIdleConnections()
|
|
|
|
}
|
|
|
|
|
2021-09-05 14:49:38 +02:00
|
|
|
// httpTransportLogger is an HTTPTransport with logging.
|
|
|
|
type httpTransportLogger struct {
|
2021-06-26 18:11:47 +02:00
|
|
|
// HTTPTransport is the underlying HTTP transport.
|
|
|
|
HTTPTransport HTTPTransport
|
|
|
|
|
|
|
|
// Logger is the underlying logger.
|
|
|
|
Logger Logger
|
|
|
|
}
|
|
|
|
|
2021-09-05 14:49:38 +02:00
|
|
|
var _ HTTPTransport = &httpTransportLogger{}
|
2021-06-26 18:11:47 +02:00
|
|
|
|
|
|
|
// RoundTrip implements HTTPTransport.RoundTrip.
|
2021-09-05 14:49:38 +02:00
|
|
|
func (txp *httpTransportLogger) RoundTrip(req *http.Request) (*http.Response, error) {
|
2021-06-26 18:11:47 +02:00
|
|
|
host := req.Host
|
|
|
|
if host == "" {
|
|
|
|
host = req.URL.Host
|
|
|
|
}
|
|
|
|
req.Header.Set("Host", host) // anticipate what Go would do
|
|
|
|
return txp.logTrip(req)
|
|
|
|
}
|
|
|
|
|
|
|
|
// logTrip is an HTTP round trip with logging.
|
2021-09-05 14:49:38 +02:00
|
|
|
func (txp *httpTransportLogger) logTrip(req *http.Request) (*http.Response, error) {
|
2021-06-26 18:11:47 +02:00
|
|
|
txp.Logger.Debugf("> %s %s", req.Method, req.URL.String())
|
|
|
|
for key, values := range req.Header {
|
|
|
|
for _, value := range values {
|
|
|
|
txp.Logger.Debugf("> %s: %s", key, value)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
txp.Logger.Debug(">")
|
|
|
|
resp, err := txp.HTTPTransport.RoundTrip(req)
|
|
|
|
if err != nil {
|
|
|
|
txp.Logger.Debugf("< %s", err)
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
txp.Logger.Debugf("< %d", resp.StatusCode)
|
|
|
|
for key, values := range resp.Header {
|
|
|
|
for _, value := range values {
|
|
|
|
txp.Logger.Debugf("< %s: %s", key, value)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
txp.Logger.Debug("<")
|
|
|
|
return resp, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// CloseIdleConnections implement HTTPTransport.CloseIdleConnections.
|
2021-09-05 14:49:38 +02:00
|
|
|
func (txp *httpTransportLogger) CloseIdleConnections() {
|
2021-06-26 18:11:47 +02:00
|
|
|
txp.HTTPTransport.CloseIdleConnections()
|
|
|
|
}
|
2021-07-01 15:26:08 +02:00
|
|
|
|
2021-09-06 16:53:28 +02:00
|
|
|
// httpTransportConnectionsCloser is an HTTPTransport that
|
|
|
|
// correctly forwards CloseIdleConnections.
|
|
|
|
type httpTransportConnectionsCloser struct {
|
|
|
|
HTTPTransport
|
|
|
|
Dialer
|
|
|
|
TLSDialer
|
|
|
|
}
|
|
|
|
|
|
|
|
// CloseIdleConnections forwards the CloseIdleConnections calls.
|
|
|
|
func (txp *httpTransportConnectionsCloser) CloseIdleConnections() {
|
|
|
|
txp.HTTPTransport.CloseIdleConnections()
|
|
|
|
txp.Dialer.CloseIdleConnections()
|
|
|
|
txp.TLSDialer.CloseIdleConnections()
|
|
|
|
}
|
|
|
|
|
|
|
|
// NewHTTPTransport creates a new HTTP transport using the given
|
|
|
|
// dialer and TLS handshaker to create connections.
|
|
|
|
//
|
|
|
|
// We need a TLS handshaker here, as opposed to a TLSDialer, because we
|
|
|
|
// wrap the dialer we'll use to enforce timeouts for HTTP idle
|
|
|
|
// connections (see https://github.com/ooni/probe/issues/1609 for more info).
|
|
|
|
func NewHTTPTransport(dialer Dialer, tlsHandshaker TLSHandshaker) HTTPTransport {
|
|
|
|
// TODO(bassosimone): here we should copy code living inside the
|
|
|
|
// websteps prototype to use the oohttp library.
|
2021-07-01 15:26:08 +02:00
|
|
|
txp := http.DefaultTransport.(*http.Transport).Clone()
|
2021-09-06 16:53:28 +02:00
|
|
|
// This wrapping ensures that we always have a timeout when we
|
|
|
|
// are using HTTP; see https://github.com/ooni/probe/issues/1609.
|
2021-07-01 15:26:08 +02:00
|
|
|
dialer = &httpDialerWithReadTimeout{dialer}
|
|
|
|
txp.DialContext = dialer.DialContext
|
2021-09-06 16:53:28 +02:00
|
|
|
tlsDialer := NewTLSDialer(dialer, tlsHandshaker)
|
|
|
|
txp.DialTLSContext = tlsDialer.DialTLSContext
|
2021-07-01 15:26:08 +02:00
|
|
|
// Better for Cloudflare DNS and also better because we have less
|
|
|
|
// noisy events and we can better understand what happened.
|
|
|
|
txp.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.
|
|
|
|
txp.DisableCompression = true
|
2021-09-06 16:53:28 +02:00
|
|
|
txp.ForceAttemptHTTP2 = true
|
|
|
|
// Ensure we correctly forward CloseIdleConnections.
|
|
|
|
return &httpTransportConnectionsCloser{
|
|
|
|
HTTPTransport: txp,
|
|
|
|
Dialer: dialer,
|
|
|
|
TLSDialer: tlsDialer,
|
|
|
|
}
|
2021-07-01 15:26:08 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
// httpDialerWithReadTimeout enforces a read timeout for all HTTP
|
|
|
|
// connections. See https://github.com/ooni/probe/issues/1609.
|
|
|
|
type httpDialerWithReadTimeout struct {
|
|
|
|
Dialer
|
|
|
|
}
|
|
|
|
|
|
|
|
// DialContext implements Dialer.DialContext.
|
|
|
|
func (d *httpDialerWithReadTimeout) DialContext(
|
|
|
|
ctx context.Context, network, address string) (net.Conn, error) {
|
|
|
|
conn, err := d.Dialer.DialContext(ctx, network, address)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
return &httpConnWithReadTimeout{conn}, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// httpConnWithReadTimeout enforces a read timeout for all HTTP
|
|
|
|
// connections. See https://github.com/ooni/probe/issues/1609.
|
|
|
|
type httpConnWithReadTimeout struct {
|
|
|
|
net.Conn
|
|
|
|
}
|
|
|
|
|
|
|
|
// Read implements Conn.Read.
|
|
|
|
func (c *httpConnWithReadTimeout) Read(b []byte) (int, error) {
|
|
|
|
c.Conn.SetReadDeadline(time.Now().Add(30 * time.Second))
|
|
|
|
defer c.Conn.SetReadDeadline(time.Time{})
|
|
|
|
return c.Conn.Read(b)
|
|
|
|
}
|