github.com/coreos/goproxy@v0.0.0-20190513173959-f8dc2d7ba04e/examples/goproxy-httpdump/httpdump.go (about)

     1  package main
     2  
     3  import (
     4  	"errors"
     5  	"flag"
     6  	"fmt"
     7  	"io"
     8  	"log"
     9  	"net"
    10  	"net/http"
    11  	"net/http/httputil"
    12  	"os"
    13  	"os/signal"
    14  	"path"
    15  	"sync"
    16  	"time"
    17  
    18  	"github.com/elazarl/goproxy"
    19  	"github.com/elazarl/goproxy/transport"
    20  )
    21  
    22  type FileStream struct {
    23  	path string
    24  	f    *os.File
    25  }
    26  
    27  func NewFileStream(path string) *FileStream {
    28  	return &FileStream{path, nil}
    29  }
    30  
    31  func (fs *FileStream) Write(b []byte) (nr int, err error) {
    32  	if fs.f == nil {
    33  		fs.f, err = os.Create(fs.path)
    34  		if err != nil {
    35  			return 0, err
    36  		}
    37  	}
    38  	return fs.f.Write(b)
    39  }
    40  
    41  func (fs *FileStream) Close() error {
    42  	fmt.Println("Close", fs.path)
    43  	if fs.f == nil {
    44  		return errors.New("FileStream was never written into")
    45  	}
    46  	return fs.f.Close()
    47  }
    48  
    49  type Meta struct {
    50  	req      *http.Request
    51  	resp     *http.Response
    52  	err      error
    53  	t        time.Time
    54  	sess     int64
    55  	bodyPath string
    56  	from     string
    57  }
    58  
    59  func fprintf(nr *int64, err *error, w io.Writer, pat string, a ...interface{}) {
    60  	if *err != nil {
    61  		return
    62  	}
    63  	var n int
    64  	n, *err = fmt.Fprintf(w, pat, a...)
    65  	*nr += int64(n)
    66  }
    67  
    68  func write(nr *int64, err *error, w io.Writer, b []byte) {
    69  	if *err != nil {
    70  		return
    71  	}
    72  	var n int
    73  	n, *err = w.Write(b)
    74  	*nr += int64(n)
    75  }
    76  
    77  func (m *Meta) WriteTo(w io.Writer) (nr int64, err error) {
    78  	if m.req != nil {
    79  		fprintf(&nr, &err, w, "Type: request\r\n")
    80  	} else if m.resp != nil {
    81  		fprintf(&nr, &err, w, "Type: response\r\n")
    82  	}
    83  	fprintf(&nr, &err, w, "ReceivedAt: %v\r\n", m.t)
    84  	fprintf(&nr, &err, w, "Session: %d\r\n", m.sess)
    85  	fprintf(&nr, &err, w, "From: %v\r\n", m.from)
    86  	if m.err != nil {
    87  		// note the empty response
    88  		fprintf(&nr, &err, w, "Error: %v\r\n\r\n\r\n\r\n", m.err)
    89  	} else if m.req != nil {
    90  		fprintf(&nr, &err, w, "\r\n")
    91  		buf, err2 := httputil.DumpRequest(m.req, false)
    92  		if err2 != nil {
    93  			return nr, err2
    94  		}
    95  		write(&nr, &err, w, buf)
    96  	} else if m.resp != nil {
    97  		fprintf(&nr, &err, w, "\r\n")
    98  		buf, err2 := httputil.DumpResponse(m.resp, false)
    99  		if err2 != nil {
   100  			return nr, err2
   101  		}
   102  		write(&nr, &err, w, buf)
   103  	}
   104  	return
   105  }
   106  
   107  // HttpLogger is an asynchronous HTTP request/response logger. It traces
   108  // requests and responses headers in a "log" file in logger directory and dumps
   109  // their bodies in files prefixed with the session identifiers.
   110  // Close it to ensure pending items are correctly logged.
   111  type HttpLogger struct {
   112  	path  string
   113  	c     chan *Meta
   114  	errch chan error
   115  }
   116  
   117  func NewLogger(basepath string) (*HttpLogger, error) {
   118  	f, err := os.Create(path.Join(basepath, "log"))
   119  	if err != nil {
   120  		return nil, err
   121  	}
   122  	logger := &HttpLogger{basepath, make(chan *Meta), make(chan error)}
   123  	go func() {
   124  		for m := range logger.c {
   125  			if _, err := m.WriteTo(f); err != nil {
   126  				log.Println("Can't write meta", err)
   127  			}
   128  		}
   129  		logger.errch <- f.Close()
   130  	}()
   131  	return logger, nil
   132  }
   133  
   134  func (logger *HttpLogger) LogResp(resp *http.Response, ctx *goproxy.ProxyCtx) {
   135  	body := path.Join(logger.path, fmt.Sprintf("%d_resp", ctx.Session))
   136  	from := ""
   137  	if ctx.UserData != nil {
   138  		from = ctx.UserData.(*transport.RoundTripDetails).TCPAddr.String()
   139  	}
   140  	if resp == nil {
   141  		resp = emptyResp
   142  	} else {
   143  		resp.Body = NewTeeReadCloser(resp.Body, NewFileStream(body))
   144  	}
   145  	logger.LogMeta(&Meta{
   146  		resp: resp,
   147  		err:  ctx.Error,
   148  		t:    time.Now(),
   149  		sess: ctx.Session,
   150  		from: from})
   151  }
   152  
   153  var emptyResp = &http.Response{}
   154  var emptyReq = &http.Request{}
   155  
   156  func (logger *HttpLogger) LogReq(req *http.Request, ctx *goproxy.ProxyCtx) {
   157  	body := path.Join(logger.path, fmt.Sprintf("%d_req", ctx.Session))
   158  	if req == nil {
   159  		req = emptyReq
   160  	} else {
   161  		req.Body = NewTeeReadCloser(req.Body, NewFileStream(body))
   162  	}
   163  	logger.LogMeta(&Meta{
   164  		req:  req,
   165  		err:  ctx.Error,
   166  		t:    time.Now(),
   167  		sess: ctx.Session,
   168  		from: req.RemoteAddr})
   169  }
   170  
   171  func (logger *HttpLogger) LogMeta(m *Meta) {
   172  	logger.c <- m
   173  }
   174  
   175  func (logger *HttpLogger) Close() error {
   176  	close(logger.c)
   177  	return <-logger.errch
   178  }
   179  
   180  // TeeReadCloser extends io.TeeReader by allowing reader and writer to be
   181  // closed.
   182  type TeeReadCloser struct {
   183  	r io.Reader
   184  	w io.WriteCloser
   185  	c io.Closer
   186  }
   187  
   188  func NewTeeReadCloser(r io.ReadCloser, w io.WriteCloser) io.ReadCloser {
   189  	return &TeeReadCloser{io.TeeReader(r, w), w, r}
   190  }
   191  
   192  func (t *TeeReadCloser) Read(b []byte) (int, error) {
   193  	return t.r.Read(b)
   194  }
   195  
   196  // Close attempts to close the reader and write. It returns an error if both
   197  // failed to Close.
   198  func (t *TeeReadCloser) Close() error {
   199  	err1 := t.c.Close()
   200  	err2 := t.w.Close()
   201  	if err1 != nil {
   202  		return err1
   203  	}
   204  	return err2
   205  }
   206  
   207  // stoppableListener serves stoppableConn and tracks their lifetime to notify
   208  // when it is safe to terminate the application.
   209  type stoppableListener struct {
   210  	net.Listener
   211  	sync.WaitGroup
   212  }
   213  
   214  type stoppableConn struct {
   215  	net.Conn
   216  	wg *sync.WaitGroup
   217  }
   218  
   219  func newStoppableListener(l net.Listener) *stoppableListener {
   220  	return &stoppableListener{l, sync.WaitGroup{}}
   221  }
   222  
   223  func (sl *stoppableListener) Accept() (net.Conn, error) {
   224  	c, err := sl.Listener.Accept()
   225  	if err != nil {
   226  		return c, err
   227  	}
   228  	sl.Add(1)
   229  	return &stoppableConn{c, &sl.WaitGroup}, nil
   230  }
   231  
   232  func (sc *stoppableConn) Close() error {
   233  	sc.wg.Done()
   234  	return sc.Conn.Close()
   235  }
   236  
   237  func main() {
   238  	verbose := flag.Bool("v", false, "should every proxy request be logged to stdout")
   239  	addr := flag.String("l", ":8080", "on which address should the proxy listen")
   240  	flag.Parse()
   241  	proxy := goproxy.NewProxyHttpServer()
   242  	proxy.Verbose = *verbose
   243  	if err := os.MkdirAll("db", 0755); err != nil {
   244  		log.Fatal("Can't create dir", err)
   245  	}
   246  	logger, err := NewLogger("db")
   247  	if err != nil {
   248  		log.Fatal("can't open log file", err)
   249  	}
   250  	tr := transport.Transport{Proxy: transport.ProxyFromEnvironment}
   251  	// For every incoming request, override the RoundTripper to extract
   252  	// connection information. Store it is session context log it after
   253  	// handling the response.
   254  	proxy.OnRequest().DoFunc(func(req *http.Request, ctx *goproxy.ProxyCtx) (*http.Request, *http.Response) {
   255  		ctx.RoundTripper = goproxy.RoundTripperFunc(func(req *http.Request, ctx *goproxy.ProxyCtx) (resp *http.Response, err error) {
   256  			ctx.UserData, resp, err = tr.DetailedRoundTrip(req)
   257  			return
   258  		})
   259  		logger.LogReq(req, ctx)
   260  		return req, nil
   261  	})
   262  	proxy.OnResponse().DoFunc(func(resp *http.Response, ctx *goproxy.ProxyCtx) *http.Response {
   263  		logger.LogResp(resp, ctx)
   264  		return resp
   265  	})
   266  	l, err := net.Listen("tcp", *addr)
   267  	if err != nil {
   268  		log.Fatal("listen:", err)
   269  	}
   270  	sl := newStoppableListener(l)
   271  	ch := make(chan os.Signal)
   272  	signal.Notify(ch, os.Interrupt)
   273  	go func() {
   274  		<-ch
   275  		log.Println("Got SIGINT exiting")
   276  		sl.Add(1)
   277  		sl.Close()
   278  		logger.Close()
   279  		sl.Done()
   280  	}()
   281  	log.Println("Starting Proxy")
   282  	http.Serve(sl, proxy)
   283  	sl.Wait()
   284  	log.Println("All connections closed - exit")
   285  }