@@ -22,10 +22,15 @@ import (
2222 "crypto/tls"
2323 "crypto/x509"
2424 "fmt"
25+ "io"
2526 "io/ioutil"
27+ "net/http"
28+ "net/http/httptrace"
29+ "net/http/httputil"
2630 "strings"
2731
2832 "github.com/containerd/console"
33+ "github.com/containerd/containerd/log"
2934 "github.com/containerd/containerd/remotes"
3035 "github.com/containerd/containerd/remotes/docker"
3136 "github.com/containerd/containerd/remotes/docker/config"
@@ -96,6 +101,16 @@ func GetResolver(ctx gocontext.Context, clicontext *cli.Context) (remotes.Resolv
96101 hostOptions .HostDir = config .HostDirFromRoot (hostDir )
97102 }
98103
104+ if clicontext .Bool ("http-dump" ) {
105+ hostOptions .UpdateClient = func (client * http.Client ) error {
106+ client .Transport = & DebugTransport {
107+ transport : client .Transport ,
108+ writer : log .G (ctx ).Writer (),
109+ }
110+ return nil
111+ }
112+ }
113+
99114 options .Hosts = config .ConfigureHosts (ctx , hostOptions )
100115
101116 return docker .NewResolver (options ), nil
@@ -135,3 +150,57 @@ func resolverDefaultTLS(clicontext *cli.Context) (*tls.Config, error) {
135150
136151 return config , nil
137152}
153+
154+ // DebugTransport wraps the underlying http.RoundTripper interface and dumps all requests/responses to the writer.
155+ type DebugTransport struct {
156+ transport http.RoundTripper
157+ writer io.Writer
158+ }
159+
160+ // RoundTrip dumps request/responses and executes the request using the underlying transport.
161+ func (t DebugTransport ) RoundTrip (req * http.Request ) (* http.Response , error ) {
162+ in , err := httputil .DumpRequest (req , true )
163+ if err != nil {
164+ return nil , errors .Wrap (err , "failed to dump request" )
165+ }
166+
167+ if _ , err := t .writer .Write (in ); err != nil {
168+ return nil , err
169+ }
170+
171+ resp , err := t .transport .RoundTrip (req )
172+ if err != nil {
173+ return nil , err
174+ }
175+
176+ out , err := httputil .DumpResponse (resp , true )
177+ if err != nil {
178+ return nil , errors .Wrap (err , "failed to dump response" )
179+ }
180+
181+ if _ , err := t .writer .Write (out ); err != nil {
182+ return nil , err
183+ }
184+
185+ return resp , err
186+ }
187+
188+ // NewDebugClientTrace returns a Go http trace client predefined to write DNS and connection
189+ // information to the log. This is used via the --http-trace flag on push and pull operations in ctr.
190+ func NewDebugClientTrace (ctx gocontext.Context ) * httptrace.ClientTrace {
191+ return & httptrace.ClientTrace {
192+ DNSStart : func (dnsInfo httptrace.DNSStartInfo ) {
193+ log .G (ctx ).WithField ("host" , dnsInfo .Host ).Debugf ("DNS lookup" )
194+ },
195+ DNSDone : func (dnsInfo httptrace.DNSDoneInfo ) {
196+ if dnsInfo .Err != nil {
197+ log .G (ctx ).WithField ("lookup_err" , dnsInfo .Err ).Debugf ("DNS lookup error" )
198+ } else {
199+ log .G (ctx ).WithField ("result" , dnsInfo .Addrs [0 ].String ()).WithField ("coalesced" , dnsInfo .Coalesced ).Debugf ("DNS lookup complete" )
200+ }
201+ },
202+ GotConn : func (connInfo httptrace.GotConnInfo ) {
203+ log .G (ctx ).WithField ("reused" , connInfo .Reused ).WithField ("remote_addr" , connInfo .Conn .RemoteAddr ().String ()).Debugf ("Connection successful" )
204+ },
205+ }
206+ }
0 commit comments